Mise à jour
Mise à jour de la base de données, veuillez patienter...
Site original : Tiger-222
# À adapter suivant votre distribution, ici pour celles basées sur Debian :
$ aptitude install zsh
# Pour tout le monde
$ wget --no-check-certificate https://tiger-222.fr/files/zsh.tar
$ tar -xf zsh.tar
$ rm zsh.tar
$ mv -f zshenv zshrc zlogout /etc/zsh
# Changement de shell pour l'utilisateur courant :
$ chsh
# Entrez /bin/zsh puis déconnectez-vous.
# À adapter suivant votre distribution, ici pour celles basées sur Debian :
$ aptitude install zsh
# Pour tout le monde
$ wget --no-check-certificate https://tiger-222.fr/files/zsh.tar
$ tar -xf zsh.tar
$ rm zsh.tar
$ mv -f zshenv zshrc zlogout /etc/zsh
# Changement de shell pour l'utilisateur courant :
$ chsh
# Entrez /bin/zsh puis déconnectez-vous.
from Quartz import *
# Nécessaire pour l'enregistrement de la capture dans un fichier.
# Ici, on utilise le PNG, mais d'autres formats existent [UTType]
from LaunchServices import kUTTypePNG
[UTType]
Dans le document UTType Reference, on peut trouver, entre autres, le type JPEG (kUTTypeJPEG
) ou encore GIF (kUTTypeGIF
).def enum_display_monitors(oneshot=False):
# Si oneshot est à True, alors on récupère les informations de tous
# les écrans d'un coup.
# Retourne une liste de dictionnaires contenant les informations
# des écran.
results = []
if oneshot:
rect = CGRectInfinite
results.append({
b'left' : int(rect.origin.x),
b'top' : int(rect.origin.y),
b'width' : int(rect.size.width),
b'height' : int(rect.size.height)
})
else:
max_displays = 32 # Peut-être augmenté, si besoin...
rotations = {0.0: 'normal', 90.0: 'right', -90.0: 'left'}
res, ids, count = CGGetActiveDisplayList(max_displays, None, None)
for display in ids:
rect = CGRectStandardize(CGDisplayBounds(display))
left, top = rect.origin.x, rect.origin.y
width, height = rect.size.width, rect.size.height
# La fonction suivante retourne un double pour exprimer la
# rotation de l'écran : 0.0, 90.0, -90.0, ... On le traduit
# par normal, left ou right, plus compréhensible.
rot = CGDisplayRotation(display)
rotation = rotations[rot]
if rotation in ['left', 'right']:
width, height = height, width
results.append({
b'left' : int(left),
b'top' : int(top),
b'width' : int(width),
b'height' : int(height),
b'rotation': rotation
})
return results
Si onshot=True
, nous utilisons CGRectInfinite
qui renvoie des données assez... monstrueuses ! Je vous laisse tester pour voir ce que ça donne, ça n'est pas utilisable en l'état. Mais une option dans la fonction suivante permettra de rectifier le tir.# Un seul écran :
[{'width': 1280, 'top': 0, 'height': 800, 'left': 0}]
# Deux écrans :
[
{'width': 1280, 'top': 0, 'rotation': 'normal', 'height': 800, 'left': 0},
{'width': 1360, 'top': 32, 'rotation': 'normal', 'height': 768, 'left': -1360}
]
# Deux écrans, oneshot=True :
[{'width': 179769..., 'top': -89884..., 'height': 179769..., 'left': -89884...}]
def get_pixels_and_save(monitor, filename):
# Récupérer les pixels d'un écran, puis enregistrement.
width, height = monitor[b'width'], monitor[b'height']
left, top = monitor[b'left'], monitor[b'top']
dpi = 72
# Récupération des données brutes
rect = CGRect((left, top), (width, height))
image = CGWindowListCreateImage(
rect, kCGWindowListOptionOnScreenOnly,
kCGNullWindowID, kCGWindowImageDefault)
# Enregistrement
url = NSURL.fileURLWithPath_(filename)
dest = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, None)
properties = {
kCGImagePropertyDPIWidth: dpi,
kCGImagePropertyDPIHeight: dpi,
}
CGImageDestinationAddImage(dest, image, properties)
if CGImageDestinationFinalize(dest):
print('Fichier {0} créé.'.format(filename))
L'option kCGWindowListOptionOnScreenOnly
permet de ne créer une image que de ce qui est affiché à l'écran. Elle permet de ne pas avoir une image improbable lorsqu'on récupère les dimensions via CGRectInfinite
.if __name__ == '__main__':
# Une capture par écran
i = 1
for monitor in enum_display_monitors():
filename = 'mss-capture-{0}.png'.format(i)
get_pixels_and_save(monitor, filename)
i += 1
# Capture complète
monitor = enum_display_monitors(oneshot=True)[0]
filename = 'mss-capture-complet.png'
get_pixels_and_save(monitor, filename)
from Quartz import *
# Nécessaire pour l'enregistrement de la capture dans un fichier.
# Ici, on utilise le PNG, mais d'autres formats existent [UTType]
from LaunchServices import kUTTypePNG
[UTType]
Dans le document UTType Reference, on peut trouver, entre autres, le type JPEG (kUTTypeJPEG
) ou encore GIF (kUTTypeGIF
).def enum_display_monitors(oneshot=False):
# Si oneshot est à True, alors on récupère les informations de tous
# les écrans d'un coup.
# Retourne une liste de dictionnaires contenant les informations
# des écran.
results = []
if oneshot:
rect = CGRectInfinite
results.append({
b'left' : int(rect.origin.x),
b'top' : int(rect.origin.y),
b'width' : int(rect.size.width),
b'height' : int(rect.size.height)
})
else:
max_displays = 32 # Peut-être augmenté, si besoin...
rotations = {0.0: 'normal', 90.0: 'right', -90.0: 'left'}
res, ids, count = CGGetActiveDisplayList(max_displays, None, None)
for display in ids:
rect = CGRectStandardize(CGDisplayBounds(display))
left, top = rect.origin.x, rect.origin.y
width, height = rect.size.width, rect.size.height
# La fonction suivante retourne un double pour exprimer la
# rotation de l'écran : 0.0, 90.0, -90.0, ... On le traduit
# par normal, left ou right, plus compréhensible.
rot = CGDisplayRotation(display)
rotation = rotations[rot]
if rotation in ['left', 'right']:
width, height = height, width
results.append({
b'left' : int(left),
b'top' : int(top),
b'width' : int(width),
b'height' : int(height),
b'rotation': rotation
})
return results
Si onshot=True
, nous utilisons CGRectInfinite
qui renvoie des données assez... monstrueuses ! Je vous laisse tester pour voir ce que ça donne, ça n'est pas utilisable en l'état. Mais une option dans la fonction suivante permettra de rectifier le tir.# Un seul écran :
[{'width': 1280, 'top': 0, 'height': 800, 'left': 0}]
# Deux écrans :
[
{'width': 1280, 'top': 0, 'rotation': 'normal', 'height': 800, 'left': 0},
{'width': 1360, 'top': 32, 'rotation': 'normal', 'height': 768, 'left': -1360}
]
# Deux écrans, oneshot=True :
[{'width': 179769..., 'top': -89884..., 'height': 179769..., 'left': -89884...}]
def get_pixels_and_save(monitor, filename):
# Récupérer les pixels d'un écran, puis enregistrement.
width, height = monitor[b'width'], monitor[b'height']
left, top = monitor[b'left'], monitor[b'top']
dpi = 72
# Récupération des données brutes
rect = CGRect((left, top), (width, height))
image = CGWindowListCreateImage(
rect, kCGWindowListOptionOnScreenOnly,
kCGNullWindowID, kCGWindowImageDefault)
# Enregistrement
url = NSURL.fileURLWithPath_(filename)
dest = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, None)
properties = {
kCGImagePropertyDPIWidth: dpi,
kCGImagePropertyDPIHeight: dpi,
}
CGImageDestinationAddImage(dest, image, properties)
if CGImageDestinationFinalize(dest):
print('Fichier {0} créé.'.format(filename))
L'option kCGWindowListOptionOnScreenOnly
permet de ne créer une image que de ce qui est affiché à l'écran. Elle permet de ne pas avoir une image improbable lorsqu'on récupère les dimensions via CGRectInfinite
.if __name__ == '__main__':
# Une capture par écran
i = 1
for monitor in enum_display_monitors():
filename = 'mss-capture-{0}.png'.format(i)
get_pixels_and_save(monitor, filename)
i += 1
# Capture complète
monitor = enum_display_monitors(oneshot=True)[0]
filename = 'mss-capture-complet.png'
get_pixels_and_save(monitor, filename)
argtypes
et restypes
respectivement.# Pour la capture d'un seul écran, il faut lire un XML de l'utilisateur
from os.path import expanduser, isfile
import xml.etree.ElementTree as ET
# Pour la variable d'environnement $DISPLAY
from os import environ
from struct import pack
from ctypes.util import find_library
from ctypes import byref, cast, cdll
from ctypes import (
c_char_p, c_int, c_int32, c_uint, c_uint32,
c_ulong, c_void_p, POINTER, Structure
)
class Display(Structure):
pass
class XWindowAttributes(Structure):
_fields_ = [
("x", c_int32),
("y", c_int32),
("width", c_int32),
("height", c_int32),
("border_width", c_int32),
("depth", c_int32),
("visual", c_ulong),
("root", c_ulong),
("class", c_int32),
("bit_gravity", c_int32),
("win_gravity", c_int32),
("backing_store", c_int32),
("backing_planes", c_ulong),
("backing_pixel", c_ulong),
("save_under", c_int32),
("colourmap", c_ulong),
("mapinstalled", c_uint32),
("map_state", c_uint32),
("all_event_masks", c_ulong),
("your_event_mask", c_ulong),
("do_not_propagate_mask", c_ulong),
("override_redirect", c_int32),
("screen", c_ulong)
]
class XImage(Structure):
_fields_ = [
('width' , c_int),
('height' , c_int),
('xoffset' , c_int),
('format' , c_int),
('data' , c_char_p),
('byte_order' , c_int),
('bitmap_unit' , c_int),
('bitmap_bit_order' , c_int),
('bitmap_pad' , c_int),
('depth' , c_int),
('bytes_per_line' , c_int),
('bits_per_pixel' , c_int),
('red_mask' , c_ulong),
('green_mask' , c_ulong),
('blue_mask' , c_ulong)
]
def b(x):
return pack(b'
# Chargement de la bibliothèque Xlib
x11 = find_library('X11')
if x11 is None:
raise OSError('MSSLinux: no X11 library found.')
else:
xlib = cdll.LoadLibrary(x11)
XOpenDisplay = xlib.XOpenDisplay
XDefaultScreen = xlib.XDefaultScreen
XDefaultRootWindow = xlib.XDefaultRootWindow
XGetWindowAttributes = xlib.XGetWindowAttributes
XAllPlanes = xlib.XAllPlanes
XGetImage = xlib.XGetImage
XGetPixel = xlib.XGetPixel
XCreateImage = xlib.XCreateImage
XFree = xlib.XFree
Pour plus d'informations sur une fonction, reportez-vous à sa documentation en ligne.argtypes
ne peut être qu'une liste.XOpenDisplay.argtypes = [c_char_p]
XDefaultScreen.argtypes = [POINTER(Display)]
XDefaultRootWindow.argtypes = [POINTER(Display)]
XGetWindowAttributes.argtypes = [POINTER(Display),
POINTER(XWindowAttributes), POINTER(XWindowAttributes)]
XAllPlanes.argtypes = []
XGetImage.argtypes = [POINTER(Display), POINTER(Display),
c_int, c_int, c_uint, c_uint, c_ulong, c_int]
XGetPixel.argtypes = [POINTER(XImage), c_int, c_int]
XCreateImage.argtypes = [POINTER(Display), POINTER(Display),
c_int, c_int, c_uint, c_uint, c_ulong, c_int]
XFree.argtypes = [POINTER(XImage)]
XOpenDisplay.restype = POINTER(Display)
XDefaultScreen.restype = c_int
XDefaultRootWindow.restype = POINTER(XWindowAttributes)
XGetWindowAttributes.restype = c_int
XAllPlanes.restype = c_ulong
XGetImage.restype = POINTER(XImage)
XGetPixel.restype = c_ulong
XCreateImage.restype = POINTER(XImage)
XFree.restype = c_void_p
def enum_display_monitors(oneshot=False):
# Si oneshot est à True, alors on récupère les informations de tous
# les écrans d'un coup.
# Retourne une liste de dictionnaires contenant les informations
# des écran.
results = []
if oneshot:
gwa = XWindowAttributes()
XGetWindowAttributes(display, root_win, byref(gwa))
infos = {
b'left' : int(gwa.x),
b'top' : int(gwa.y),
b'width' : int(gwa.width),
b'height': int(gwa.height)
}
results.append(infos)
else:
# C'est un chouilla plus compliqué, nous devons trouver les infos
# dans le fichier ~/.config/monitors.xml, si présent. Je n'ai pas
# encore trouvé un moyen de le faire à l'aide de Xlib.
monitors = expanduser('~/.config/monitors.xml')
if not isfile(monitors):
print('MSSLinux: _enum_display_monitors() failed (no monitors.xml).')
return enum_display_monitors(oneshot=True)
conf = []
tree = ET.parse(monitors)
root = tree.getroot()
config = root.findall('configuration')[-1]
for output in config.findall('output'):
name = output.get('name')
if name != 'default':
x = output.find('x')
y = output.find('y')
width = output.find('width')
height = output.find('height')
rotation = output.find('rotation')
if None not in [x, y, width, height] and name not in conf:
conf.append(name)
if rotation.text == 'left' or rotation.text == 'right':
width, height = height, width
results.append({
b'left' : int(x.text),
b'top' : int(y.text),
b'width' : int(width.text),
b'height' : int(height.text),
b'rotation': rotation.text
})
return results
<configuration>
<clone>no</clone>
<output name="VGA-0">
<vendor>AIC</vendor>
<product>0x4191</product>
<serial>0x00000346</serial>
<width>1280</width>
<height>1024</height>
<rate>60</rate>
<x>0</x>
<y>0</y>
<rotation>normal</rotation>
<reflect_x>no</reflect_x>
<reflect_y>no</reflect_y>
<primary>yes</primary>
</output>
<output name="DVI-I-0">
</output>
<output name="HDMI-0">
</output>
<output name="DVI-I-1">
<vendor>AOC</vendor>
<product>0x2260</product>
<serial>0x0000051a</serial>
<width>1920</width>
<height>1080</height>
<rate>60</rate>
<x>1280</x>
<y>0</y>
<rotation>left</rotation>
<reflect_x>no</reflect_x>
<reflect_y>no</reflect_y>
<primary>no</primary>
</output>
</configuration>
# Un seul écran :
[{'width': 1280, 'top': 0, 'height': 1024, 'left': 0}]
# Deux écrans :
[
{'width': 1280, 'top': 0, 'rotation': 'normal', 'height': 1024, 'left': 0},
{'width': 1920, 'top': 0, 'rotation': 'left', 'height': 1080, 'left': 1280}
]
# Deux écrans, oneshot=True :
[{'width': 2360, 'top': 0, 'height': 1920, 'left': 0}]
def get_pixels(monitor):
# Récupérer les pixels d'un écran.
width, height = monitor[b'width'], monitor[b'height']
left, top = monitor[b'left'], monitor[b'top']
ZPixmap = 2
allplanes = XAllPlanes()
ZPixmap = 2
# Fix pour XGetImage: expected LP_Display instance instead of LP_XWindowAttributes
root = cast(root_win, POINTER(Display))
image = XGetImage(display, root, left, top, width, height, allplanes, ZPixmap)
if image is None:
raise ValueError('MSSLinux: XGetImage() failed.')
pixels = [b'0'] * (3 * width * height)
for x in range(width):
for y in range(height):
pixel = XGetPixel(image, x, y)
blue = pixel & 255
green = (pixel & 65280) >> 8
red = (pixel & 16711680) >> 16
offset = (x + width * y) * 3
pixels[offset:offset+3] = b(red), b(green), b(blue)
XFree(image)
return b''.join(pixels)
/!\ Dans le code, aux lignes de calcul des pixels r, g, b
, il y a un &
qui traine, il faut le remplacer par &
.MSSImage
(qui permettra de se passer de module tierce pour la sauvegarde des images), nous utiliserons Pillow pour enregistrer l'image dans un fichier.if __name__ == '__main__':
# Utilisation de Pillow (ou PIL) pour enregistrer l'image.
# MSSImage est prêt mais trop long à inclure ici.
from PIL import Image, ImageFile
def pil_save(filename, width, height):
buffer_len = (width * 3 + 3) & -4
img = Image.frombuffer('RGB', (width, height), pixels, 'raw',
'RGB', buffer_len, 1)
ImageFile.MAXBLOCK = width * height
img.save(filename, quality=95, optimize=True, progressive=True)
print('Fichier {0} créé.'.format(filename))
# Une capture par écran
i = 1
for monitor in enum_display_monitors():
pixels = get_pixels(monitor)
filename = 'mss-capture-{0}.jpg'.format(i)
pil_save(filename, monitor['width'], monitor['height'])
i += 1
# Capture complète
monitor = enum_display_monitors(oneshot=True)[0]
pixels = get_pixels(monitor)
filename = 'mss-capture-complet.jpg'
pil_save(filename, monitor['width'], monitor['height'])
/!\ Dans le code, à la ligne de définition de buffer_len
, il y a un &
qui traine, il faut le remplacer par &
.-X
. Sinon, vous tomberez sur une faute de segmentation.XGetPixel()
, voir si on peut copier/coller directement une zone de l'écran vers un fichier physique (JPEG/PNG)