Skip to content

Commit

Permalink
ALL: Added saveSetup() and restoreSetup(). Fixed / Improved watchdog …
Browse files Browse the repository at this point in the history
…(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
  • Loading branch information
Kalmat committed Apr 19, 2024
1 parent b57795e commit 722be51
Showing 1 changed file with 64 additions and 51 deletions.
115 changes: 64 additions & 51 deletions src/pymonctl/_pymonctl_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down

0 comments on commit 722be51

Please sign in to comment.