Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/linux-system-color-scheme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
tao: minor
---

Use the Linux XDG Desktop Portal to add support for the system color scheme. Needs `dbus` feature flag.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ categories = ["gui"]
include = ["/README.md", "src/**/*.rs", "examples/**/*.rs", "LICENSE*"]

[package.metadata.docs.rs]
features = ["rwh_04", "rwh_05", "rwh_06", "serde", "x11"]
features = ["rwh_04", "rwh_05", "rwh_06", "serde", "x11", "dbus"]
default-target = "x86_64-unknown-linux-gnu"
targets = [
"i686-pc-windows-msvc",
Expand All @@ -28,12 +28,13 @@ targets = [
]

[features]
default = ["rwh_06", "x11"]
default = ["rwh_06", "x11", "dbus"]
serde = ["dep:serde", "dpi/serde"]
rwh_04 = ["dep:rwh_04"]
rwh_05 = ["dep:rwh_05"]
rwh_06 = ["dep:rwh_06"]
x11 = ["dep:gdkx11-sys", "dep:x11-dl"]
dbus = ["dep:dbus"]

[workspace]
members = ["tao-macros"]
Expand Down Expand Up @@ -157,3 +158,4 @@ gdkwayland-sys = "0.18.0"
x11-dl = { version = "2.21", optional = true }
parking_lot = "0.12"
dlopen2 = "0.8.0"
dbus = { version = "0.9", optional = true }
2 changes: 1 addition & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ pub enum WindowEvent<'a> {
///
/// ## Platform-specific
///
/// - **Linux / Android / iOS:** Unsupported
/// - **Android / iOS:** Unsupported
Comment thread
haecker-felix marked this conversation as resolved.
Comment thread
Hofer-Julian marked this conversation as resolved.
ThemeChanged(Theme),

/// The window decorations has been clicked.
Expand Down
21 changes: 18 additions & 3 deletions src/platform_impl/linux/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,15 @@ impl<T: 'static> EventLoop<T> {
let mut taskbar = TaskbarIndicator::new();
let is_wayland = window_target.is_wayland();

// Receive portal events
#[cfg(feature = "dbus")]
{
let tx_requests_clone = window_target.window_requests_tx.clone();
if let Err(e) = super::portal::receive_theme_changed(tx_requests_clone) {
log::debug!("Unable to receive theme changed events: {e}");
}
}

// Window Request
window_requests_rx.attach(Some(&context), move |(id, request)| {
if let Some(window) = app_.window_by_id(id.0) {
Expand Down Expand Up @@ -947,9 +956,15 @@ impl<T: 'static> EventLoop<T> {
}
WindowRequest::SetTheme(theme) => {
if let Some(settings) = Settings::default() {
match theme {
Some(Theme::Dark) => settings.set_gtk_application_prefer_dark_theme(true),
Some(Theme::Light) | None => settings.set_gtk_application_prefer_dark_theme(false),
settings.set_gtk_application_prefer_dark_theme(theme == Some(Theme::Dark));
if let Err(e) = event_tx.send(Event::WindowEvent {
window_id: RootWindowId(id),
event: WindowEvent::ThemeChanged(theme.unwrap_or_default()),
}) {
log::warn!(
"Failed to send window theme changed event to event channel: {}",
e
);
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ mod icon;
mod keyboard;
mod keycode;
mod monitor;
#[cfg(feature = "dbus")]
mod portal;
mod util;
mod window;

Expand Down
78 changes: 78 additions & 0 deletions src/platform_impl/linux/portal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#[cfg(feature = "dbus")]
use dbus::{
arg::Variant,
blocking::{Connection, SyncConnection},
message::MatchRule,
Error,
};
use gtk::glib::{ControlFlow, MainContext, Priority, Sender};
use log::warn;
use std::{thread, time::Duration};

use crate::{
platform_impl::{platform::window::WindowRequest, WindowId},
window::Theme,
};

pub fn theme() -> Result<Theme, Error> {
let conn = Connection::new_session()?;
let proxy = conn.with_proxy(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
Duration::from_secs(5),
);

let result: (Variant<Variant<u32>>,) = proxy.method_call(
"org.freedesktop.portal.Settings",
"Read",
("org.freedesktop.appearance", "color-scheme"),
)?;

Ok(color_scheme_to_theme(result.0 .0 .0))
}

pub fn receive_theme_changed(window_tx: Sender<(WindowId, WindowRequest)>) -> Result<(), Error> {
let conn = SyncConnection::new_session()?;
let match_rule = MatchRule::new_signal("org.freedesktop.portal.Settings", "SettingChanged");
let (tx, rx) = MainContext::channel(Priority::DEFAULT);

conn.add_match(match_rule, move |_: (), _, msg| {
let mut iter = msg.iter_init();
if let (Ok("org.freedesktop.appearance"), Ok("color-scheme"), Ok(value)) = (
iter.read::<&str>(), // Namespace
iter.read::<&str>(), // Key
iter.read::<Variant<u32>>(), // Value
) {
if let Err(e) = tx.send(color_scheme_to_theme(value.0)) {
warn!("Failed to send theme change via channel: {}", e);
}
}
true
})?;

rx.attach(None, move |theme| {
if let Err(e) = window_tx.send((WindowId::dummy(), WindowRequest::SetTheme(Some(theme)))) {
warn!("Failed to send theme change request: {}", e);
ControlFlow::Break
} else {
ControlFlow::Continue
}
});

thread::spawn(move || loop {
if let Err(e) = conn.process(Duration::from_secs(5)) {
warn!("D-Bus message processing error: {}", e);
break;
}
});

Ok(())
}

fn color_scheme_to_theme(color_scheme: u32) -> Theme {
match color_scheme {
1 => Theme::Dark, // Prefer Dark
2 => Theme::Light, // Prefer Light
_ => Theme::Light, // No Preference, default to Light
}
}
44 changes: 13 additions & 31 deletions src/platform_impl/linux/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ impl WindowId {
}
}

// Currently GTK doesn't provide feature for detect theme, so we need to check theme manually.
// ref: https://github.com/WebKit/WebKit/blob/e44ffaa0d999a9807f76f1805943eea204cfdfbc/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp#L587
const GTK_THEME_SUFFIX_LIST: [&str; 3] = ["-dark", "-Dark", "-Darker"];

pub struct Window {
/// Window id.
pub(crate) window_id: WindowId,
Expand Down Expand Up @@ -197,29 +193,17 @@ impl Window {
window.stick();
}

let preferred_theme = if let Some(settings) = Settings::default() {
if let Some(preferred_theme) = attributes.preferred_theme {
match preferred_theme {
Theme::Dark => settings.set_gtk_application_prefer_dark_theme(true),
Theme::Light => {
if let Some(theme) = settings.gtk_theme_name() {
let theme = theme.as_str();
// Remove dark variant.
if let Some(theme) = GTK_THEME_SUFFIX_LIST
.iter()
.find(|t| theme.ends_with(*t))
.map(|v| theme.strip_suffix(v))
{
settings.set_gtk_theme_name(theme);
}
}
}
}
// Set initial `preferred_theme` value to current portal color-scheme
#[cfg(feature = "dbus")]
let preferred_theme = super::portal::theme().ok();
#[cfg(not(feature = "dbus"))]
let preferred_theme = None;

if let Some(theme) = preferred_theme {
if let Some(settings) = Settings::default() {
settings.set_gtk_application_prefer_dark_theme(theme == Theme::Dark);
}
attributes.preferred_theme
} else {
None
};
}

if attributes.visible {
window.show_all();
Expand Down Expand Up @@ -1029,11 +1013,9 @@ impl Window {
return theme;
}

if let Some(theme) = Settings::default().and_then(|s| s.gtk_theme_name()) {
let theme = theme.as_str();
if GTK_THEME_SUFFIX_LIST.iter().any(|t| theme.ends_with(t)) {
return Theme::Dark;
}
#[cfg(feature = "dbus")]
if let Ok(portal_theme) = super::portal::theme() {
return portal_theme;
}

Theme::Light
Expand Down
Loading