Repository: wezterm/wezterm (language: rust)

Issue:
get_appearance toggling skips first change when starting in dark mode
### What Operating System(s) are you seeing this problem on?

Linux Wayland

### Which Wayland compositor or X11 Window manager(s) are you using?

Sway

### WezTerm version

20240203-110809-5046fc22

### Did you try the latest nightly build to see if the issue is better (or worse!) than your current version?

Yes, the toggling didn't work at all.

### Describe the bug

I'm using `get_appearance` as shown [here](https://wezfurlong.org/wezterm/config/lua/window/get_appearance.html?h=get_app) to toggle between a like and dark theme.  When I start a new process (i.e., when there aren't any wezterm windows open or if I run  `wezterm start --always-new-process`) and I am already in dark mode, switching my desktop to light mode does not change wezterm's appearance. If I then switch back to dark mode, and switch again to light mode, wezterm's appearance is updated. Subsequent changes work correctly.

This does not happen when I start wezterm and am already in light mode. In this instance, changing to dark mode immediately switches to dark mode, and subsequent changes work correctly.

I am using `darkman` as my colors-scheme switcher.

### To Reproduce

This is easily reproduced  under sway using darkman.

- `darkmanctl set dark` to get into dark mode
- With no wezterm instances running, run wezterm
- toggle to light mode using `darkmanctl toggle` (nothing will happen to wezterm)
- toggle back to dark mode with `darkmanctl toggle`
- toggle once more to light mode with `darkmanctl toggle` (wezterm is now using the light theme)

### Configuration

```lua
local wezterm = require 'wezterm'
wezterm.add_to_config_reload_watch_list(wezterm.config_dir)

function get_appearance()
  if wezterm.gui then
    return wezterm.gui.get_appearance()
  end
  return 'Dark'
end

function scheme_for_appearance(appearance)
  if appearance:find 'Dark' then
    return 'Modus-Vivendi'
  else
    return 'Modus-Operandi-Tinted'
  end
end

function weight_for_appearance(appearance)
   if appearance:find 'Dark' then
      return "Medium"
   else
      return "Light"
   end
end

return {
  window_decorations = 'RESIZE',
  term = 'wezterm',
  enable_wayland = true,
  color_scheme = scheme_for_appearance(get_appearance()),
  font = wezterm.font {
     family = "Iosevka Comfy Motion",
     weight = weight_for_appearance(get_appearance()),
     harfbuzz_features = { "calt" },
  },
  font_size = 11,
  use_fancy_tab_bar = false,
  hide_tab_bar_if_only_one_tab = true,
}
```

### Expected Behavior

The theme is changed to light theme immediately

### Logs

Nothing shows up in the logs

### Anything else?

I tried this using nightly, but nightly didn't seem to work at all with `get_appearance`. The theme was always the light theme no matter what I did. I tried this on the nightly appimage and by building it from source.
get_appearance toggling skips first change when starting in dark mode
### What Operating System(s) are you seeing this problem on?

Linux Wayland

### Which Wayland compositor or X11 Window manager(s) are you using?

Sway

### WezTerm version

20240203-110809-5046fc22

### Did you try the latest nightly build to see if the issue is better (or worse!) than your current version?

Yes, the toggling didn't work at all.

### Describe the bug

I'm using `get_appearance` as shown [here](https://wezfurlong.org/wezterm/config/lua/window/get_appearance.html?h=get_app) to toggle between a like and dark theme.  When I start a new process (i.e., when there aren't any wezterm windows open or if I run  `wezterm start --always-new-process`) and I am already in dark mode, switching my desktop to light mode does not change wezterm's appearance. If I then switch back to dark mode, and switch again to light mode, wezterm's appearance is updated. Subsequent changes work correctly.

This does not happen when I start wezterm and am already in light mode. In this instance, changing to dark mode immediately switches to dark mode, and subsequent changes work correctly.

I am using `darkman` as my colors-scheme switcher.

### To Reproduce

This is easily reproduced  under sway using darkman.

- `darkmanctl set dark` to get into dark mode
- With no wezterm instances running, run wezterm
- toggle to light mode using `darkmanctl toggle` (nothing will happen to wezterm)
- toggle back to dark mode with `darkmanctl toggle`
- toggle once more to light mode with `darkmanctl toggle` (wezterm is now using the light theme)

### Configuration

```lua
local wezterm = require 'wezterm'
wezterm.add_to_config_reload_watch_list(wezterm.config_dir)

function get_appearance()
  if wezterm.gui then
    return wezterm.gui.get_appearance()
  end
  return 'Dark'
end

function scheme_for_appearance(appearance)
  if appearance:find 'Dark' then
    return 'Modus-Vivendi'
  else
    return 'Modus-Operandi-Tinted'
  end
end

function weight_for_appearance(appearance)
   if appearance:find 'Dark' then
      return "Medium"
   else
      return "Light"
   end
end

return {
  window_decorations = 'RESIZE',
  term = 'wezterm',
  enable_wayland = true,
  color_scheme = scheme_for_appearance(get_appearance()),
  font = wezterm.font {
     family = "Iosevka Comfy Motion",
     weight = weight_for_appearance(get_appearance()),
     harfbuzz_features = { "calt" },
  },
  font_size = 11,
  use_fancy_tab_bar = false,
  hide_tab_bar_if_only_one_tab = true,
}
```

### Expected Behavior

The theme is changed to light theme immediately

### Logs

Nothing shows up in the logs

### Anything else?

I tried this using nightly, but nightly didn't seem to work at all with `get_appearance`. The theme was always the light theme no matter what I did. I tried this on the nightly appimage and by building it from source.


Discussion / hints:
I also tried this on gnome using the quick settings toggle to change back and forth, and the issue is present there as well.
I also tried this on gnome using the quick settings toggle to change back and forth, and the issue is present there as well.

Fix patch:
diff --git a/window/src/os/wayland/connection.rs b/window/src/os/wayland/connection.rs
index f917bdfcadd..c85fb8aafb1 100644
--- a/window/src/os/wayland/connection.rs
+++ b/window/src/os/wayland/connection.rs
@@ -13,7 +13,7 @@ use wayland_client::{Connection as WConnection, EventQueue};
 
 use crate::screen::{ScreenInfo, Screens};
 use crate::spawn::SPAWN_QUEUE;
-use crate::{Connection, ConnectionOps, ScreenRect};
+use crate::{Appearance, Connection, ConnectionOps, ScreenRect};
 
 use super::state::WaylandState;
 use super::WaylandWindowInner;
@@ -46,7 +46,11 @@ impl WaylandConnection {
         Ok(wayland_connection)
     }
 
-    pub(crate) fn advise_of_appearance_change(&self, _appearance: crate::Appearance) {}
+    pub(crate) fn advise_of_appearance_change(&self, appearance: crate::Appearance) {
+        for win in self.wayland_state.borrow().windows.borrow().values() {
+            win.borrow_mut().appearance_changed(appearance);
+        }
+    }
 
     fn run_message_loop_impl(&self) -> anyhow::Result<()> {
         const TOK_WL: usize = 0xffff_fffc;
@@ -172,6 +176,18 @@ impl ConnectionOps for WaylandConnection {
         res
     }
 
+    fn get_appearance(&self) -> Appearance {
+        match promise::spawn::block_on(crate::os::xdg_desktop_portal::get_appearance()) {
+            Ok(Some(appearance)) => return appearance,
+            Ok(None) => {}
+            Err(err) => {
+                log::warn!("Unable to resolve appearance using xdg-desktop-portal: {err:#}");
+            }
+        }
+        // fallback
+        Appearance::Light
+    }
+
     fn screens(&self) -> anyhow::Result<crate::screen::Screens> {
         log::trace!("Getting screens for wayland connection");
 
diff --git a/window/src/os/wayland/window.rs b/window/src/os/wayland/window.rs
index e6cd1d4f630..cb3fa44662e 100644
--- a/window/src/os/wayland/window.rs
+++ b/window/src/os/wayland/window.rs
@@ -44,6 +44,7 @@ use wezterm_input_types::{
 use crate::wayland::WaylandConnection;
 use crate::x11::KeyboardWithFallback;
 use crate::{
+    Appearance,
     Clipboard, Connection, ConnectionOps, Dimensions, MouseCursor, Point, Rect,
     RequestedWindowGeometry, ResizeIncrement, ResolvedGeometry, Window, WindowEvent,
     WindowEventSender, WindowKeyEvent, WindowOps, WindowState,
@@ -269,6 +270,8 @@ impl WaylandWindow {
             surface_to_pending.insert(surface.id(), Arc::clone(&pending_mouse));
         }
 
+        let appearance = conn.get_appearance();
+
         let inner = Rc::new(RefCell::new(WaylandWindowInner {
             events: WindowEventSender::new(event_handler),
             surface_factor: 1.0,
@@ -296,6 +299,7 @@ impl WaylandWindow {
             frame_callback: None,
 
             text_cursor: None,
+            appearance,
 
             config,
 
@@ -520,7 +524,7 @@ pub struct WaylandWindowInner {
     invalidated: bool,
     // font_config: Rc<FontConfiguration>,
     text_cursor: Option<Rect>,
-    // appearance: Appearance,
+    appearance: Appearance,
     config: ConfigHandle,
     // cache the title for comparison to avoid spamming
     // the compositor with updates that don't actually change it
@@ -1062,6 +1066,14 @@ impl WaylandWindowInner {
         self.text_cursor.take();
     }
 
+    pub(crate) fn appearance_changed(&mut self, appearance: Appearance) {
+        if appearance != self.appearance {
+            self.appearance = appearance;
+            self.events
+                .dispatch(WindowEvent::AppearanceChanged(appearance));
+        }
+    }
+
     pub(super) fn keyboard_event(
         &mut self,
         mapper: &mut KeyboardWithFallback,


Test patch: