albertosottile / darkdetect

Detect OS Dark Mode from Python
Other
171 stars 18 forks source link

Listener for dark mode switch? #14

Closed ribeaud closed 1 year ago

ribeaud commented 2 years ago

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

albertosottile commented 2 years ago

i actually have no plans in this regard. This could be a nice enhancement indeed, however I foresee a lot of work and I am not even sure this is possible in pure Python and for all the platform.

Personally, I could invest some time for macOS, but I am not sure when I will have enough capacity for doing this.

Nevertheless, PRs in this direction are more than welcome.

albertosottile commented 2 years ago

Just FYI, this is something that is easily doable in PySide2 (and I guess pyqt5):

import darkdetect

from PySide2 import QtCore, QtWidgets

class LabelHandler():
    def __init__(self):
        self.label = QtWidgets.QLabel(f'Mode: {darkdetect.theme()}')

    @QtCore.Slot(str)
    def mode_change(self):
        self.label.setText(f'Mode: {darkdetect.theme()}')

if __name__ == "__main__":
    app = QtWidgets.QApplication([])

    win = QtWidgets.QWidget()
    layout = QtWidgets.QVBoxLayout()

    lh = LabelHandler()
    layout.addWidget(lh.label)

    app.paletteChanged.connect(lh.mode_change)

    win.setLayout(layout)
    win.show()
    app.exec_()

Darkdetect was mostly created to overcome the limitations of GUI libraries, and detecting this change on the fly seems to be supported by (at least some) of them. I would encourage you to check whether this listener detection is supported in the library of your choice.

TransparentLC commented 2 years ago

On Windows the dark mode setting is stored in registry key HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize DWORD AppsUseLightTheme. I found a WIN32 API function named RegNotifyChangeKeyValue which makes notifies about changes to a specified registry key. Perhaps a native pyd extension is required but I'm not familiar with WIN32 APIs.

TransparentLC commented 2 years ago

I forked this repo and commited the native extension's code to add a listener function on Windows. Pass a callback function and it will be called with string "Dark" or "Light" when the OS switches the dark mode setting. On Linux and macOS it simply raises a NotImplementedError.

The listener is an infinite loop so a separated thread is required. However the thread is unstoppable so I think the API design and the implementation can be further improved.

Usage example:

import darkdetect, threading
# def listener(callback: typing.Callable[[str], None]) -> None: ...
t = threading.Thread(target=darkdetect.listener, args=(print,))
t.daemon = True
t.start()

Try it out by cloning it and install with command python setup.py build_ext --inplace and python setup.py install.

TransparentLC commented 2 years ago

https://user-images.githubusercontent.com/47057319/169501001-51aac359-e925-4605-8f0c-9db35fefbf5c.mp4

I guess we can spawn a subprocess gsettings monitor org.gnome.desktop.interface gtk-theme and read its output to detect dark mode switch on Linux. I'll try to add it to my fork (and the pull request).

TransparentLC commented 2 years ago

@albertosottile I also found a solution for macOS: Golang: Detect dark mode change in OSX

albertosottile commented 2 years ago

@albertosottile I also found a solution for macOS: Golang: Detect dark mode change in OSX

The detection method based on defaults read -g AppleInterfaceStyle is less reliable than the one implemented in darkdetect ([[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]).

In principle, Foundation allows monitoring values from NSUserDefaults with callbacks, but I was not able to get this working via ctypes when I tried.

For the moment, I would merge your PR as it is. We can then continue investigating if callback can be implemented on macOS with the NSUserDefaults-based detection method.

TransparentLC commented 2 years ago

Just Googled for any available solutions and I don't have any device running macOS. So I'm afraid I can't provide further help for implementation for macOS.

albertosottile commented 2 years ago

The listeners for Windows and Linux were released in 0.6.0. I will keep this issue open for macOS.

albertosottile commented 1 year ago

I invested a lot of time in trying to implement a listener for macOS and failed miserably. Since I want to add more information on what I tried (in view of helping further people that might venture through the same path) I am now closing this issue and putting the macOS endeavor in #25.

@TransparentLC thanks again for having implemented the listeners for Windows and Linux!

ribeaud commented 1 year ago

Many thanks to all!