diff --git a/.gitignore b/.gitignore index 96ef6c0..eb489b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target +/examples/*/target Cargo.lock +.vscode diff --git a/Cargo.toml b/Cargo.toml index 84e1e46..21333b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,12 @@ description = "Detect if dark mode or light mode is enabled" readme = "README.md" build = "build.rs" +[dependencies] +anyhow = "1.0.52" + +[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 = "0.2" dconf_rs = "0.3" @@ -16,6 +22,9 @@ dirs = "4.0" zbus = "3.0" zvariant = "3.6" rust-ini = "0.18" +ashpd = { git = "https://github.com/bilelmoussaoui/ashpd", branch = "master" } +crossbeam = "0.8.2" +tokio = { version = "1.23.0", features = ["full"] } [target.'cfg(windows)'.dependencies] winreg = "0.10" diff --git a/examples/color_scheme.rs b/examples/color_scheme.rs new file mode 100644 index 0000000..a594b7c --- /dev/null +++ b/examples/color_scheme.rs @@ -0,0 +1,10 @@ +#[tokio::main] +async fn main() { + let proxy = ashpd::desktop::settings::Settings::new().await.unwrap(); + let color_scheme = proxy.color_scheme().await.unwrap(); + match color_scheme { + ashpd::desktop::settings::ColorScheme::PreferDark => println!("Dark"), + ashpd::desktop::settings::ColorScheme::PreferLight => println!("Light"), + ashpd::desktop::settings::ColorScheme::NoPreference => println!("Default"), + }; +} \ No newline at end of file diff --git a/examples/notify.rs b/examples/notify.rs new file mode 100644 index 0000000..a13f0ae --- /dev/null +++ b/examples/notify.rs @@ -0,0 +1,12 @@ +use anyhow::Ok; +use dark_light::*; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let (tx, mut rx) = tokio::sync::mpsc::channel(4); + notify(tx).await?; + while let Some(mode) = rx.recv().await { + println!("{:?}", mode) + } + Ok(()) +} \ No newline at end of file diff --git a/src/freedesktop.rs b/src/freedesktop.rs deleted file mode 100644 index 68ff269..0000000 --- a/src/freedesktop.rs +++ /dev/null @@ -1,96 +0,0 @@ -use detect_desktop_environment::DesktopEnvironment; -use ini::Ini; -use std::path::{Path, PathBuf}; -use zbus::blocking::Connection; -use zvariant::Value; - -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::(); - if theme.is_err() { - return None; - } - let theme = theme.unwrap().downcast::(); - 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(e) => { - eprintln!("{:?}", e); - 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/src/freedesktop/detect.rs b/src/freedesktop/detect.rs new file mode 100644 index 0000000..3752240 --- /dev/null +++ b/src/freedesktop/detect.rs @@ -0,0 +1,59 @@ +use anyhow::Context; +use detect_desktop_environment::DesktopEnvironment; +use ini::Ini; +use std::path::{Path, PathBuf}; + +use crate::Mode; + +const XDG_KDEGLOBALS: &str = "/etc/xdg/kdeglobals"; + +fn detect_gtk(pattern: &str) -> Mode { + match dconf_rs::get_string(pattern) { + Ok(theme) => Mode::from(theme.to_lowercase().contains("dark")), + Err(_) => Mode::Default, + } +} + +fn detect_kde(path: &str) -> anyhow::Result { + let cfg = Ini::load_from_file(path)?; + let section = cfg.section(Some("Colors:Window")).with_context(|| "Failed to get section Colors:Window")?; + let values = section.get("BackgroundNormal").with_context(|| "Failed to get BackgroundNormal inside Colors:Window")?; + let rgb = values + .split(',') + .map(|s| s.parse::().unwrap_or(255)) + .collect::>(); + let rgb = if rgb.len() >= 3 { + rgb + } else { + vec![255, 255, 255] + }; + let (r, g, b) = (rgb[0], rgb[1], rgb[2]); + Ok(Mode::from_rgb(r, g, b)) +} + +fn legacy_detect() -> anyhow::Result { + let mode = 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, + }; + Ok(mode) +} + +pub fn detect() -> Mode { + match legacy_detect() { + Ok(mode) => mode, + Err(_) => Mode::Default, + } +} + diff --git a/src/freedesktop/mod.rs b/src/freedesktop/mod.rs new file mode 100644 index 0000000..65537b0 --- /dev/null +++ b/src/freedesktop/mod.rs @@ -0,0 +1,17 @@ +use ashpd::desktop::settings::{Settings, ColorScheme}; + +use crate::Mode; + +pub mod detect; +pub mod notify; + +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) +} \ No newline at end of file diff --git a/src/freedesktop/notify.rs b/src/freedesktop/notify.rs new file mode 100644 index 0000000..f244864 --- /dev/null +++ b/src/freedesktop/notify.rs @@ -0,0 +1,41 @@ + +use tokio::sync::mpsc::Sender; +use ashpd::desktop::settings::{Settings, ColorScheme}; + +use crate::{Mode, detect}; + +use super::get_freedesktop_color_scheme; + +pub async fn notify(tx: Sender) -> anyhow::Result<()> { + if get_freedesktop_color_scheme().await.is_ok() { + tokio::spawn(freedesktop_watch(tx)); + } else { + eprintln!("Unable to start proxy, falling back to legacy..."); + tokio::spawn(non_freedesktop_watch(tx)); + } + Ok(()) +} + +async fn freedesktop_watch(tx: Sender) -> anyhow::Result<()> { + let proxy = Settings::new().await?; + while let Ok(color_scheme) = proxy.receive_color_scheme_changed().await { + let mode = match color_scheme { + ColorScheme::NoPreference => Mode::Default, + ColorScheme::PreferDark => Mode::Dark, + ColorScheme::PreferLight => Mode::Light, + }; + tx.send(mode).await?; + } + Ok(()) +} + +async fn non_freedesktop_watch(tx: Sender) -> anyhow::Result<()> { + let mut mode = detect(); + loop { + let new_mode = detect(); + if mode != new_mode { + mode = new_mode; + tx.send(mode).await?; + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 04b7420..51c462a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ #[cfg(target_os = "macos")] mod macos; + #[cfg(target_os = "macos")] use macos as platform; @@ -63,6 +64,8 @@ mod platform { } } +use tokio::sync::mpsc::Sender; + #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Mode { /// Dark mode @@ -81,7 +84,7 @@ impl Mode { Mode::Light } } - fn rgb(r: u32, g: u32, b: u32) -> Self { + fn from_rgb(r: u32, g: u32, b: u32) -> Self { let window_background_gray = (r * 11 + g * 16 + b * 5) / 32; if window_background_gray < 192 { Self::Dark @@ -93,5 +96,9 @@ impl Mode { /// Detect if light mode or dark mode is enabled. If the mode can’t be detected, fall back to [`Mode::Default`]. pub fn detect() -> Mode { - platform::detect() + platform::detect::detect() } + +pub async fn notify(tx: Sender) -> anyhow::Result<()> { + platform::notify::notify(tx).await +} \ No newline at end of file diff --git a/src/macos.rs b/src/macos/detect.rs similarity index 100% rename from src/macos.rs rename to src/macos/detect.rs diff --git a/src/macos/mod.rs b/src/macos/mod.rs new file mode 100644 index 0000000..106135e --- /dev/null +++ b/src/macos/mod.rs @@ -0,0 +1,2 @@ +pub mod detect; +pub mod notify; \ No newline at end of file diff --git a/src/macos/notify.rs b/src/macos/notify.rs new file mode 100644 index 0000000..eeea9df --- /dev/null +++ b/src/macos/notify.rs @@ -0,0 +1,5 @@ +use crate::Mode; + +pub async fn notify(callback: &dyn Fn(Mode)) -> anyhow::Result<()> { + todo!() +} \ No newline at end of file diff --git a/src/windows.rs b/src/windows/detect.rs similarity index 100% rename from src/windows.rs rename to src/windows/detect.rs diff --git a/src/windows/mod.rs b/src/windows/mod.rs new file mode 100644 index 0000000..bfae545 --- /dev/null +++ b/src/windows/mod.rs @@ -0,0 +1,2 @@ +pub mod detect; +pub mod notify; diff --git a/src/windows/notify.rs b/src/windows/notify.rs new file mode 100644 index 0000000..025a965 --- /dev/null +++ b/src/windows/notify.rs @@ -0,0 +1,12 @@ +use std::sync::mpsc::Sender; + +const duration: std::time::Duration = std::time::Duration::from_secs(1); + +pub async fn notify(tx: Sender) -> anyhow::Result<()> { + tx.send(crate::Mode::Default)?; + std::thread::sleep(duration); + tx.send(crate::Mode::Light)?; + std::thread::sleep(duration); + tx.send(crate::Mode::Dark)?; + Ok(()) +} \ No newline at end of file