From b2907e9475a2708abcd07e14413445ec856aec08 Mon Sep 17 00:00:00 2001 From: Vladimir Stoilov Date: Tue, 25 Feb 2025 11:34:21 +0200 Subject: [PATCH] [desktop] Add rust-dark-light as direct dependency --- desktop/tauri/rust-dark-light/.gitignore | 4 + desktop/tauri/rust-dark-light/Cargo.toml | 34 +++++++ desktop/tauri/rust-dark-light/README.md | 39 ++++++++ desktop/tauri/rust-dark-light/build.rs | 5 + .../tauri/rust-dark-light/src/freedesktop.rs | 92 +++++++++++++++++++ desktop/tauri/rust-dark-light/src/lib.rs | 73 +++++++++++++++ .../src/platforms/freedesktop/detect.rs | 47 ++++++++++ .../src/platforms/freedesktop/mod.rs | 88 ++++++++++++++++++ .../src/platforms/freedesktop/notify.rs | 42 +++++++++ .../src/platforms/macos/detect.rs | 56 +++++++++++ .../src/platforms/macos/mod.rs | 2 + .../src/platforms/macos/notify.rs | 23 +++++ .../rust-dark-light/src/platforms/mod.rs | 48 ++++++++++ .../src/platforms/websys/detect.rs | 11 +++ .../src/platforms/websys/mod.rs | 2 + .../src/platforms/websys/notify.rs | 23 +++++ .../src/platforms/windows/detect.rs | 15 +++ .../src/platforms/windows/mod.rs | 2 + .../src/platforms/windows/notify.rs | 23 +++++ .../tauri/rust-dark-light/src/utils/mod.rs | 1 + .../tauri/rust-dark-light/src/utils/rgb.rs | 23 +++++ desktop/tauri/src-tauri/Cargo.lock | 1 - desktop/tauri/src-tauri/Cargo.toml | 2 +- 23 files changed, 654 insertions(+), 2 deletions(-) create mode 100644 desktop/tauri/rust-dark-light/.gitignore create mode 100644 desktop/tauri/rust-dark-light/Cargo.toml create mode 100644 desktop/tauri/rust-dark-light/README.md create mode 100644 desktop/tauri/rust-dark-light/build.rs create mode 100644 desktop/tauri/rust-dark-light/src/freedesktop.rs create mode 100644 desktop/tauri/rust-dark-light/src/lib.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/freedesktop/detect.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/freedesktop/mod.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/freedesktop/notify.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/macos/detect.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/macos/mod.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/macos/notify.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/mod.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/websys/detect.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/websys/mod.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/websys/notify.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/windows/detect.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/windows/mod.rs create mode 100644 desktop/tauri/rust-dark-light/src/platforms/windows/notify.rs create mode 100644 desktop/tauri/rust-dark-light/src/utils/mod.rs create mode 100644 desktop/tauri/rust-dark-light/src/utils/rgb.rs diff --git a/desktop/tauri/rust-dark-light/.gitignore b/desktop/tauri/rust-dark-light/.gitignore new file mode 100644 index 00000000..eb489b96 --- /dev/null +++ b/desktop/tauri/rust-dark-light/.gitignore @@ -0,0 +1,4 @@ +/target +/examples/*/target +Cargo.lock +.vscode diff --git a/desktop/tauri/rust-dark-light/Cargo.toml b/desktop/tauri/rust-dark-light/Cargo.toml new file mode 100644 index 00000000..0ad97cf0 --- /dev/null +++ b/desktop/tauri/rust-dark-light/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "dark-light" +version = "1.1.1" +authors = ["Corey Farwell "] +edition = "2018" +license = "MIT/Apache-2.0" +repository = "https://github.com/frewsxcv/rust-dark-light" +description = "Detect if dark mode or light mode is enabled" +readme = "README.md" +build = "build.rs" + +[dependencies] +futures = "0.3.30" +anyhow = "1.0.79" + +[dev-dependencies] +tokio = { version = "1.23.0", features = ["full"] } + +[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies] +detect-desktop-environment = "1.0.0" +dconf_rs = "0.3" +zbus = "3.0" +rust-ini = "0.20" +ashpd = "0.7.0" +xdg = "2.4.1" + +[target.'cfg(windows)'.dependencies] +winreg = "0.52.0" + +[target.'cfg(target_os = "macos")'.dependencies] +objc = "0.2" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +web-sys = { version = "0.3", features = ["MediaQueryList", "Window"] } diff --git a/desktop/tauri/rust-dark-light/README.md b/desktop/tauri/rust-dark-light/README.md new file mode 100644 index 00000000..490561b9 --- /dev/null +++ b/desktop/tauri/rust-dark-light/README.md @@ -0,0 +1,39 @@ +# rust-dark-light + +Rust crate to detect if dark mode or light mode is enabled. Supports macOS, Windows, Linux, BSDs, and WASM. On Linux and BSDs, first the XDG Desktop Portal dbus API is checked for the `color-scheme` preference, which works in Flatpak sandboxes without needing filesystem access. If that does not work, fallback methods are used for KDE, GNOME, Cinnamon, MATE, XFCE, and Unity. + +[API Documentation](https://docs.rs/dark-light/) + +## Usage + +```rust +fn main() { + let mode = dark_light::detect(); + + match mode { + // Dark mode + dark_light::Mode::Dark => {}, + // Light mode + dark_light::Mode::Light => {}, + // Unspecified + dark_light::Mode::Default => {}, + } +} +``` + +## Example + +``` +cargo run --example detect +``` + +## License + +Licensed under either of + + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + + diff --git a/desktop/tauri/rust-dark-light/build.rs b/desktop/tauri/rust-dark-light/build.rs new file mode 100644 index 00000000..07763e7a --- /dev/null +++ b/desktop/tauri/rust-dark-light/build.rs @@ -0,0 +1,5 @@ +fn main() { + if let Ok("apple") = std::env::var("CARGO_CFG_TARGET_VENDOR").as_deref() { + println!("cargo:rustc-link-lib=framework=AppKit"); + } +} diff --git a/desktop/tauri/rust-dark-light/src/freedesktop.rs b/desktop/tauri/rust-dark-light/src/freedesktop.rs new file mode 100644 index 00000000..39843d75 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/freedesktop.rs @@ -0,0 +1,92 @@ +use detect_desktop_environment::DesktopEnvironment; +use ini::Ini; +use std::path::{Path, PathBuf}; +use zbus::blocking::Connection; + +use crate::Mode; + +const XDG_KDEGLOBALS: &str = "/etc/xdg/kdeglobals"; + +fn get_freedesktop_color_scheme() -> Option { + let conn = Connection::session(); + if conn.is_err() { + return None; + } + let reply = conn.unwrap().call_method( + Some("org.freedesktop.portal.Desktop"), + "/org/freedesktop/portal/desktop", + Some("org.freedesktop.portal.Settings"), + "Read", + &("org.freedesktop.appearance", "color-scheme"), + ); + if let Ok(reply) = &reply { + let theme = reply.body().deserialize::(); + if theme.is_err() { + return None; + } + + match theme.unwrap() { + 1 => Some(Mode::Dark), + 2 => Some(Mode::Light), + _ => None, + } + } else { + None + } +} + +fn detect_gtk(pattern: &str) -> Mode { + match dconf_rs::get_string(pattern) { + Ok(theme) => Mode::from(theme.to_lowercase().contains("dark")), + Err(_) => Mode::Light, + } +} + +fn detect_kde(path: &str) -> Mode { + match Ini::load_from_file(path) { + Ok(cfg) => { + let section = match cfg.section(Some("Colors:Window")) { + Some(section) => section, + None => return Mode::Light, + }; + let values = match section.get("BackgroundNormal") { + Some(string) => string, + None => return Mode::Light, + }; + let rgb = values + .split(',') + .map(|s| s.parse::().unwrap_or(255)) + .collect::>(); + let rgb = if rgb.len() > 2 { + rgb + } else { + vec![255, 255, 255] + }; + let (r, g, b) = (rgb[0], rgb[1], rgb[2]); + Mode::rgb(r, g, b) + } + Err(_) => Mode::Light, + } +} + +pub fn detect() -> Mode { + match get_freedesktop_color_scheme() { + Some(mode) => mode, + // Other desktop environments are still being worked on, fow now, only the following implementations work. + None => match DesktopEnvironment::detect() { + DesktopEnvironment::Kde => { + let path = if Path::new(XDG_KDEGLOBALS).exists() { + PathBuf::from(XDG_KDEGLOBALS) + } else { + dirs::home_dir().unwrap().join(".config/kdeglobals") + }; + detect_kde(path.to_str().unwrap()) + } + DesktopEnvironment::Cinnamon => detect_gtk("/org/cinnamon/desktop/interface/gtk-theme"), + DesktopEnvironment::Gnome => detect_gtk("/org/gnome/desktop/interface/gtk-theme"), + DesktopEnvironment::Mate => detect_gtk("/org/mate/desktop/interface/gtk-theme"), + DesktopEnvironment::Unity => detect_gtk("/org/gnome/desktop/interface/gtk-theme"), + _ => Mode::Default, + }, + } +} diff --git a/desktop/tauri/rust-dark-light/src/lib.rs b/desktop/tauri/rust-dark-light/src/lib.rs new file mode 100644 index 00000000..6c3d47eb --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/lib.rs @@ -0,0 +1,73 @@ +//! Detect if dark mode or light mode is enabled. +//! +//! # Examples +//! +//! ``` +//! let mode = dark_light::detect(); +//! +//! match mode { +//! // Dark mode +//! dark_light::Mode::Dark => {}, +//! // Light mode +//! dark_light::Mode::Light => {}, +//! // Unspecified +//! dark_light::Mode::Default => {}, +//! } +//! ``` + +mod platforms; +use platforms::platform; + +mod utils; +#[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" +))] +use utils::rgb::Rgb; + +/// Enum representing dark mode, light mode, or unspecified. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum Mode { + /// Dark mode + Dark, + /// Light mode + Light, + /// Unspecified + Default, +} + +impl Mode { + #[allow(dead_code)] + fn from_bool(b: bool) -> Self { + if b { + Mode::Dark + } else { + Mode::Light + } + } + + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" + ))] + /// Convert an RGB color to [`Mode`]. The color is converted to grayscale, and if the grayscale value is less than 192, [`Mode::Dark`] is returned. Otherwise, [`Mode::Light`] is returned. + fn from_rgb(rgb: Rgb) -> Self { + let window_background_gray = (rgb.0 * 11 + rgb.1 * 16 + rgb.2 * 5) / 32; + if window_background_gray < 192 { + Self::Dark + } else { + Self::Light + } + } +} + +/// Detect if light mode or dark mode is enabled. If the mode can’t be detected, fall back to [`Mode::Default`]. +pub use platform::detect::detect; +/// Notifies the user if the system theme has been changed. +pub use platform::notify::subscribe; diff --git a/desktop/tauri/rust-dark-light/src/platforms/freedesktop/detect.rs b/desktop/tauri/rust-dark-light/src/platforms/freedesktop/detect.rs new file mode 100644 index 00000000..4901475a --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/freedesktop/detect.rs @@ -0,0 +1,47 @@ +use detect_desktop_environment::DesktopEnvironment; + +use crate::Mode; + +use super::{dconf_detect, gsetting_detect, kde_detect, CINNAMON, GNOME, MATE}; + +pub fn detect() -> Mode { + NonFreeDesktop::detect() +} + +/// Detects the color scheme on a platform. +trait ColorScheme { + fn detect() -> Mode; +} + +/// Represents the FreeDesktop platform. +struct FreeDesktop; + +/// Represents non FreeDesktop platforms. +struct NonFreeDesktop; + +/// Detects the color scheme on FreeDesktop platforms. It makes use of the DBus interface. +impl ColorScheme for FreeDesktop { + fn detect() -> Mode { + todo!() + } +} + +/// Detects the color scheme on non FreeDesktop platforms, having a custom implementation for each desktop environment. +impl ColorScheme for NonFreeDesktop { + fn detect() -> Mode { + match DesktopEnvironment::detect() { + Some(mode) => match mode { + DesktopEnvironment::Kde => match kde_detect() { + Ok(mode) => mode, + Err(_) => Mode::Default, + }, + DesktopEnvironment::Cinnamon => dconf_detect(CINNAMON), + DesktopEnvironment::Gnome => gsetting_detect(), + DesktopEnvironment::Mate => dconf_detect(MATE), + DesktopEnvironment::Unity => dconf_detect(GNOME), + _ => Mode::Default, + }, + None => Mode::Default, + } + } +} diff --git a/desktop/tauri/rust-dark-light/src/platforms/freedesktop/mod.rs b/desktop/tauri/rust-dark-light/src/platforms/freedesktop/mod.rs new file mode 100644 index 00000000..0dd73c75 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/freedesktop/mod.rs @@ -0,0 +1,88 @@ +use std::{process::Command, str::FromStr}; + +use anyhow::Context; +use ini::Ini; + +use crate::{utils::rgb::Rgb, Mode}; + +pub mod detect; +pub mod notify; + +const MATE: &str = "/org/mate/desktop/interface/gtk-theme"; +const GNOME: &str = "/org/gnome/desktop/interface/gtk-theme"; +const CINNAMON: &str = "/org/cinnamon/desktop/interface/gtk-theme"; + +fn dconf_detect(path: &str) -> Mode { + match dconf_rs::get_string(path) { + Ok(theme) => { + println!("dconf output: {}", theme); + if theme.is_empty() { + Mode::Default + } else { + if theme.to_lowercase().contains("dark") { + Mode::Dark + } else { + Mode::Light + } + } + } + Err(_) => Mode::Default, + } +} + +pub fn gsetting_detect() -> Mode { + let mode = match Command::new("gsettings") + .arg("get") + .arg("org.gnome.desktop.interface") + .arg("color-scheme") + .output() + { + Ok(output) => { + if let Ok(scheme) = String::from_utf8(output.stdout) { + if scheme.contains("prefer-dark") { + Mode::Dark + } else if scheme.contains("prefer-light") { + Mode::Dark + } else { + Mode::Default + } + } else { + Mode::Default + } + } + Err(_) => Mode::Default, + }; + + // Fallback to dconf + if mode == Mode::Default { + return dconf_detect(GNOME); + } + + mode +} + +fn kde_detect() -> anyhow::Result { + let xdg = xdg::BaseDirectories::new()?; + let path = xdg + .find_config_file("kdeglobals") + .context("Path not found")?; + let cfg = Ini::load_from_file(path)?; + let properties = cfg + .section(Some("Colors:Window")) + .context("Failed to get section Colors:Window")?; + let background = properties + .get("BackgroundNormal") + .context("Failed to get BackgroundNormal inside Colors:Window")?; + let rgb = Rgb::from_str(background).unwrap(); + Ok(Mode::from_rgb(rgb)) +} + +impl From for Mode { + fn from(value: ashpd::desktop::settings::ColorScheme) -> Self { + match value { + ashpd::desktop::settings::ColorScheme::NoPreference => Mode::Default, + ashpd::desktop::settings::ColorScheme::PreferDark => Mode::Dark, + ashpd::desktop::settings::ColorScheme::PreferLight => Mode::Light, + } + } +} diff --git a/desktop/tauri/rust-dark-light/src/platforms/freedesktop/notify.rs b/desktop/tauri/rust-dark-light/src/platforms/freedesktop/notify.rs new file mode 100644 index 00000000..c63fdc11 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/freedesktop/notify.rs @@ -0,0 +1,42 @@ +use ashpd::desktop::settings::{ColorScheme, Settings}; +use futures::{stream, Stream, StreamExt}; +use std::task::Poll; + +use crate::{detect, Mode}; + +pub async fn subscribe() -> anyhow::Result + Send> { + let stream = if get_freedesktop_color_scheme().await.is_ok() { + let proxy = Settings::new().await?; + proxy + .receive_color_scheme_changed() + .await? + .map(Mode::from) + .boxed() + } else { + let mut last_mode = detect(); + stream::poll_fn(move |ctx| -> Poll> { + let current_mode = detect(); + if current_mode != last_mode { + last_mode = current_mode; + Poll::Ready(Some(current_mode)) + } else { + ctx.waker().wake_by_ref(); + Poll::Pending + } + }) + .boxed() + }; + + Ok(stream) +} + +async fn get_freedesktop_color_scheme() -> anyhow::Result { + let proxy = Settings::new().await?; + let color_scheme = proxy.color_scheme().await?; + let mode = match color_scheme { + ColorScheme::PreferDark => Mode::Dark, + ColorScheme::PreferLight => Mode::Light, + ColorScheme::NoPreference => Mode::Default, + }; + Ok(mode) +} diff --git a/desktop/tauri/rust-dark-light/src/platforms/macos/detect.rs b/desktop/tauri/rust-dark-light/src/platforms/macos/detect.rs new file mode 100644 index 00000000..5c56cb83 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/macos/detect.rs @@ -0,0 +1,56 @@ +// Dark/light mode detection on macOS. +// Written with help from Ryan McGrath (https://rymc.io/). + +use crate::Mode; +use objc::runtime::Object; +use objc::{class, msg_send, sel, sel_impl}; + +extern "C" { + static NSAppearanceNameAqua: *const Object; + static NSAppearanceNameAccessibilityHighContrastAqua: *const Object; + static NSAppearanceNameDarkAqua: *const Object; + static NSAppearanceNameAccessibilityHighContrastDarkAqua: *const Object; +} + +fn is_dark_mode_enabled() -> bool { + unsafe { + let mut appearance: *const Object = msg_send![class!(NSAppearance), currentAppearance]; + if appearance.is_null() { + appearance = msg_send![class!(NSApp), effectiveAppearance]; + } + + let objects = [ + NSAppearanceNameAqua, + NSAppearanceNameAccessibilityHighContrastAqua, + NSAppearanceNameDarkAqua, + NSAppearanceNameAccessibilityHighContrastDarkAqua, + ]; + let names: *const Object = msg_send![ + class!(NSArray), + arrayWithObjects:objects.as_ptr() + count:objects.len() + ]; + + // `bestMatchFromAppearancesWithNames` is only available in macOS 10.14+. + // Gracefully handle earlier versions. + let responds_to_selector: objc::runtime::BOOL = msg_send![ + appearance, + respondsToSelector: sel!(bestMatchFromAppearancesWithNames:) + ]; + if responds_to_selector == objc::runtime::NO { + return false; + } + + let style: *const Object = msg_send![ + appearance, + bestMatchFromAppearancesWithNames:&*names + ]; + + style == NSAppearanceNameDarkAqua + || style == NSAppearanceNameAccessibilityHighContrastDarkAqua + } +} + +pub fn detect() -> crate::Mode { + Mode::from_bool(is_dark_mode_enabled()) +} diff --git a/desktop/tauri/rust-dark-light/src/platforms/macos/mod.rs b/desktop/tauri/rust-dark-light/src/platforms/macos/mod.rs new file mode 100644 index 00000000..bfae5452 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/macos/mod.rs @@ -0,0 +1,2 @@ +pub mod detect; +pub mod notify; diff --git a/desktop/tauri/rust-dark-light/src/platforms/macos/notify.rs b/desktop/tauri/rust-dark-light/src/platforms/macos/notify.rs new file mode 100644 index 00000000..305205f2 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/macos/notify.rs @@ -0,0 +1,23 @@ +use std::task::Poll; + +use futures::{stream, Stream}; + +use crate::{detect, Mode}; + +pub async fn subscribe() -> anyhow::Result + Send> { + let mut last_mode = detect(); + + let stream = stream::poll_fn(move |ctx| -> Poll> { + let current_mode = detect(); + + if current_mode != last_mode { + last_mode = current_mode; + Poll::Ready(Some(current_mode)) + } else { + ctx.waker().wake_by_ref(); + Poll::Pending + } + }); + + Ok(stream) +} diff --git a/desktop/tauri/rust-dark-light/src/platforms/mod.rs b/desktop/tauri/rust-dark-light/src/platforms/mod.rs new file mode 100644 index 00000000..35037de3 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/mod.rs @@ -0,0 +1,48 @@ +#[cfg(target_os = "macos")] +pub mod macos; + +#[cfg(target_os = "macos")] +pub use macos as platform; + +#[cfg(target_os = "windows")] +pub mod windows; +#[cfg(target_os = "windows")] +pub use windows as platform; + +#[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" +))] +pub mod freedesktop; +#[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd" +))] +pub use freedesktop as platform; + +#[cfg(target_arch = "wasm32")] +pub mod websys; +#[cfg(target_arch = "wasm32")] +pub use websys as platform; + +#[cfg(not(any( + target_os = "macos", + target_os = "windows", + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "netbsd", + target_os = "openbsd", + target_arch = "wasm32" +)))] +pub mod platform { + pub fn detect() -> crate::Mode { + super::Mode::Light + } +} diff --git a/desktop/tauri/rust-dark-light/src/platforms/websys/detect.rs b/desktop/tauri/rust-dark-light/src/platforms/websys/detect.rs new file mode 100644 index 00000000..33431e82 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/websys/detect.rs @@ -0,0 +1,11 @@ +use crate::Mode; + +pub fn detect() -> crate::Mode { + if let Some(window) = web_sys::window() { + let query_result = window.match_media("(prefers-color-scheme: dark)"); + if let Ok(Some(mql)) = query_result { + return Mode::from_bool(mql.matches()); + } + } + Mode::Light +} diff --git a/desktop/tauri/rust-dark-light/src/platforms/websys/mod.rs b/desktop/tauri/rust-dark-light/src/platforms/websys/mod.rs new file mode 100644 index 00000000..bfae5452 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/websys/mod.rs @@ -0,0 +1,2 @@ +pub mod detect; +pub mod notify; diff --git a/desktop/tauri/rust-dark-light/src/platforms/websys/notify.rs b/desktop/tauri/rust-dark-light/src/platforms/websys/notify.rs new file mode 100644 index 00000000..305205f2 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/websys/notify.rs @@ -0,0 +1,23 @@ +use std::task::Poll; + +use futures::{stream, Stream}; + +use crate::{detect, Mode}; + +pub async fn subscribe() -> anyhow::Result + Send> { + let mut last_mode = detect(); + + let stream = stream::poll_fn(move |ctx| -> Poll> { + let current_mode = detect(); + + if current_mode != last_mode { + last_mode = current_mode; + Poll::Ready(Some(current_mode)) + } else { + ctx.waker().wake_by_ref(); + Poll::Pending + } + }); + + Ok(stream) +} diff --git a/desktop/tauri/rust-dark-light/src/platforms/windows/detect.rs b/desktop/tauri/rust-dark-light/src/platforms/windows/detect.rs new file mode 100644 index 00000000..39b94603 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/windows/detect.rs @@ -0,0 +1,15 @@ +use crate::Mode; +use winreg::RegKey; + +const SUBKEY: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +const VALUE: &str = "AppsUseLightTheme"; + +pub fn detect() -> Mode { + let hkcu = RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + if let Ok(subkey) = hkcu.open_subkey(SUBKEY) { + if let Ok(dword) = subkey.get_value::(VALUE) { + return Mode::from_bool(dword == 0); + } + } + Mode::Light +} diff --git a/desktop/tauri/rust-dark-light/src/platforms/windows/mod.rs b/desktop/tauri/rust-dark-light/src/platforms/windows/mod.rs new file mode 100644 index 00000000..bfae5452 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/windows/mod.rs @@ -0,0 +1,2 @@ +pub mod detect; +pub mod notify; diff --git a/desktop/tauri/rust-dark-light/src/platforms/windows/notify.rs b/desktop/tauri/rust-dark-light/src/platforms/windows/notify.rs new file mode 100644 index 00000000..305205f2 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/platforms/windows/notify.rs @@ -0,0 +1,23 @@ +use std::task::Poll; + +use futures::{stream, Stream}; + +use crate::{detect, Mode}; + +pub async fn subscribe() -> anyhow::Result + Send> { + let mut last_mode = detect(); + + let stream = stream::poll_fn(move |ctx| -> Poll> { + let current_mode = detect(); + + if current_mode != last_mode { + last_mode = current_mode; + Poll::Ready(Some(current_mode)) + } else { + ctx.waker().wake_by_ref(); + Poll::Pending + } + }); + + Ok(stream) +} diff --git a/desktop/tauri/rust-dark-light/src/utils/mod.rs b/desktop/tauri/rust-dark-light/src/utils/mod.rs new file mode 100644 index 00000000..9d3d8988 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod rgb; diff --git a/desktop/tauri/rust-dark-light/src/utils/rgb.rs b/desktop/tauri/rust-dark-light/src/utils/rgb.rs new file mode 100644 index 00000000..d43f62b9 --- /dev/null +++ b/desktop/tauri/rust-dark-light/src/utils/rgb.rs @@ -0,0 +1,23 @@ +use std::str::FromStr; + +/// Struct representing an RGB color +pub(crate) struct Rgb(pub(crate) u32, pub(crate) u32, pub(crate) u32); + +impl FromStr for Rgb { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let rgb = s + .split(',') + .map(|s| s.parse::().unwrap_or(255)) + .try_fold(vec![], |mut acc, x| { + if acc.len() < 3 { + acc.push(x); + Ok(acc) + } else { + Err(anyhow::anyhow!("RGB format is invalid")) + } + })?; + Ok(Rgb(rgb[0], rgb[1], rgb[2])) + } +} diff --git a/desktop/tauri/src-tauri/Cargo.lock b/desktop/tauri/src-tauri/Cargo.lock index d1117733..49a5095b 100644 --- a/desktop/tauri/src-tauri/Cargo.lock +++ b/desktop/tauri/src-tauri/Cargo.lock @@ -1144,7 +1144,6 @@ dependencies = [ [[package]] name = "dark-light" version = "1.1.1" -source = "git+https://github.com/vlabo/rust-dark-light?rev=1f955c84d0ea05729bb5ecab29fb1b315b9897de#1f955c84d0ea05729bb5ecab29fb1b315b9897de" dependencies = [ "anyhow", "ashpd 0.7.0", diff --git a/desktop/tauri/src-tauri/Cargo.toml b/desktop/tauri/src-tauri/Cargo.toml index 98f0767b..073cf8e1 100644 --- a/desktop/tauri/src-tauri/Cargo.toml +++ b/desktop/tauri/src-tauri/Cargo.toml @@ -52,7 +52,7 @@ reqwest = { version = "0.12" } rfd = { version = "*", default-features = false, features = [ "tokio", "gtk3", "common-controls-v6" ] } open = "5.1.3" -dark-light = { git = "https://github.com/vlabo/rust-dark-light", rev = "1f955c84d0ea05729bb5ecab29fb1b315b9897de" } +dark-light = { path = "../rust-dark-light" } # Linux only [target.'cfg(target_os = "linux")'.dependencies]