From 722be51420f57ac31093cd821d55e1c56df444c3 Mon Sep 17 00:00:00 2001 From: Kalmat Date: Sat, 20 Apr 2024 01:39:45 +0200 Subject: [PATCH] ALL: Added saveSetup() and restoreSetup(). Fixed / Improved watchdog (especially in Linux). Fixed / improved setPosition() method LINUX: Added ewmhlib as separate module. Fixed watchdog (freezing randomly invoking screen_resources and get_output_info), fixed workarea crash (some apps/environments do not set it), improved to work almost fine in Manjaro/KDE, avoid crashing in Wayland for "fake" :1 display (though module won't likely work) WIN32: Fixed dev.StateFlags returning weird values for multi-monitor. Fixed GetAwarenessFromDpiAwarenessContext not supported on Windows Server MACOS: Replaced display-manager-lib by other alternatives which seem to work in several macOS versions. Added setScale() method (using a workaround). Added wakeup feature to turnOn() method --- src/pymonctl/_pymonctl_macos.py | 115 ++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/src/pymonctl/_pymonctl_macos.py b/src/pymonctl/_pymonctl_macos.py index 05b3478..75195b3 100644 --- a/src/pymonctl/_pymonctl_macos.py +++ b/src/pymonctl/_pymonctl_macos.py @@ -436,10 +436,10 @@ def brightness(self) -> Optional[int]: if self._iokit is None: self._iokit, self._cf, self._ioservice = _loadIOKit(self.handle) if self._iokit is not None and self._cf is not None and self._ioservice is not None: - var_name = CF.CFStringCreateWithCString(None, b"brightness", 0) + kDisplayBrightnessKey = CF.CFStringCreateWithCString(None, b"brightness", 0) value = ctypes.c_float() try: - ret = self._iokit.IODisplayGetFloatParameter(self._ioservice, 0, var_name, ctypes.byref(value)) + ret = self._iokit.IODisplayGetFloatParameter(self._ioservice, 0, kDisplayBrightnessKey, ctypes.byref(value)) except: ret = 1 if ret == 0: @@ -488,10 +488,10 @@ def setBrightness(self, brightness: Optional[int]): if self._iokit is None: self._iokit, self._cf, self._ioservice = _loadIOKit(self.handle) if self._iokit is not None and self._cf is not None and self._ioservice is not None: - var_name = CF.CFStringCreateWithCString(None, b"brightness", 0) + kDisplayBrightnessKey = CF.CFStringCreateWithCString(None, b"brightness", 0) value = ctypes.c_float(brightness / 100) try: - ret = self._iokit.IODisplaySetFloatParameter(self._ioservice, 0, var_name, value) + ret = self._iokit.IODisplaySetFloatParameter(self._ioservice, 0, kDisplayBrightnessKey, value) except: ret = 1 if ret != 0: @@ -706,7 +706,6 @@ def _NSgetAllMonitorsDict(): # https://stackoverflow.com/questions/30816183/iokit-ioservicegetmatchingservices-broken-under-python3 # https://stackoverflow.com/questions/65150131/iodisplayconnect-is-gone-in-big-sur-of-apple-silicon-what-is-the-replacement # https://alexdelorenzo.dev/programming/2018/08/16/reverse_engineering_private_apple_apis -# https://github.com/nriley/brightness/blob/master/brightness.c def _loadDisplayServices(): # Display Services Framework can be used in modern systems. It takes A LOT to load @@ -733,57 +732,71 @@ def _loadCoreDisplay(): def _loadIOKit(displayID = Quartz.CGMainDisplayID()): # In other systems, we can try to use IOKit + # https://github.com/nriley/brightness/blob/master/brightness.c # https://stackoverflow.com/questions/22841741/calling-functions-with-arguments-from-corefoundation-using-ctypes - try: - class _CFString(ctypes.Structure): - pass - - CFStringRef = ctypes.POINTER(_CFString) - - lib = ctypes.util.find_library("CoreFoundation") - if not lib: - return None, None, None - CF: ctypes.CDLL = ctypes.cdll.LoadLibrary(lib) - CF.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32] - CF.CFStringCreateWithCString.restype = CFStringRef - - lib = ctypes.util.find_library('IOKit') - if not lib: - return None, None, None - iokit: ctypes.CDLL = ctypes.cdll.LoadLibrary(lib) - iokit.IODisplayGetFloatParameter.argtypes = [ctypes.c_void_p, ctypes.c_uint, CFStringRef, ctypes.POINTER(ctypes.c_float)] - iokit.IODisplayGetFloatParameter.restype = ctypes.c_int - iokit.IODisplaySetFloatParameter.argtypes = [ctypes.c_void_p, ctypes.c_uint, CFStringRef, ctypes.c_float] - iokit.IODisplaySetFloatParameter.restype = ctypes.c_int - iokit.IOServiceRequestProbe.argtypes = [ctypes.c_void_p, ctypes.c_uint] - iokit.IOServiceRequestProbe.restype = ctypes.c_int - - try: - service: int = Quartz.CGDisplayIOServicePort(displayID) - except: - service = 0 - - if not service: - - iokit.IOServiceMatching.restype = ctypes.c_void_p - iokit.IOServiceGetMatchingService.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - iokit.IOServiceGetMatchingService.restype = ctypes.c_void_p - - try: - kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault") + class _CFString(ctypes.Structure): + pass - service = iokit.IOServiceGetMatchingService( - kIOMasterPortDefault, - iokit.IOServiceMatching(b'IODisplayConnect') - ) - except: - service = 0 + CFStringRef = ctypes.POINTER(_CFString) + + lib = ctypes.util.find_library("CoreFoundation") + if not lib: + return None, None, None + CF: ctypes.CDLL = ctypes.cdll.LoadLibrary(lib) + CF.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32] + CF.CFStringCreateWithCString.restype = CFStringRef + CF.CFDictionaryGetValue.argtypes = [ctypes.c_void_p, CFStringRef] + CF.CFDictionaryGetValue.restype = ctypes.c_void_p + + lib = ctypes.util.find_library('IOKit') + if not lib: + return None, None, None + iokit: ctypes.CDLL = ctypes.cdll.LoadLibrary(lib) + iokit.IODisplayGetFloatParameter.argtypes = [ctypes.c_void_p, ctypes.c_uint, CFStringRef, ctypes.POINTER(ctypes.c_float)] + iokit.IODisplayGetFloatParameter.restype = ctypes.c_int + iokit.IODisplaySetFloatParameter.argtypes = [ctypes.c_void_p, ctypes.c_uint, CFStringRef, ctypes.c_float] + iokit.IODisplaySetFloatParameter.restype = ctypes.c_int + iokit.IOServiceRequestProbe.argtypes = [ctypes.c_void_p, ctypes.c_uint] + iokit.IOServiceRequestProbe.restype = ctypes.c_int + # iokit.IODisplayCreateInfoDictionary.argtypes = [ctypes.c_void_p, ctypes.c_uint] + # iokit.IODisplayCreateInfoDictionary.restype = ctypes.Structure - if service: - return iokit, CF, service + try: + # CGDisplayIOServicePort is deprecated as of 10.9 + service: int = Quartz.CGDisplayIOServicePort(displayID) except: - pass + service = 0 + + # Check if this works in an actual macOS (preferably in several versions) + if not service: + + iokit.IOServiceMatching.restype = ctypes.c_void_p + iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] + iokit.IOServiceGetMatchingServices.restype = ctypes.c_void_p + iokit.IODisplayCreateInfoDictionary.restype = ctypes.c_void_p + + kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault") + iterator = ctypes.c_void_p() + ret = iokit.IOServiceGetMatchingServices( + kIOMasterPortDefault, + iokit.IOServiceMatching(b'IODisplayConnect'), + ctypes.byref(iterator) + ) + + kIODisplayNoProductName = 0x00000400 + kDisplaySerialNumber = CF.CFStringCreateWithCString(None, b"DisplaySerialNumber", 0) + while True: + service = iokit.IOIteratorNext(iterator) + if not service: + break + info = iokit.IODisplayCreateInfoDictionary(service, kIODisplayNoProductName) + serialNumber = CF.CFDictionaryGetValue(info, kDisplaySerialNumber) + if serialNumber == Quartz.CGDisplaySerialNumber(displayID): + break + + if service: + return iokit, CF, service return None, None, None