diff --git a/syncplay/vendor/darkdetect/__init__.py b/syncplay/vendor/darkdetect/__init__.py index fc68343..a797685 100644 --- a/syncplay/vendor/darkdetect/__init__.py +++ b/syncplay/vendor/darkdetect/__init__.py @@ -4,19 +4,31 @@ # Distributed under the terms of the 3-clause BSD License. #----------------------------------------------------------------------------- -__version__ = '0.5.2' +__version__ = '0.7.1' import sys import platform -if sys.platform == "darwin": - from distutils.version import LooseVersion as V - if V(platform.mac_ver()[0]) < V("10.14"): - from ._dummy import * +def macos_supported_version(): + sysver = platform.mac_ver()[0] #typically 10.14.2 or 12.3 + major = int(sysver.split('.')[0]) + if major < 10: + return False + elif major >= 11: + return True else: + minor = int(sysver.split('.')[1]) + if minor < 14: + return False + else: + return True + +if sys.platform == "darwin": + if macos_supported_version(): from ._mac_detect import * - del V -elif sys.platform == "win32" and int(platform.release()) >= 10: + else: + from ._dummy import * +elif sys.platform == "win32" and platform.release().isdigit() and int(platform.release()) >= 10: # Checks if running Windows 10 version 10.0.14393 (Anniversary Update) OR HIGHER. The getwindowsversion method returns a tuple. # The third item is the build number that we can use to check if the user has a new enough version of Windows. winver = int(platform.version().split('.')[2]) @@ -29,4 +41,4 @@ elif sys.platform == "linux": else: from ._dummy import * -del sys, platform \ No newline at end of file +del sys, platform diff --git a/syncplay/vendor/darkdetect/__main__.py b/syncplay/vendor/darkdetect/__main__.py new file mode 100644 index 0000000..1cb260b --- /dev/null +++ b/syncplay/vendor/darkdetect/__main__.py @@ -0,0 +1,9 @@ +#----------------------------------------------------------------------------- +# Copyright (C) 2019 Alberto Sottile +# +# Distributed under the terms of the 3-clause BSD License. +#----------------------------------------------------------------------------- + +import darkdetect + +print('Current theme: {}'.format(darkdetect.theme())) diff --git a/syncplay/vendor/darkdetect/_dummy.py b/syncplay/vendor/darkdetect/_dummy.py index 1e99668..1e82117 100644 --- a/syncplay/vendor/darkdetect/_dummy.py +++ b/syncplay/vendor/darkdetect/_dummy.py @@ -4,6 +4,8 @@ # Distributed under the terms of the 3-clause BSD License. #----------------------------------------------------------------------------- +import typing + def theme(): return None @@ -12,3 +14,6 @@ def isDark(): def isLight(): return None + +def listener(callback: typing.Callable[[str], None]) -> None: + raise NotImplementedError() diff --git a/syncplay/vendor/darkdetect/_linux_detect.py b/syncplay/vendor/darkdetect/_linux_detect.py index 3110ae8..094a2fc 100644 --- a/syncplay/vendor/darkdetect/_linux_detect.py +++ b/syncplay/vendor/darkdetect/_linux_detect.py @@ -6,7 +6,6 @@ import subprocess - def theme(): # Here we just triage to GTK settings for now try: @@ -18,7 +17,7 @@ def theme(): return 'Light' # we have a string, now remove start and end quote theme = stdout.lower().strip()[1:-1] - if theme.endswith('-dark'): + if '-dark' in theme.lower(): return 'Dark' else: return 'Light' @@ -28,3 +27,13 @@ def isDark(): def isLight(): return theme() == 'Light' + +# def listener(callback: typing.Callable[[str], None]) -> None: +def listener(callback): + with subprocess.Popen( + ('gsettings', 'monitor', 'org.gnome.desktop.interface', 'gtk-theme'), + stdout=subprocess.PIPE, + universal_newlines=True, + ) as p: + for line in p.stdout: + callback('Dark' if '-dark' in line.strip().removeprefix("gtk-theme: '").removesuffix("'").lower() else 'Light') diff --git a/syncplay/vendor/darkdetect/_mac_detect.py b/syncplay/vendor/darkdetect/_mac_detect.py index fbe6a35..24fc4a4 100644 --- a/syncplay/vendor/darkdetect/_mac_detect.py +++ b/syncplay/vendor/darkdetect/_mac_detect.py @@ -68,3 +68,7 @@ def isDark(): def isLight(): return theme() == 'Light' + +#def listener(callback: typing.Callable[[str], None]) -> None: +def listener(callback): + raise NotImplementedError() diff --git a/syncplay/vendor/darkdetect/_windows_detect.py b/syncplay/vendor/darkdetect/_windows_detect.py index a976885..2363f18 100644 --- a/syncplay/vendor/darkdetect/_windows_detect.py +++ b/syncplay/vendor/darkdetect/_windows_detect.py @@ -1,5 +1,60 @@ from winreg import HKEY_CURRENT_USER as hkey, QueryValueEx as getSubkeyValue, OpenKey as getKey +import ctypes +import ctypes.wintypes + +advapi32 = ctypes.windll.advapi32 + +# LSTATUS RegOpenKeyExA( +# HKEY hKey, +# LPCSTR lpSubKey, +# DWORD ulOptions, +# REGSAM samDesired, +# PHKEY phkResult +# ); +advapi32.RegOpenKeyExA.argtypes = ( + ctypes.wintypes.HKEY, + ctypes.wintypes.LPCSTR, + ctypes.wintypes.DWORD, + ctypes.wintypes.DWORD, + ctypes.POINTER(ctypes.wintypes.HKEY), +) +advapi32.RegOpenKeyExA.restype = ctypes.wintypes.LONG + +# LSTATUS RegQueryValueExA( +# HKEY hKey, +# LPCSTR lpValueName, +# LPDWORD lpReserved, +# LPDWORD lpType, +# LPBYTE lpData, +# LPDWORD lpcbData +# ); +advapi32.RegQueryValueExA.argtypes = ( + ctypes.wintypes.HKEY, + ctypes.wintypes.LPCSTR, + ctypes.wintypes.LPDWORD, + ctypes.wintypes.LPDWORD, + ctypes.wintypes.LPBYTE, + ctypes.wintypes.LPDWORD, +) +advapi32.RegQueryValueExA.restype = ctypes.wintypes.LONG + +# LSTATUS RegNotifyChangeKeyValue( +# HKEY hKey, +# WINBOOL bWatchSubtree, +# DWORD dwNotifyFilter, +# HANDLE hEvent, +# WINBOOL fAsynchronous +# ); +advapi32.RegNotifyChangeKeyValue.argtypes = ( + ctypes.wintypes.HKEY, + ctypes.wintypes.BOOL, + ctypes.wintypes.DWORD, + ctypes.wintypes.HANDLE, + ctypes.wintypes.BOOL, +) +advapi32.RegNotifyChangeKeyValue.restype = ctypes.wintypes.LONG + def theme(): """ Uses the Windows Registry to detect if the user is using Dark Mode """ # Registry will return 0 if Windows is in Dark Mode and 1 if Windows is in Light Mode. This dictionary converts that output into the text that the program is expecting. @@ -21,4 +76,47 @@ def isDark(): def isLight(): if theme() is not None: - return theme() == 'Light' \ No newline at end of file + return theme() == 'Light' + +#def listener(callback: typing.Callable[[str], None]) -> None: +def listener(callback): + hKey = ctypes.wintypes.HKEY() + advapi32.RegOpenKeyExA( + ctypes.wintypes.HKEY(0x80000001), # HKEY_CURRENT_USER + ctypes.wintypes.LPCSTR(b'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize'), + ctypes.wintypes.DWORD(), + ctypes.wintypes.DWORD(0x00020019), # KEY_READ + ctypes.byref(hKey), + ) + + dwSize = ctypes.wintypes.DWORD(ctypes.sizeof(ctypes.wintypes.DWORD)) + queryValueLast = ctypes.wintypes.DWORD() + queryValue = ctypes.wintypes.DWORD() + advapi32.RegQueryValueExA( + hKey, + ctypes.wintypes.LPCSTR(b'AppsUseLightTheme'), + ctypes.wintypes.LPDWORD(), + ctypes.wintypes.LPDWORD(), + ctypes.cast(ctypes.byref(queryValueLast), ctypes.wintypes.LPBYTE), + ctypes.byref(dwSize), + ) + + while True: + advapi32.RegNotifyChangeKeyValue( + hKey, + ctypes.wintypes.BOOL(True), + ctypes.wintypes.DWORD(0x00000004), # REG_NOTIFY_CHANGE_LAST_SET + ctypes.wintypes.HANDLE(None), + ctypes.wintypes.BOOL(False), + ) + advapi32.RegQueryValueExA( + hKey, + ctypes.wintypes.LPCSTR(b'AppsUseLightTheme'), + ctypes.wintypes.LPDWORD(), + ctypes.wintypes.LPDWORD(), + ctypes.cast(ctypes.byref(queryValue), ctypes.wintypes.LPBYTE), + ctypes.byref(dwSize), + ) + if queryValueLast.value != queryValue.value: + queryValueLast.value = queryValue.value + callback('Light' if queryValue.value else 'Dark')