aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-18 02:52:35 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-18 02:53:31 +0300
commit36b4001f4526b60f1c9b67a4a34596e5ad2bcca1 (patch)
tree520ab28a0a3f3f798e6e655fc83529e8617720e9 /src/server
parentaca35ea76f3df15ec68fd07959d939f5e7a78a6c (diff)
feat: sync theme with host appearance
refs #675
Diffstat (limited to 'src/server')
-rw-r--r--src/server/clients.rs48
-rw-r--r--src/server/headless.rs154
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();