aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-09-04 21:59:13 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-09-04 21:59:13 +0300
commita2b128978a154c02311b90da778ca115758f75d7 (patch)
tree429d5ea3f44c91b85ed54138a1b4a6ad7ea1e1a4 /src
parent6045fe6a8735609507ec8600fa07205227546e8b (diff)
feat: enable kitty graphics by default
Diffstat (limited to 'src')
-rw-r--r--src/api/server/pane_graphics_stream.rs2
-rw-r--r--src/app/api/pane_graphics.rs2
-rw-r--r--src/app/mod.rs49
-rw-r--r--src/app/pane_graphics.rs1
-rw-r--r--src/client/mod.rs2
-rw-r--r--src/config.rs7
-rw-r--r--src/config/model.rs48
-rw-r--r--src/main.rs6
-rw-r--r--src/server/headless/tests/pane_graphics.rs1
9 files changed, 93 insertions, 25 deletions
diff --git a/src/api/server/pane_graphics_stream.rs b/src/api/server/pane_graphics_stream.rs
index b6420f95..e4d3315d 100644
--- a/src/api/server/pane_graphics_stream.rs
+++ b/src/api/server/pane_graphics_stream.rs
@@ -798,7 +798,7 @@ mod tests {
.send(super::super::error_response_json(
open.request.id,
"feature_disabled",
- "pane graphics require experimental.kitty_graphics".into(),
+ "pane graphics are disabled by terminal.kitty_graphics".into(),
))
.unwrap();
diff --git a/src/app/api/pane_graphics.rs b/src/app/api/pane_graphics.rs
index 3dddccc9..649282b5 100644
--- a/src/app/api/pane_graphics.rs
+++ b/src/app/api/pane_graphics.rs
@@ -536,7 +536,7 @@ fn require_enabled(app: &App, id: &str) -> Result<(), String> {
encode_error(
id.to_owned(),
"feature_disabled",
- "pane graphics require experimental.kitty_graphics",
+ "pane graphics are disabled by terminal.kitty_graphics",
)
})
}
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 02175481..ed2c5fd9 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -359,7 +359,7 @@ impl App {
event_hub: crate::api::EventHub,
) -> Self {
let (prefix_code, prefix_mods) = config.prefix_key();
- crate::kitty_graphics::set_enabled(config.experimental.kitty_graphics);
+ crate::kitty_graphics::set_enabled(config.kitty_graphics_enabled());
let (event_tx, event_rx) = mpsc::channel::<AppEvent>(APP_EVENT_CHANNEL_CAPACITY);
let render_notify = Arc::new(Notify::new());
let render_dirty = Arc::new(crate::render_signal::RenderSignal::new());
@@ -494,7 +494,7 @@ impl App {
cjk_ime_agent_filter_configured: !config.experimental.cjk_ime_agents.is_empty(),
cjk_ime_agents: parse_cjk_ime_agents(&config.experimental.cjk_ime_agents),
cjk_ime_cursor_shape: config.experimental.cjk_ime_cursor_shape.to_decscusr(),
- kitty_graphics_enabled: config.experimental.kitty_graphics,
+ kitty_graphics_enabled: config.kitty_graphics_enabled(),
default_shell: config.terminal.default_shell.clone(),
shell_mode: config.terminal.shell_mode,
new_terminal_cwd: config.terminal.new_cwd.clone(),
@@ -841,14 +841,18 @@ impl App {
}
}
+ let graphics_config_valid = !invalid_section("terminal")
+ && (config.terminal.kitty_graphics.is_some() || !invalid_section("experimental"));
+ if graphics_config_valid
+ && config.kitty_graphics_enabled() != self.state.kitty_graphics_enabled
+ {
+ diagnostics.push(
+ "terminal.kitty_graphics changes require restarting Herdr; kept current setting"
+ .into(),
+ );
+ }
+
if !invalid_section("experimental") {
- let was_kitty_graphics_enabled = self.state.kitty_graphics_enabled;
- self.state.kitty_graphics_enabled = config.experimental.kitty_graphics;
- crate::kitty_graphics::set_enabled(config.experimental.kitty_graphics);
- if was_kitty_graphics_enabled && !config.experimental.kitty_graphics {
- self.pane_graphics.clear();
- self.state.host_cell_size = crate::kitty_graphics::HostCellSize::default();
- }
self.state.reveal_hidden_cursor_for_cjk_ime =
config.experimental.reveal_hidden_cursor_for_cjk_ime;
self.state.cjk_ime_agent_filter_configured =
@@ -1722,6 +1726,33 @@ mod tests {
}
#[test]
+ fn reload_config_keeps_kitty_graphics_until_restart() {
+ let _guard = config_env_lock().lock().unwrap();
+ let path = temp_config_path("reload-config-kitty-graphics");
+ std::fs::create_dir_all(path.parent().unwrap()).unwrap();
+ std::fs::write(&path, "[terminal]\nkitty_graphics = false\n").unwrap();
+ std::env::set_var(crate::config::CONFIG_PATH_ENV_VAR, &path);
+
+ let mut app = test_app();
+ assert!(app.state.kitty_graphics_enabled);
+
+ let report = app.reload_config();
+
+ assert_eq!(report.status, crate::config::ConfigReloadStatus::Partial);
+ assert!(app.state.kitty_graphics_enabled);
+ assert_eq!(
+ report.diagnostics,
+ vec![
+ "terminal.kitty_graphics changes require restarting Herdr; kept current setting"
+ .to_owned()
+ ]
+ );
+
+ std::env::remove_var(crate::config::CONFIG_PATH_ENV_VAR);
+ let _ = std::fs::remove_dir_all(path.parent().unwrap());
+ }
+
+ #[test]
fn reload_config_requests_client_reload_for_key_only_change() {
let _guard = config_env_lock().lock().unwrap();
let path = temp_config_path("reload-config-key-only");
diff --git a/src/app/pane_graphics.rs b/src/app/pane_graphics.rs
index e6c43fa1..40081cd3 100644
--- a/src/app/pane_graphics.rs
+++ b/src/app/pane_graphics.rs
@@ -108,6 +108,7 @@ impl Runtime {
self.revision = self.revision.wrapping_add(1);
}
+ #[cfg(test)]
pub(crate) fn clear(&mut self) {
self.slots.clear();
self.mark_changed();
diff --git a/src/client/mod.rs b/src/client/mod.rs
index b20a6e85..ea789431 100644
--- a/src/client/mod.rs
+++ b/src/client/mod.rs
@@ -371,7 +371,7 @@ fn run_client_with_mode(
let host_cursor = loaded_config.config.ui.host_cursor;
let remote_image_paste_key = client_remote_image_paste_key(&loaded_config.config);
let kitty_graphics_enabled =
- loaded_config.config.experimental.kitty_graphics && client_rendered_shell;
+ loaded_config.config.kitty_graphics_enabled() && client_rendered_shell;
let pixel_geometry_enabled = kitty_graphics_enabled || attach_escape.is_some();
let loop_config = ClientLoopConfig {
sound_config: loaded_config.config.ui.sound,
diff --git a/src/config.rs b/src/config.rs
index 117374ee..4c966453 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -95,6 +95,13 @@ impl Config {
self.onboarding.unwrap_or(true)
}
+ pub fn kitty_graphics_enabled(&self) -> bool {
+ self.terminal
+ .kitty_graphics
+ .or(self.experimental.kitty_graphics)
+ .unwrap_or(true)
+ }
+
pub fn prefix_key(&self) -> (KeyCode, KeyModifiers) {
self.validated_keybinds().1
}
diff --git a/src/config/model.rs b/src/config/model.rs
index 552c19de..a400382c 100644
--- a/src/config/model.rs
+++ b/src/config/model.rs
@@ -258,6 +258,8 @@ pub struct TerminalConfig {
pub shell_mode: ShellModeConfig,
/// CWD policy for new interactive panes, tabs, and workspaces.
pub new_cwd: NewTerminalCwdConfig,
+ /// Render Kitty graphics in compatible outer terminals. Default: true.
+ pub kitty_graphics: Option<bool>,
}
#[derive(Debug, Deserialize)]
@@ -978,8 +980,8 @@ impl Default for RemoteConfig {
pub struct ExperimentalConfig {
/// Allow launching herdr inside an existing herdr pane. Default: false.
pub allow_nested: bool,
- /// Experimental local Kitty graphics rendering for attached clients. Default: false.
- pub kitty_graphics: bool,
+ /// Deprecated compatibility key for `terminal.kitty_graphics`.
+ pub kitty_graphics: Option<bool>,
/// Persist pane screen history to session-history.json. Default: false.
pub pane_history: bool,
/// Expose the focused pane's cursor anchor to the outer terminal even when
@@ -1911,16 +1913,41 @@ pane_history = true
}
#[test]
- fn kitty_graphics_default_off_and_parse() {
- let config = Config::default();
- assert!(!config.experimental.kitty_graphics);
+ fn kitty_graphics_default_on_with_stable_opt_out() {
+ assert!(Config::default().kitty_graphics_enabled());
+
+ let config: Config = toml::from_str(
+ r#"
+[terminal]
+kitty_graphics = false
+"#,
+ )
+ .unwrap();
+ assert!(!config.kitty_graphics_enabled());
+ }
+
+ #[test]
+ fn legacy_experimental_kitty_graphics_setting_remains_compatible() {
+ let disabled: Config = toml::from_str(
+ r#"
+[experimental]
+kitty_graphics = false
+"#,
+ )
+ .unwrap();
+ assert!(!disabled.kitty_graphics_enabled());
+
+ let stable_setting_wins: Config = toml::from_str(
+ r#"
+[terminal]
+kitty_graphics = false
- let toml = r#"
[experimental]
kitty_graphics = true
-"#;
- let config: Config = toml::from_str(toml).unwrap();
- assert!(config.experimental.kitty_graphics);
+"#,
+ )
+ .unwrap();
+ assert!(!stable_setting_wins.kitty_graphics_enabled());
}
#[test]
@@ -1934,7 +1961,8 @@ switch_ascii_input_source_in_prefix = true
"#;
let config: Config = toml::from_str(toml).unwrap();
assert!(config.experimental.allow_nested);
- assert!(config.experimental.kitty_graphics);
+ assert_eq!(config.experimental.kitty_graphics, Some(true));
+ assert!(config.kitty_graphics_enabled());
assert!(config.experimental.pane_history);
assert!(config.experimental.switch_ascii_input_source_in_prefix);
}
diff --git a/src/main.rs b/src/main.rs
index 91a6b2e4..ee38e84b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -114,6 +114,9 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration
# "current" for Herdr's process directory, or a fixed path such as "~/Projects".
# new_cwd = "follow"
+# Render pane images in Kitty graphics-compatible outer terminals.
+# kitty_graphics = true
+
[update]
# Update channel used by background version checks and `herdr update`.
# Stable builds default to "stable". Windows preview builds default to "preview"
@@ -400,9 +403,6 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration
[experimental]
# Allow launching herdr from inside a herdr-managed pane.
# allow_nested = false
-# Experimental local Kitty graphics rendering for attached clients.
-# Requires a Kitty graphics-compatible outer terminal.
-# kitty_graphics = false
# Save recent pane screen history across full server restarts.
pane_history = false
# While prefix mode is active, temporarily switch the host input source to
diff --git a/src/server/headless/tests/pane_graphics.rs b/src/server/headless/tests/pane_graphics.rs
index 6c0cd8b0..d310003c 100644
--- a/src/server/headless/tests/pane_graphics.rs
+++ b/src/server/headless/tests/pane_graphics.rs
@@ -540,6 +540,7 @@ fn rejected_or_stale_requests_do_not_schedule_rendering() {
server.app.state.workspaces = vec![workspace];
server.app.state.active = Some(0);
server.app.state.selected = 0;
+ server.app.state.kitty_graphics_enabled = false;
let (respond_to, response_rx) = std::sync::mpsc::channel();
let changed = server.handle_api_request_with_shutdown_check(api::ApiRequestMessage {