diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-18 02:52:35 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-18 02:53:31 +0300 |
| commit | 36b4001f4526b60f1c9b67a4a34596e5ad2bcca1 (patch) | |
| tree | 520ab28a0a3f3f798e6e655fc83529e8617720e9 /src/server | |
| parent | aca35ea76f3df15ec68fd07959d939f5e7a78a6c (diff) | |
feat: sync theme with host appearance
refs #675
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/clients.rs | 48 | ||||
| -rw-r--r-- | src/server/headless.rs | 154 |
2 files changed, 193 insertions, 9 deletions
diff --git a/src/server/clients.rs b/src/server/clients.rs index 611d87eb..eb823cfc 100644 --- a/src/server/clients.rs +++ b/src/server/clients.rs @@ -33,6 +33,10 @@ pub(crate) struct ClientConnection { pub(crate) cell_size: crate::kitty_graphics::HostCellSize, /// Last known host terminal default colors for this client. pub(crate) host_terminal_theme: crate::terminal_theme::TerminalTheme, + /// Last known host terminal appearance for this client. + pub(crate) host_terminal_appearance: Option<crate::terminal_theme::HostAppearance>, + /// True when appearance came from an explicit host color-scheme report. + pub(crate) host_terminal_appearance_explicit: bool, /// Last reported focus state for this client's outer terminal. pub(crate) outer_terminal_focus: Option<bool>, /// Stateful parser for app-client input split across transport reads. @@ -98,6 +102,10 @@ impl ClientConnection { keybindings, terminal_size, cell_size, + host_terminal_appearance: host_terminal_theme + .background + .map(crate::terminal_theme::RgbColor::inferred_appearance), + host_terminal_appearance_explicit: false, host_terminal_theme, outer_terminal_focus, raw_input: crate::raw_input::RawInputFramer::default(), @@ -130,17 +138,47 @@ impl ClientConnection { events: &[crate::raw_input::RawInputEvent], ) -> bool { let mut next_theme = self.host_terminal_theme; + let mut changed = false; for event in events { - if let crate::raw_input::RawInputEvent::HostDefaultColor { kind, color } = event { - next_theme = next_theme.with_color(*kind, *color); + match event { + crate::raw_input::RawInputEvent::HostDefaultColor { kind, color } => { + next_theme = next_theme.with_color(*kind, *color); + if matches!(kind, crate::terminal_theme::DefaultColorKind::Background) + && !self.host_terminal_appearance_explicit + { + changed |= + self.set_host_appearance(Some(color.inferred_appearance()), false); + } + } + crate::raw_input::RawInputEvent::HostColorSchemeChanged(appearance) => { + changed |= self.set_host_appearance(Some(*appearance), true); + } + _ => {} } } - if next_theme == self.host_terminal_theme { - return false; + if next_theme != self.host_terminal_theme { + self.host_terminal_theme = next_theme; + changed = true; } + changed + } - self.host_terminal_theme = next_theme; + fn set_host_appearance( + &mut self, + appearance: Option<crate::terminal_theme::HostAppearance>, + explicit: bool, + ) -> bool { + if self.host_terminal_appearance_explicit && !explicit { + return false; + } + if self.host_terminal_appearance == appearance + && self.host_terminal_appearance_explicit == explicit + { + return false; + } + self.host_terminal_appearance = appearance; + self.host_terminal_appearance_explicit = explicit; true } diff --git a/src/server/headless.rs b/src/server/headless.rs index 67a876f9..b162b8fe 100644 --- a/src/server/headless.rs +++ b/src/server/headless.rs @@ -778,6 +778,8 @@ impl HeadlessServer { let terminal_size = client.terminal_size; let outer_terminal_focus = client.outer_terminal_focus; let host_terminal_theme = client.host_terminal_theme; + let host_terminal_appearance = client.host_terminal_appearance; + let host_terminal_appearance_explicit = client.host_terminal_appearance_explicit; let uses_local_keybindings = client.keybindings.is_some(); let keybindings = client .keybindings @@ -792,9 +794,11 @@ impl HeadlessServer { if outer_terminal_focus == Some(true) { self.app.state.mark_active_tab_seen(); } - if !host_terminal_theme.is_empty() { - self.app.set_host_terminal_theme(host_terminal_theme); - } + self.app.set_host_terminal_appearance_state( + host_terminal_appearance, + host_terminal_appearance_explicit, + ); + self.app.set_host_terminal_theme(host_terminal_theme); } #[cfg(unix)] @@ -1229,7 +1233,11 @@ impl HeadlessServer { } if self.foreground_client_id == Some(client_id) { - let changed = self.app.set_host_terminal_theme(client.host_terminal_theme); + let mut changed = self.app.set_host_terminal_appearance_state( + client.host_terminal_appearance, + client.host_terminal_appearance_explicit, + ); + changed |= self.app.set_host_terminal_theme(client.host_terminal_theme); if changed { self.resize_shared_runtime_to_effective_size_before_input(); } @@ -5490,6 +5498,144 @@ next_tab = "" } #[test] + fn foreground_client_without_host_theme_clears_previous_host_theme() { + let mut server = test_headless_server(); + let known_theme = crate::terminal_theme::TerminalTheme { + foreground: Some(crate::terminal_theme::RgbColor { + r: 0x10, + g: 0x20, + b: 0x30, + }), + background: Some(crate::terminal_theme::RgbColor { + r: 0x40, + g: 0x50, + b: 0x60, + }), + }; + server.clients.insert( + 1, + ClientConnection::new( + (120, 40), + crate::kitty_graphics::HostCellSize::default(), + known_theme, + None, + 1, + RenderEncoding::SemanticFrame, + None, + ), + ); + server.clients.insert( + 2, + ClientConnection::new( + (120, 40), + crate::kitty_graphics::HostCellSize::default(), + crate::terminal_theme::TerminalTheme::default(), + None, + 2, + RenderEncoding::SemanticFrame, + None, + ), + ); + + assert!(server.promote_client_to_foreground(1)); + assert_eq!(server.app.state.host_terminal_theme, known_theme); + + assert!(server.promote_client_to_foreground(2)); + assert_eq!( + server.app.state.host_terminal_theme, + crate::terminal_theme::TerminalTheme::default() + ); + } + + #[test] + fn foreground_client_appearance_controls_auto_theme() { + let mut server = test_headless_server(); + server.app.state.theme_runtime.auto_switch = true; + server.app.state.theme_runtime.dark_name = "catppuccin".to_string(); + server.app.state.theme_runtime.light_name = "catppuccin-latte".to_string(); + server.clients.insert( + 1, + ClientConnection::new( + (120, 40), + crate::kitty_graphics::HostCellSize::default(), + crate::terminal_theme::TerminalTheme { + foreground: None, + background: Some(crate::terminal_theme::RgbColor { r: 0, g: 0, b: 0 }), + }, + None, + 1, + RenderEncoding::SemanticFrame, + None, + ), + ); + server.clients.insert( + 2, + ClientConnection::new( + (120, 40), + crate::kitty_graphics::HostCellSize::default(), + crate::terminal_theme::TerminalTheme { + foreground: None, + background: Some(crate::terminal_theme::RgbColor { + r: 255, + g: 255, + b: 255, + }), + }, + None, + 2, + RenderEncoding::SemanticFrame, + None, + ), + ); + + assert!(server.promote_client_to_foreground(1)); + assert_eq!(server.app.state.theme_name, "catppuccin"); + + assert!(server.promote_client_to_foreground(2)); + assert_eq!(server.app.state.theme_name, "catppuccin-latte"); + } + + #[test] + fn color_scheme_change_event_is_inert_on_server() { + let mut server = test_headless_server(); + let initial_theme = crate::terminal_theme::TerminalTheme { + foreground: Some(crate::terminal_theme::RgbColor { + r: 0x10, + g: 0x20, + b: 0x30, + }), + background: Some(crate::terminal_theme::RgbColor { + r: 0x40, + g: 0x50, + b: 0x60, + }), + }; + server.app.state.host_terminal_theme = initial_theme; + server.clients.insert( + 1, + ClientConnection::new( + (120, 40), + crate::kitty_graphics::HostCellSize::default(), + initial_theme, + None, + 1, + RenderEncoding::SemanticFrame, + None, + ), + ); + + let changed = server.handle_server_event(ServerEvent::ClientInput { + client_id: 1, + data: crate::raw_input::GHOSTTY_COLOR_SCHEME_DARK_REPORT.to_vec(), + }); + + assert!(!changed); + assert_eq!(server.foreground_client_id, None); + assert_eq!(server.clients[&1].host_terminal_theme, initial_theme); + assert_eq!(server.app.state.host_terminal_theme, initial_theme); + } + + #[test] fn focus_lost_updates_client_without_promoting_foreground() { let mut server = test_headless_server(); |
