Repository: slint-ui/slint (language: rust)

Issue:
Dark/Light mode detection on Linux with winit is unreliable 
As a fan of the dark mode, the default bright mode is not very friendly to my eyes.

It would be useful to add a dark background

![Screenshot from 2024-01-21 14-53-14](https://github.com/slint-ui/slint/assets/41945903/a0706034-87f6-4d21-8ec0-7d4492e0b48e)

Edit: While the contrast issue is fixed, the issue that remains here is that with winit we don't get notified when the theme changes on Linux. See also https://github.com/rust-windowing/winit/issues/1549


Discussion / hints:
Odd, for me the toolbar with the pick mode button and the combobox looks okay (macOS). But the area around the preview'ed UI is unconditionally white:

```
background: root.experimental ? Colors.pink : Colors.white ;
```

I guess that should be `Palette.background` instead? @FloVanGH : Or should it be another one of the alternate backgrounds?
@tronical yes it's right it should be `Palette.background`
I would love to use a checkerboard pattern or something that contrasts with both light and dark backgrounds. We would have tiled images for that.
I did not use any default background color as that is very likely used in the embedded UI as well, making it hard to see where the "outer" UI stops and the "inner" UI a user wants to design starts.
We just discussed this briefly. While for the preview it would make sense to use `Palette.alternate-background`, the real issue seems to be that on Linux with winit the detection of the theme doesn't work reliably. Strictly speaking, this regressed with commit a45421b425748d459f8916e730f37b64aed9dfbe .
Light-sensitive legally blind developer here. Please support dark mode. Someone just removed dark-light detection on Linux to save 1/6 of their compile time. Accessibility should be on by default, not off by default. Dark mode is part of accessibility. It should at least default to dark mode if you're going to remove detection, because most other apps are now defaulting to dark mode.

Make dark-light part of the default feature set. Flag it as a feature. Then, devs will have to explicitly remove it. Sadly, they will just to save compile time in development, and forget to turn it on in production, because accessibility is always an afterthought. But it's better than turning it off for everyone.
Is there at the least some kind of environment variable or LSP arg to use dark mode with a dark background?

Fix patch:
diff --git a/internal/backends/winit/Cargo.toml b/internal/backends/winit/Cargo.toml
index 4ca76f40327..27e7089fb67 100644
--- a/internal/backends/winit/Cargo.toml
+++ b/internal/backends/winit/Cargo.toml
@@ -69,6 +69,10 @@ accesskit = { version = "0.17", optional = true }
 accesskit_winit = { version = "0.23", optional = true }
 copypasta = { version = "0.10", default-features = false }
 
+[target.'cfg(not(any(target_family = "windows", target_os = "macos", target_os = "ios", target_arch = "wasm32")))'.dependencies]
+ashpd = { version = "0.9.2" }
+futures = { version = "0.3.31" }
+
 [target.'cfg(target_os = "macos")'.dependencies]
 # For GL rendering
 objc2-app-kit = { version = "0.2.2" }
diff --git a/internal/backends/winit/build.rs b/internal/backends/winit/build.rs
index 44374c63fdf..a9a8ca811f4 100644
--- a/internal/backends/winit/build.rs
+++ b/internal/backends/winit/build.rs
@@ -8,7 +8,8 @@ fn main() {
     cfg_aliases! {
        enable_skia_renderer: { any(feature = "renderer-skia", feature = "renderer-skia-opengl", feature = "renderer-skia-vulkan")},
        enable_accesskit: { all(feature = "accessibility", not(target_arch = "wasm32")) },
-       supports_opengl: { all(any(enable_skia_renderer, feature = "renderer-femtovg"), not(target_os = "ios")) }
+       supports_opengl: { all(any(enable_skia_renderer, feature = "renderer-femtovg"), not(target_os = "ios")) },
+       use_winit_theme: { any(target_family = "windows", target_os = "macos", target_os = "ios", target_arch = "wasm32") }
     }
     // This uses `web_sys_unstable_api`, which is typically set via `RUST_FLAGS`
     println!("cargo:rustc-check-cfg=cfg(web_sys_unstable_apis)");
diff --git a/internal/backends/winit/winitwindowadapter.rs b/internal/backends/winit/winitwindowadapter.rs
index a31fa4890a2..473ea7f4be3 100644
--- a/internal/backends/winit/winitwindowadapter.rs
+++ b/internal/backends/winit/winitwindowadapter.rs
@@ -290,6 +290,9 @@ pub struct WinitWindowAdapter {
     >,
 
     winit_window_or_none: RefCell<WinitWindowOrNone>,
+
+    #[cfg(not(use_winit_theme))]
+    xdg_settings_watcher: RefCell<Option<i_slint_core::future::JoinHandle<()>>>,
 }
 
 impl WinitWindowAdapter {
@@ -323,6 +326,8 @@ impl WinitWindowAdapter {
             #[cfg(enable_accesskit)]
             event_loop_proxy: proxy,
             window_event_filter: Cell::new(None),
+            #[cfg(not(use_winit_theme))]
+            xdg_settings_watcher: Default::default(),
         });
 
         let winit_window = self_rc.ensure_window()?;
@@ -628,6 +633,44 @@ impl WinitWindowAdapter {
             WinitWindowOrNone::None(..) => {}
         }
     }
+
+    #[cfg(not(use_winit_theme))]
+    fn spawn_xdg_settings_watcher(&self) -> Option<i_slint_core::future::JoinHandle<()>> {
+        let window_inner = WindowInner::from_pub(self.window());
+        let self_weak = self.self_weak.clone();
+        window_inner
+            .context()
+            .spawn_local(async move {
+                let Ok(settings) = ashpd::desktop::settings::Settings::new().await else { return };
+
+                let Ok(initial_color_scheme_value) = settings.color_scheme().await else { return };
+
+                let convert = |ashpd_color_scheme| match ashpd_color_scheme {
+                    ashpd::desktop::settings::ColorScheme::NoPreference => ColorScheme::Unknown,
+                    ashpd::desktop::settings::ColorScheme::PreferDark => ColorScheme::Dark,
+                    ashpd::desktop::settings::ColorScheme::PreferLight => ColorScheme::Light,
+                };
+
+                if let Some(window) = self_weak.upgrade() {
+                    window.set_color_scheme(convert(initial_color_scheme_value));
+                }
+
+                let Ok(mut color_scheme_stream) = settings.receive_color_scheme_changed().await
+                else {
+                    return;
+                };
+
+                loop {
+                    use futures::stream::StreamExt;
+
+                    let Some(new_scheme) = color_scheme_stream.next().await else { break };
+                    if let Some(window) = self_weak.upgrade() {
+                        window.set_color_scheme(convert(new_scheme));
+                    }
+                }
+            })
+            .ok()
+    }
 }
 
 impl WindowAdapter for WinitWindowAdapter {
@@ -1046,14 +1089,23 @@ impl WindowAdapterInternal for WinitWindowAdapter {
         self.color_scheme
             .get_or_init(|| {
                 Box::pin(Property::new({
-                    self.winit_window_or_none
-                        .borrow()
-                        .as_window()
-                        .and_then(|window| window.theme())
-                        .map_or(ColorScheme::Unknown, |theme| match theme {
-                            winit::window::Theme::Dark => ColorScheme::Dark,
-                            winit::window::Theme::Light => ColorScheme::Light,
-                        })
+                    cfg_if::cfg_if! {
+                        if #[cfg(use_winit_theme)] {
+                            self.winit_window_or_none
+                                .borrow()
+                                .as_window()
+                                .and_then(|window| window.theme())
+                                .map_or(ColorScheme::Unknown, |theme| match theme {
+                                    winit::window::Theme::Dark => ColorScheme::Dark,
+                                    winit::window::Theme::Light => ColorScheme::Light,
+                                })
+                        } else {
+                            if let Some(old_watch) = self.xdg_settings_watcher.replace(self.spawn_xdg_settings_watcher()) {
+                                old_watch.abort()
+                            }
+                            ColorScheme::Unknown
+                        }
+                    }
                 }))
             })
             .as_ref()
@@ -1121,6 +1173,11 @@ impl Drop for WinitWindowAdapter {
         if let Some(winit_window) = self.winit_window_or_none.borrow().as_window() {
             crate::event_loop::unregister_window(winit_window.id());
         }
+
+        #[cfg(not(use_winit_theme))]
+        if let Some(xdg_watch_future) = self.xdg_settings_watcher.take() {
+            xdg_watch_future.abort();
+        }
     }
 }
 
diff --git a/internal/core/window.rs b/internal/core/window.rs
index 77823476872..cbe14c10266 100644
--- a/internal/core/window.rs
+++ b/internal/core/window.rs
@@ -1294,6 +1294,11 @@ impl WindowInner {
     pub fn from_pub(window: &crate::api::Window) -> &Self {
         &window.0
     }
+
+    /// Provides access to the Windows' Slint context.
+    pub fn context(&self) -> &crate::SlintContext {
+        &*self.ctx
+    }
 }
 
 /// Internal alias for `Rc<dyn WindowAdapter>`.


Test patch: