[desktop] Add rust-dark-light as direct dependency
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Mode> {
|
||||
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<ashpd::desktop::settings::ColorScheme> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<impl Stream<Item = Mode> + 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<Option<Mode>> {
|
||||
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<Mode> {
|
||||
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)
|
||||
}
|
||||
56
desktop/tauri/rust-dark-light/src/platforms/macos/detect.rs
Normal file
56
desktop/tauri/rust-dark-light/src/platforms/macos/detect.rs
Normal file
@@ -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())
|
||||
}
|
||||
2
desktop/tauri/rust-dark-light/src/platforms/macos/mod.rs
Normal file
2
desktop/tauri/rust-dark-light/src/platforms/macos/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod detect;
|
||||
pub mod notify;
|
||||
23
desktop/tauri/rust-dark-light/src/platforms/macos/notify.rs
Normal file
23
desktop/tauri/rust-dark-light/src/platforms/macos/notify.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::task::Poll;
|
||||
|
||||
use futures::{stream, Stream};
|
||||
|
||||
use crate::{detect, Mode};
|
||||
|
||||
pub async fn subscribe() -> anyhow::Result<impl Stream<Item = Mode> + Send> {
|
||||
let mut last_mode = detect();
|
||||
|
||||
let stream = stream::poll_fn(move |ctx| -> Poll<Option<Mode>> {
|
||||
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)
|
||||
}
|
||||
48
desktop/tauri/rust-dark-light/src/platforms/mod.rs
Normal file
48
desktop/tauri/rust-dark-light/src/platforms/mod.rs
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
11
desktop/tauri/rust-dark-light/src/platforms/websys/detect.rs
Normal file
11
desktop/tauri/rust-dark-light/src/platforms/websys/detect.rs
Normal file
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod detect;
|
||||
pub mod notify;
|
||||
23
desktop/tauri/rust-dark-light/src/platforms/websys/notify.rs
Normal file
23
desktop/tauri/rust-dark-light/src/platforms/websys/notify.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::task::Poll;
|
||||
|
||||
use futures::{stream, Stream};
|
||||
|
||||
use crate::{detect, Mode};
|
||||
|
||||
pub async fn subscribe() -> anyhow::Result<impl Stream<Item = Mode> + Send> {
|
||||
let mut last_mode = detect();
|
||||
|
||||
let stream = stream::poll_fn(move |ctx| -> Poll<Option<Mode>> {
|
||||
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)
|
||||
}
|
||||
@@ -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::<u32, _>(VALUE) {
|
||||
return Mode::from_bool(dword == 0);
|
||||
}
|
||||
}
|
||||
Mode::Light
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod detect;
|
||||
pub mod notify;
|
||||
@@ -0,0 +1,23 @@
|
||||
use std::task::Poll;
|
||||
|
||||
use futures::{stream, Stream};
|
||||
|
||||
use crate::{detect, Mode};
|
||||
|
||||
pub async fn subscribe() -> anyhow::Result<impl Stream<Item = Mode> + Send> {
|
||||
let mut last_mode = detect();
|
||||
|
||||
let stream = stream::poll_fn(move |ctx| -> Poll<Option<Mode>> {
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user