albertosottile / darkdetect Goto Github PK
View Code? Open in Web Editor NEWDetect OS Dark Mode from Python
License: Other
Detect OS Dark Mode from Python
License: Other
Hi, first of all thanks for sharing the code.
I'm curious, was there a specific reason why you didn't choose this simpler way of detecting dark mode?
# querying for dark mode
try:
get_status = 'defaults read -g AppleInterfaceStyle'
import subprocess
status = subprocess.check_output(
get_status.split(),
stderr = subprocess.STDOUT
).decode()
status = status.replace('\n', '')
except subprocess.CalledProcessError as e:
return False
return True if status.lower() == 'dark' else False
My neofetch
info (excerpt):
WM: Mutter
WM Theme: Pop
Theme: Pop-dark [GTK2/3]
Icons: Pop [GTK2/3]
Terminal: gnome-terminal
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
style_context = Gtk.Window().get_style_context()
vals = [0, 0]
for idx, color in enumerate((
style_context.get_background_color(
Gtk.StateFlags.NORMAL
),
style_context.get_color(
Gtk.StateFlags.NORMAL
)
)):
for channel in ('red', 'green', 'blue', ):
vals[idx] += getattr(color, channel)
print(f'Is dark theme: {vals[0] < vals[1]}')
Since setuptools https://setuptools.pypa.io/en/latest/history.html#v59-6-0 it emits a deprecation warning.
darkdetect/darkdetect/__init__.py
Lines 13 to 14 in 6556925
This means that dependees that turn warnings into error will fail (for example in CI).
if V(platform.mac_ver()[0]) < V("10.14"):
../../miniconda3-intel/envs/napari/lib/python3.9/site-packages/setuptools/_distutils/version.py:53: in __init__
warnings.warn(
E DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
This can be worked around with a warning filter , for example:
[tool.pytest.ini_options]
filterwarnings = [
"ignore:.*distutils Version classes are deprecated::darkdetect",
]
It looks like you may not want dependencies, so I'm not if and how you wish to fix it.
Apparently Windows 10 has dark mode too. I wrote this if you would consider extending darkdetect
functionality to Windows.
import platform
if platform.system() == 'Windows':
import winreg
def is_windows_using_dark_theme():
value = 1 # default to light theme
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize') as key:
value, value_type = winreg.QueryValueEx(key, 'AppsUseLightTheme')
except OSError as e:
pass
return value > 0
If the theme is changed from light to dark while the app is running, the app will stay in light mode while the rest of the system changes to dark mode. If you watch for theme changes, you can adapt your Qt app to dark mode just like a native app.
Hi @albertosottile thanks for working on this :)
I had a question:
Is this ifelse code https://github.com/albertosottile/darkdetect/blob/master/darkdetect/__init__.py#L19 working as expected?
Will it not work for other versions of windows besides 10 ?
thanks!
Current Windows implementation doesn't allow breaking out of the infinite while loop
This is related to:
#11
I reported crashes on Big Sur, but it seems my issue wasn't related to Big Sur per se, but to architecture (arm64, Apple Silicon M1). In fact the version detection added in 0.4 doesn't seem to be needed in my environment (Big Sur 11.5.2, python 3.9.6/7)
I've narrowed down the fault to the mismatch in args between:
darkdetect/darkdetect/_mac_detect.py
Line 27 in e6d1db5
darkdetect/darkdetect/_mac_detect.py
Lines 52 to 53 in e6d1db5
As a simple test, add at line 41: (after C is defined)
NSString = C('NSString')
objc.objc_msgSend(NSString, n("stringWithUTF8String:"), _utf8("reason"))
with: objc.objc_msgSend.argtypes = [void_p, void_p]
this crashes.
with: objc.objc_msgSend.argtypes = [void_p, void_p, void_p]
this runs.
As such, I was able to patch this locally by changing the number of argtypes and args for all msg
. I can do a PR, but it will for sure need some extra testing/review.
Edit: I have a more elegant fix, where just the .argtypes
is changed just for the 3 arg msg
lines.
Would a PR containing a listener class be acceptable? This would not have to remove old functionality, it could be purely additive.
subprocess.POpen
. If a user were to force quit (for example a SIGQUIT
on linux) the process, or a native component of a GUI library were to segfault, python may not clean up the child process it was listening to. Proving an API that promises to do so would be greatly appreciated. For example, a user could add a segfault handler to kill child subprocesses before os._exit(1)
. Right now I'm using a somewhat reimplemented version of darkdetect
due to the lack of this functionality.NotImpelementedError()
s are acceptable.class BaseListener:
def __init__(self, callback):
self.set_callback(callback)
def set_callback(self, callback):
self._callback = callback
def listen(self):
raise NotImpelementedError()
def stop(self):
raise NotImpelementedError()
In _linux_detect.py
:
class Listener(BaseListener):
def __init__(self, callback):
super().__init__(callback)
self._proc = None
def listen(self):
self._proc = subprocess.Popen(
('gsettings', 'monitor', 'org.gnome.desktop.interface', 'gtk-theme'),
stdout=subprocess.PIPE,
universal_newlines=True,
)
with self._proc:
for line in p.stdout:
self._callback('Dark' if '-dark' in line.strip().removeprefix("gtk-theme: '").removesuffix("'").lower() else 'Light')
def stop(self):
if self._proc is not None:
self._proc.kill()
self._proc.wait()
self._proc = None
# For those who want to use the old API
def listener(callback):
Listener(callback).listen()
This would allow users to ensure that cleanup has happened properly, interrupt a listener, or subclass to add / edit functionality. For listeners that subclass, really the important bit is having the subprocess saved into something that users can kill if the need should arise, but this class struct has other benefits as mentioned above.
These are other possible benefits of allowing subclassing:
set_callback
, or have it set via a subclass methodListener
and their own class as part of a mixin design pattern or some other pattern.In the examples above, darkdetect
still only provides an API that users can use, so unlike in #29 we don't need to do threading stuff. We simply provide a listener object for the user to use; and to support the existing API we simply just have the standard listener(callback)
function invoke Listener(callback).listen()
.
If I were to make a PR to add such functionality for each supported OS, would it be considered or rejected?
Right now the macOS listener invokes a subprocess with args (sys.executable, "-c", cmd)
; this assumes that sys.executable
is a python interpreter or at least exposes -c
. That is not necessarily the case when running from something like a pyinstaller build, which bundles the interpreter and other files into a binary executable which does not expose -c
.
This is undocumented, though that is fixed by: #32
Right now the listener will silently fail; it would be nice to change this. One possible solution is to check that Path(sys.executable).name.startsWith("python")
and fail with an exception; though then python interpreter hardlinks to different names might not work; this is why the problem is non-trivial. There would need to be some way to determine if sys.executable
is a python interpreter to fail more loudly; or we could maybe wait on the subprocess for a few moments to see if it dies immediately? How exactly would need to be decided.
EDIT:
A hacky fix for pyinstaller specifically is to prefix cmd
with from multiprocessing.resource_tracker import main;
; as pyinstaller supports -c
for any command prefixed by that string.
Heya, I'm looking to use this module for a system tray app, however, the darkdetect
is returning Light even when the taskbar is in 'dark' mode.
It seems my apps are in light mode, just the taskbar is dark, but for a system tray icon, that's all I am after.
There is an identical registry key at the same location: SystemUsesLightTheme
which accurately reflects the taskbar dark mode, however darkdetect is only using AppsUseLightTheme
in it's check/response.
Can we get it to support both on Windows?
Hi. Recently I found that darkdetect listener cannot work under sudo mode. This is how I reproduce:
Environment: macOS 13.5.1 Apple Silicon, Python 3.11
Create a new venv, and install darkdetect by pip install "darkdetect[macos-listener]"
Test sample.py
as follows:
import threading
import darkdetect
def printCurrentTheme(theme):
print(f'System theme is {theme}')
if __name__ == '__main__':
thread = threading.Thread(
target=darkdetect.listener, args=(printCurrentTheme,), daemon=True
)
thread.start()
thread.join()
When running with python sample.py
, the theme is printed correctly when changing system theme in the settings manually.
However, if running with sudo python sample.py
, the callback never called when switching system theme.
There are some cases that I want to launch my application as sudo, and found that darkdetect failed to behave as expected.
Is this a bug related to darkdetect, or related to macos-listener implementation under the hood? Will darkdetect fix this in the future? Thanks.
A listener
function for macOS is currently missing. As stated in the README, calling this function on this platform raises NotImplementedError
.
EDIT:
The listener
function must:
callback
function (Callable[[str], None]
) as its only argumentdarkdetect.theme()
in a loop) as that wastes resourcesThe listener
function should, if possible:
pip install darkdetect[macos-listener]
)Related: #14
I get the following error when using darkdetect from the terminal:
>>> darkdetect.isDark()
2021-06-08 19:36:55.831 Python[4299:381204] +[NSUserDefaults standardUserDefaults]: unrecognized selector sent to class 0x7fff8011ea10
2021-06-08 19:36:55.835 Python[4299:381204] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSUserDefaults standardUserDefaults]: unrecognized selector sent to class 0x7fff8011ea10'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2055287b __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff2028ad92 objc_exception_throw + 48
2 CoreFoundation 0x00007fff205d52e5 __CFExceptionProem + 0
3 CoreFoundation 0x00007fff204ba90b ___forwarding___ + 1448
4 CoreFoundation 0x00007fff204ba2d8 _CF_forwarding_prep_0 + 120
5 libffi.dylib 0x00007fff2d9ba8f5 ffi_call_unix64 + 85
6 ??? 0x00007ffeee3d67e0 0x0 + 140732895422432
)
libc++abi: terminating with uncaught exception of type NSException
zsh: abort python3
This happens when using any method, i.e., isDark
, isLight
, or theme
. For information, I'm running macOS Big Sur 11.4 (20F71).
Hi. I am using your very useful library to know whether dark mode has been switched on on a specific target system.
I was wondering if you plan to implement kind of listener. The problem I am currently having: if the OS switches the dark mode while the application is running, the application does not get notified about it. Only code being executed on-runtime, after the switch, will be able to fetch the newest value of the mode.
I hope, my explanation was clear enough. Otherwise, kindly let me know.
Best regards,
christian
Hi,
I get a false positive on my macOS.
As you see, the theme turn to light (auto), but the script still tell me, that it should be dark.
To clarify, I get 'dark' even when I directly run defaults read -g AppleInterfaceStyle
.
This only happens, when the theme changes while the system is locked.
best regards
The module incorrectly assumes that the output from platform.release() can be translated into int. This will cause an error on import in Windows 8.1.
Traceback (most recent call last):
File "<pyshell>", line 1, in <module>
File "C:\Users\David\AppData\Local\Programs\Python\Python310\lib\site-packages\darkdetect\__init__.py", line 19, in <module>
elif sys.platform == "win32" and int(platform.release()) >= 10:
ValueError: invalid literal for int() with base 10: '8.1'
Cheers.
Duplicate
Hi!
I'm working on the Dark Mode support for my OSS app.
When building with PyInstaller, the following WARNING message appears:
WARNING: Ignoring AppKit.framework/AppKit imported from [project folder]/venv/lib/python3.10/site-packages/darkdetect/_mac_detect.py - only basenames are supported with ctypes imports!
Everything seems to work fine though...
Thanks!
Nice project! Do you have any plans to support GNOME (or KDE) as well?
I'm currently using python 3.8.7
I installed the package via:
pip install darkdetect
And it installs fine, but when I import it I get:
ModuleNotFoundError: No module named 'darkdetect'
I guess it has something to do with your setup file only containing python versions up til 3.7?
Could you please update it for newer versions of python?
Thanks in advance
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.