From 30f60cb3fd0df442591de73d43bfef33e63357ec Mon Sep 17 00:00:00 2001 From: Robert Grimm Date: Sun, 26 May 2024 21:18:04 -0400 Subject: [PATCH] add tests for dark theme and mode --- prettypretty/darkmode.py | 125 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 prettypretty/darkmode.py diff --git a/prettypretty/darkmode.py b/prettypretty/darkmode.py new file mode 100644 index 0000000..d26c557 --- /dev/null +++ b/prettypretty/darkmode.py @@ -0,0 +1,125 @@ +import os +import sys + +from .color.conversion import get_converter +from .color.theme import current_theme + + +def is_dark_theme() -> bool: + """ + Determine whether the current terminal theme is a dark theme. This function + converts the default foreground and background colors of the current theme + to the XYZ color space and then compares their Y, that is, luminance, + components. If foreground has has higher luminance than the background, the + terminal is using a dark therme. + """ + theme = current_theme() + + # Convert to XYZ + foreground_xyz = get_converter(theme.text.tag, 'xyz')(*theme.text.coordinates) + background_xyz = get_converter(theme.background.tag, 'xyz')( + *theme.background.coordinates + ) + + # Compare luminance components + return foreground_xyz[1] > background_xyz[1] + + +def is_dark_mode() -> None | bool: + """ + Determine whether the operating system is in dark mode. + + Returns: + ``True`` for dark mode, ``False`` for light mode, and ``None`` if the + mode could not be determined. + + The implementation builds on answers to [this StackOverflow + question](https://stackoverflow.com/questions/65294987/detect-os-dark-mode-in-python) + and [the darkdetect package](https://github.com/albertosottile/darkdetect). + The latter seems both over- and under-engineered. In contrast, this module + provides the one interesting bit, whether the system is in dark mode, if + available and nothing else. + """ + try: + if sys.platform == "darwin": + return _is_darkmode_macos() + elif sys.platform == "linux": + return _is_darkmode_linux() + elif os.name == 'nt': + return _is_darkmode_windows() + else: + return None + except: + return None + + +def _is_darkmode_windows() -> bool: + import winreg + + with winreg.OpenKey( # type: ignore[attr-defined] + winreg.HKEY_CURRENT_USER, # type: ignore[attr-defined] + "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + ) as key: # type: ignore + return not winreg.QueryValueEx( # type: ignore[attr-defined] + key, "AppsUseLightTheme" + )[0] + + +def _is_darkmode_macos() -> bool: + import subprocess + + # Use DEVNULL so that output of command isn't shown + return not subprocess.run( + ["defaults", "read", "-g", "AppleInterfaceStyle"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ).returncode + + +def _is_darkmode_linux() -> None | bool: + import subprocess + + try: + result = subprocess.run( [ + "dbus-send", + "--session", + "--print-reply=literal", + "--dest=org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Settings.Read", + "string:org.freedesktop.appearance", + "string:color-scheme" + ], + capture_output=True, + encoding='utf8', + check=True, + ) + + stdout = result.stdout.replace("variant", "").replace("uint32", "").strip() + if stdout in ("0", "1", "2"): + # 0 stands for default, 1 for prefers-dark, and 2 for prefers-light. + # Ubuntu returns 0 for light mode and 1 for dark mode. + return stdout == "1" + except: + pass + + try: + result = subprocess.run( + ["gsettings", "get", "org.gnome.desktop.interface", "color-scheme"], + capture_output=True, + encoding="utf8", + check=True, + ) + stdout = result.stdout.strip() + except: + stdout = "" + + if not stdout: + result = subprocess.run( + ["gsettings", "get", "org.gnome.desktop.interface", "gtk-theme"], + capture_output=True, + encoding="utf8", + check=True, + ) + stdout = result.stdout.strip() + return stdout.strip("'").endswith("-dark")