├── pyxcursor ├── __init__.py ├── timing.py └── pyxcursor.py ├── README.md └── LICENSE /pyxcursor/__init__.py: -------------------------------------------------------------------------------- 1 | from pyxcursor.pyxcursor import Xcursor 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyXCursor 2 | get the image of cursor/mouse-pointer for an arbitrary application window in Linux - in Python using ctypes 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 zorvios 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyxcursor/timing.py: -------------------------------------------------------------------------------- 1 | """ 2 | This Test : 3 | ------------------------------------------------------ 4 | 'init' in 0.011178 secs 5 | 'getCursorImageArray' in 0.000870 secs 6 | 'getCursorImageArrayFast' in 0.000200 secs 7 | 'saveImage' in 0.010603 secs 8 | ------------------------------------------------------ 9 | Test Inside Xcursor : 10 | ------------------------------------------------------ 11 | Import Libs : 0.152991 secs 12 | Finished '__init__' in 0.012024 secs 13 | --- 'getCursorImageArray' --- 14 | Finished 'getCursorImageData' in 0.000074 secs 15 | Finished 'argbdata_to_pixdata' in 0.000734 secs 16 | --- Finished in 0.000886 secs --- 17 | Finished 'saveImage' in 0.011071 secs 18 | ------------------------------------------------------ 19 | """ 20 | 21 | import time 22 | def timer(func): 23 | def wrapper_timer(*args, **kwargs): 24 | #start_time = time.perf_counter() 25 | start_time = time.time() 26 | value = func(*args, **kwargs) 27 | #run_time = time.perf_counter() - start_time 28 | run_time = time.time() - start_time 29 | print(f"{func.__name__!r} in {' '*(30-len(func.__name__))}{run_time:.6f} secs") 30 | return value 31 | return wrapper_timer 32 | 33 | from pyxcursor import Xcursor 34 | 35 | @timer 36 | def init(): 37 | return Xcursor() 38 | 39 | cursor = init() 40 | 41 | @timer 42 | def getCursorImageArray(): 43 | return cursor.getCursorImageArray() 44 | 45 | @timer 46 | def getCursorImageArrayFast(): 47 | return cursor.getCursorImageArrayFast() 48 | 49 | @timer 50 | def saveImage(imgarray,text): 51 | cursor.saveImage(imgarray,text) 52 | 53 | 54 | imgarray = getCursorImageArray() 55 | imgarray = getCursorImageArrayFast() 56 | saveImage(imgarray,'cursor_image.png') 57 | -------------------------------------------------------------------------------- /pyxcursor/pyxcursor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ctypes 3 | import ctypes.util 4 | import numpy as np 5 | 6 | # A helper function to convert data from Xlib to byte array. 7 | import struct, array 8 | 9 | # Define ctypes version of XFixesCursorImage structure. 10 | PIXEL_DATA_PTR = ctypes.POINTER(ctypes.c_ulong) 11 | Atom = ctypes.c_ulong 12 | 13 | class XFixesCursorImage (ctypes.Structure): 14 | """ 15 | See /usr/include/X11/extensions/Xfixes.h 16 | 17 | typedef struct { 18 | short x, y; 19 | unsigned short width, height; 20 | unsigned short xhot, yhot; 21 | unsigned long cursor_serial; 22 | unsigned long *pixels; 23 | if XFIXES_MAJOR >= 2 24 | Atom atom; /* Version >= 2 only */ 25 | const char *name; /* Version >= 2 only */ 26 | endif 27 | } XFixesCursorImage; 28 | """ 29 | _fields_ = [('x', ctypes.c_short), 30 | ('y', ctypes.c_short), 31 | ('width', ctypes.c_ushort), 32 | ('height', ctypes.c_ushort), 33 | ('xhot', ctypes.c_ushort), 34 | ('yhot', ctypes.c_ushort), 35 | ('cursor_serial', ctypes.c_ulong), 36 | ('pixels', PIXEL_DATA_PTR), 37 | ('atom', Atom), 38 | ('name', ctypes.c_char_p)] 39 | 40 | class Display(ctypes.Structure): 41 | pass 42 | 43 | 44 | class Xcursor: 45 | display = None 46 | def __init__(self, display=None): 47 | if not display: 48 | try: 49 | display = os.environ["DISPLAY"].encode("utf-8") 50 | except KeyError: 51 | raise Exception("$DISPLAY not set.") 52 | 53 | #XFixeslib = ctypes.CDLL('libXfixes.so') 54 | XFixes = ctypes.util.find_library("Xfixes") 55 | if not XFixes: 56 | raise Exception("No XFixes library found.") 57 | self.XFixeslib = ctypes.cdll.LoadLibrary(XFixes) 58 | 59 | #xlib = ctypes.CDLL('libX11.so.6') 60 | x11 = ctypes.util.find_library("X11") 61 | if not x11: 62 | raise Exception("No X11 library found.") 63 | self.xlib = ctypes.cdll.LoadLibrary(x11) 64 | 65 | # Define ctypes' version of XFixesGetCursorImage function 66 | XFixesGetCursorImage = self.XFixeslib.XFixesGetCursorImage 67 | XFixesGetCursorImage.restype = ctypes.POINTER(XFixesCursorImage) 68 | XFixesGetCursorImage.argtypes = [ctypes.POINTER(Display)] 69 | self.XFixesGetCursorImage = XFixesGetCursorImage 70 | 71 | XOpenDisplay = self.xlib.XOpenDisplay 72 | XOpenDisplay.restype = ctypes.POINTER(Display) 73 | XOpenDisplay.argtypes = [ctypes.c_char_p] 74 | 75 | if not self.display: 76 | self.display = self.xlib.XOpenDisplay(display) # (display) or (None) 77 | 78 | def argbdata_to_pixdata(self, data, len): 79 | if data == None or len < 1: return None 80 | 81 | # Create byte array 82 | b = array.array('b', b'\x00'*4*len) 83 | 84 | offset,i = 0,0 85 | while i < len: 86 | argb = data[i] & 0xffffffff 87 | rgba = (argb << 8) | (argb >> 24) 88 | b1 = (rgba >> 24) & 0xff 89 | b2 = (rgba >> 16) & 0xff 90 | b3 = (rgba >> 8) & 0xff 91 | b4 = rgba & 0xff 92 | 93 | struct.pack_into("=BBBB", b, offset, b1, b2, b3, b4) 94 | offset = offset + 4 95 | i = i + 1 96 | 97 | return b 98 | 99 | def getCursorImageData(self): 100 | # Call the function. Read data of cursor/mouse-pointer. 101 | cursor_data = self.XFixesGetCursorImage(self.display) 102 | 103 | if not (cursor_data and cursor_data[0]): 104 | raise Exception("Cannot read XFixesGetCursorImage()") 105 | 106 | # Note: cursor_data is a pointer, take cursor_data[0] 107 | return cursor_data[0] 108 | 109 | def getCursorImageArray(self): 110 | data = self.getCursorImageData() 111 | # x, y = data.x, data.y 112 | height,width = data.height, data.width 113 | 114 | bytearr = self.argbdata_to_pixdata(data.pixels, height*width) 115 | 116 | imgarray = np.array(bytearr, dtype=np.uint8) 117 | imgarray = imgarray.reshape(height,width,4) 118 | del bytearr 119 | 120 | return imgarray 121 | 122 | def getCursorImageArrayFast(self): 123 | data = self.getCursorImageData() 124 | # x, y = data.x, data.y 125 | height,width = data.height, data.width 126 | 127 | bytearr = ctypes.cast(data.pixels, ctypes.POINTER(ctypes.c_ulong *height * width))[0] 128 | imgarray = np.array(bytearray(bytearr)) 129 | imgarray = imgarray.reshape(height,width,8)[:, :, (0, 1, 2, 3)] 130 | del bytearr 131 | 132 | return imgarray 133 | 134 | def saveImage(self,imgarray,text): 135 | from PIL import Image 136 | img = Image.fromarray(imgarray) 137 | img.save(text) 138 | 139 | if __name__ == "__main__": 140 | cursor = Xcursor() 141 | imgarray = cursor.getCursorImageArrayFast() 142 | cursor.saveImage(imgarray,'cursor_image.png') 143 | --------------------------------------------------------------------------------