mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
[desktop] Add rust-dark-light as direct dependency
This commit is contained in:
parent
9ff7ec96d1
commit
b2907e9475
23 changed files with 654 additions and 2 deletions
4
desktop/tauri/rust-dark-light/.gitignore
vendored
Normal file
4
desktop/tauri/rust-dark-light/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/target
|
||||
/examples/*/target
|
||||
Cargo.lock
|
||||
.vscode
|
34
desktop/tauri/rust-dark-light/Cargo.toml
Normal file
34
desktop/tauri/rust-dark-light/Cargo.toml
Normal file
|
@ -0,0 +1,34 @@
|
|||
[package]
|
||||
name = "dark-light"
|
||||
version = "1.1.1"
|
||||
authors = ["Corey Farwell <coreyf@rwell.org>"]
|
||||
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"] }
|
39
desktop/tauri/rust-dark-light/README.md
Normal file
39
desktop/tauri/rust-dark-light/README.md
Normal file
|
@ -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.
|
||||
|
||||
|
5
desktop/tauri/rust-dark-light/build.rs
Normal file
5
desktop/tauri/rust-dark-light/build.rs
Normal file
|
@ -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");
|
||||
}
|
||||
}
|
92
desktop/tauri/rust-dark-light/src/freedesktop.rs
Normal file
92
desktop/tauri/rust-dark-light/src/freedesktop.rs
Normal file
|
@ -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<Mode> {
|
||||
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::<u32>();
|
||||
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::<u32>().unwrap_or(255))
|
||||
.collect::<Vec<u32>>();
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
73
desktop/tauri/rust-dark-light/src/lib.rs
Normal file
73
desktop/tauri/rust-dark-light/src/lib.rs
Normal file
|
@ -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;
|
|
@ -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)
|
||||
}
|
1
desktop/tauri/rust-dark-light/src/utils/mod.rs
Normal file
1
desktop/tauri/rust-dark-light/src/utils/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod rgb;
|
23
desktop/tauri/rust-dark-light/src/utils/rgb.rs
Normal file
23
desktop/tauri/rust-dark-light/src/utils/rgb.rs
Normal file
|
@ -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<Self, Self::Err> {
|
||||
let rgb = s
|
||||
.split(',')
|
||||
.map(|s| s.parse::<u32>().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]))
|
||||
}
|
||||
}
|
1
desktop/tauri/src-tauri/Cargo.lock
generated
1
desktop/tauri/src-tauri/Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Add table
Reference in a new issue