|
15 | 15 | c_int, |
16 | 16 | c_int32, |
17 | 17 | c_long, |
| 18 | + c_short, |
18 | 19 | c_ubyte, |
19 | 20 | c_uint, |
20 | 21 | c_uint32, |
21 | 22 | c_ulong, |
22 | 23 | c_ushort, |
23 | 24 | c_void_p, |
| 25 | + cast, |
24 | 26 | ) |
25 | 27 | from typing import Any, Dict, Optional, Tuple, Union |
26 | 28 |
|
@@ -60,6 +62,26 @@ class Event(Structure): |
60 | 62 | ] |
61 | 63 |
|
62 | 64 |
|
| 65 | +class XFixesCursorImage(Structure): |
| 66 | + """ |
| 67 | + XFixes is an X Window System extension. |
| 68 | + See /usr/include/X11/extensions/Xfixes.h |
| 69 | + """ |
| 70 | + |
| 71 | + _fields_ = [ |
| 72 | + ("x", c_short), |
| 73 | + ("y", c_short), |
| 74 | + ("width", c_ushort), |
| 75 | + ("height", c_ushort), |
| 76 | + ("xhot", c_ushort), |
| 77 | + ("yhot", c_ushort), |
| 78 | + ("cursor_serial", c_ulong), |
| 79 | + ("pixels", POINTER(c_ulong)), |
| 80 | + ("atom", c_ulong), |
| 81 | + ("name", c_char_p), |
| 82 | + ] |
| 83 | + |
| 84 | + |
63 | 85 | class XWindowAttributes(Structure): |
64 | 86 | """Attributes for the specified window.""" |
65 | 87 |
|
@@ -211,6 +233,7 @@ def validate(retval: int, func: Any, args: Tuple[Any, Any]) -> Tuple[Any, Any]: |
211 | 233 | CFUNCTIONS: CFunctions = { |
212 | 234 | "XDefaultRootWindow": ("xlib", [POINTER(Display)], POINTER(XWindowAttributes)), |
213 | 235 | "XDestroyImage": ("xlib", [POINTER(XImage)], c_void_p), |
| 236 | + "XFixesGetCursorImage": ("xfixes", [POINTER(Display)], POINTER(XFixesCursorImage)), |
214 | 237 | "XGetImage": ( |
215 | 238 | "xlib", |
216 | 239 | [ |
@@ -269,15 +292,18 @@ class MSS(MSSBase): |
269 | 292 | It uses intensively the Xlib and its Xrandr extension. |
270 | 293 | """ |
271 | 294 |
|
272 | | - __slots__ = {"drawable", "root", "xlib", "xrandr"} |
| 295 | + __slots__ = {"drawable", "root", "xlib", "xrandr", "xfixes", "__with_cursor"} |
273 | 296 |
|
274 | 297 | # A dict to maintain *display* values created by multiple threads. |
275 | 298 | _display_dict: Dict[threading.Thread, int] = {} |
276 | 299 |
|
277 | | - def __init__(self, display: Optional[Union[bytes, str]] = None) -> None: |
| 300 | + def __init__( |
| 301 | + self, display: Optional[Union[bytes, str]] = None, with_cursor: bool = False |
| 302 | + ) -> None: |
278 | 303 | """GNU/Linux initialisations.""" |
279 | 304 |
|
280 | 305 | super().__init__() |
| 306 | + self.with_cursor = with_cursor |
281 | 307 |
|
282 | 308 | if not display: |
283 | 309 | try: |
@@ -306,6 +332,13 @@ def __init__(self, display: Optional[Union[bytes, str]] = None) -> None: |
306 | 332 | raise ScreenShotError("No Xrandr extension found.") |
307 | 333 | self.xrandr = ctypes.cdll.LoadLibrary(xrandr) |
308 | 334 |
|
| 335 | + if self.with_cursor: |
| 336 | + xfixes = ctypes.util.find_library("Xfixes") |
| 337 | + if xfixes: |
| 338 | + self.xfixes = ctypes.cdll.LoadLibrary(xfixes) |
| 339 | + else: |
| 340 | + self.with_cursor = False |
| 341 | + |
309 | 342 | self._set_cfunctions() |
310 | 343 |
|
311 | 344 | self.root = self.xlib.XDefaultRootWindow(self._get_display(display)) |
@@ -361,6 +394,7 @@ def _set_cfunctions(self) -> None: |
361 | 394 | attrs = { |
362 | 395 | "xlib": self.xlib, |
363 | 396 | "xrandr": self.xrandr, |
| 397 | + "xfixes": getattr(self, "xfixes", None), |
364 | 398 | } |
365 | 399 | for func, (attr, argtypes, restype) in CFUNCTIONS.items(): |
366 | 400 | with contextlib.suppress(AttributeError): |
@@ -450,3 +484,32 @@ def _grab_impl(self, monitor: Monitor) -> ScreenShot: |
450 | 484 | self.xlib.XDestroyImage(ximage) |
451 | 485 |
|
452 | 486 | return self.cls_image(data, monitor) |
| 487 | + |
| 488 | + def _cursor_impl(self) -> ScreenShot: |
| 489 | + """Retrieve all cursor data. Pixels have to be RGB.""" |
| 490 | + |
| 491 | + # Read data of cursor/mouse-pointer |
| 492 | + cursor_data = self.xfixes.XFixesGetCursorImage(self._get_display()) |
| 493 | + if not (cursor_data and cursor_data.contents): |
| 494 | + raise ScreenShotError("Cannot read XFixesGetCursorImage()") |
| 495 | + |
| 496 | + ximage: XFixesCursorImage = cursor_data.contents |
| 497 | + monitor = { |
| 498 | + "left": ximage.x - ximage.xhot, |
| 499 | + "top": ximage.y - ximage.yhot, |
| 500 | + "width": ximage.width, |
| 501 | + "height": ximage.height, |
| 502 | + } |
| 503 | + |
| 504 | + raw_data = cast( |
| 505 | + ximage.pixels, POINTER(c_ulong * monitor["height"] * monitor["width"]) |
| 506 | + ) |
| 507 | + raw = bytearray(raw_data.contents) |
| 508 | + |
| 509 | + data = bytearray(monitor["height"] * monitor["width"] * 4) |
| 510 | + data[3::4] = raw[3::8] |
| 511 | + data[2::4] = raw[2::8] |
| 512 | + data[1::4] = raw[1::8] |
| 513 | + data[::4] = raw[::8] |
| 514 | + |
| 515 | + return self.cls_image(data, monitor) |
0 commit comments