diff --git a/src/config.rs b/src/config.rs index 4f307f22..3943f95e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1977,6 +1977,7 @@ mod tests { "wezterm", "tmux", "alacritty", + "ptyxis", "terminal.app", "iterm", ] { diff --git a/src/instance_binding.rs b/src/instance_binding.rs index 8a9dd823..0b2be5d9 100644 --- a/src/instance_binding.rs +++ b/src/instance_binding.rs @@ -163,6 +163,8 @@ fn capture_context() -> serde_json::Map { "KITTY_LISTEN_ON", "ALACRITTY_WINDOW_ID", "WEZTERM_PANE", + "PTYXIS_PROFILE", + "PTYXIS_VERSION", "GNOME_TERMINAL_SCREEN", "KONSOLE_DBUS_WINDOW", "TERMINATOR_UUID", diff --git a/src/shared/terminal_presets.rs b/src/shared/terminal_presets.rs index dbb33040..0da9cf64 100644 --- a/src/shared/terminal_presets.rs +++ b/src/shared/terminal_presets.rs @@ -292,6 +292,17 @@ pub static TERMINAL_PRESETS: LazyLock> = Laz &["Linux"], ), ), + ( + "ptyxis", + p( + Some("ptyxis"), + None, + argv(&["ptyxis", "--new-window", "--", "bash", "{script}"]), + NONE_ARGV, + None, + &["Linux"], + ), + ), ( "konsole", p( @@ -617,6 +628,7 @@ pub const TERMINAL_ENV_MAP: &[(&str, &str)] = &[ ("GHOSTTY_RESOURCES_DIR", "ghostty"), ("ITERM_SESSION_ID", "iterm"), ("ALACRITTY_WINDOW_ID", "alacritty"), + ("PTYXIS_VERSION", "ptyxis"), ("GNOME_TERMINAL_SCREEN", "gnome-terminal"), ("KONSOLE_DBUS_WINDOW", "konsole"), ("TERMINATOR_UUID", "terminator"), @@ -630,7 +642,7 @@ mod tests { #[test] fn test_terminal_presets_count() { - assert_eq!(TERMINAL_PRESETS.len(), 28); + assert_eq!(TERMINAL_PRESETS.len(), 29); } #[test] @@ -683,4 +695,16 @@ mod tests { assert!(win.contains(&"powershell")); assert_eq!(win.first(), Some(&"mintty")); } + + #[test] + fn test_ptyxis_opens_a_new_window() { + let preset = get_terminal_preset("ptyxis").unwrap(); + assert_eq!(preset.binary, Some("ptyxis")); + assert_eq!( + preset.open.select(false), + Some(&["ptyxis", "--new-window", "--", "bash", "{script}"] as ArgvTemplate) + ); + assert!(preset.close.select(false).is_none()); + assert!(preset.platforms.contains(&"Linux")); + } } diff --git a/src/terminal.rs b/src/terminal.rs index d0b29e40..c281f36f 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -139,6 +139,8 @@ pub(crate) const TERMINAL_CONTEXT_VARS: &[&str] = &[ "GHOSTTY_RESOURCES_DIR", "ITERM_SESSION_ID", "ALACRITTY_WINDOW_ID", + "PTYXIS_PROFILE", + "PTYXIS_VERSION", "GNOME_TERMINAL_SCREEN", "KONSOLE_DBUS_WINDOW", "TERMINATOR_UUID", @@ -368,6 +370,16 @@ fn normalize_terminal_mode_for_launch( if opens_new_window { if terminal_mode == "default" && let Some(detected) = detect_terminal_from_env() + // Ptyxis is commonly installed as a Flatpak. Such shells export + // PTYXIS_VERSION even when no host-side `ptyxis` launcher exists, + // so environment detection alone is not enough to launch a new + // window. Keep `default` in that case and use the normal Linux + // fallback selection below. A configured `ptyxis.binary` override + // (for example, a wrapper around `flatpak run`) is honored. + && (detected != "ptyxis" + || crate::config::get_merged_preset("ptyxis") + .and_then(|preset| preset.binary) + .is_some_and(|binary| which_bin(&binary).is_some())) { terminal_mode = detected; } @@ -1511,6 +1523,8 @@ pub fn get_default_fallback_terminal_name() -> &'static str { } } else if which_bin("gnome-terminal").is_some() { "gnome-terminal" + } else if which_bin("ptyxis").is_some() { + "ptyxis" } else if which_bin("konsole").is_some() { "konsole" } else if which_bin("xterm").is_some() { @@ -1531,6 +1545,10 @@ fn get_linux_terminal_argv() -> Option> { "gnome-terminal", &["gnome-terminal", "--", "bash", "{script}"] as &[&str], ), + ( + "ptyxis", + &["ptyxis", "--new-window", "--", "bash", "{script}"], + ), ("konsole", &["konsole", "-e", "bash", "{script}"]), ("xterm", &["xterm", "-e", "bash", "{script}"]), ]; @@ -1624,6 +1642,7 @@ fn is_external_terminal_launcher(argv: &[String]) -> bool { | "ttab" | "wttab" | "gnome-terminal" + | "ptyxis" | "konsole" | "xterm" | "tilix" @@ -3181,6 +3200,49 @@ mod tests { assert!(auto); } + #[test] + #[serial] + fn test_detect_terminal_from_ptyxis_version() { + let _env = EnvGuard::clear(TERMINAL_CONTEXT_VARS); + let _detect = EnvGuard::clear(DETECT_ONLY_VARS); + unsafe { + std::env::set_var("PTYXIS_VERSION", "50.1"); + } + + assert_eq!(detect_terminal_from_env().as_deref(), Some("ptyxis")); + } + + #[test] + #[serial] + fn test_auto_detected_ptyxis_requires_host_launcher_for_new_window() { + let _env = EnvGuard::clear(TERMINAL_CONTEXT_VARS); + let _detect = EnvGuard::clear(DETECT_ONLY_VARS); + let _path = EnvGuard::clear(&["PATH"]); + let empty_path = tempfile::tempdir().unwrap(); + unsafe { + std::env::set_var("PTYXIS_VERSION", "50.1"); + std::env::set_var("PATH", empty_path.path()); + } + + let (mode, socket) = normalize_terminal_mode_for_launch("default".to_string(), true, false); + + assert_eq!(mode, "default"); + assert!(socket.is_empty()); + } + + #[test] + fn test_launcher_env_strips_ptyxis_identity() { + let env = get_launcher_env_from(vec![ + ("PTYXIS_PROFILE".into(), "profile-id".into()), + ("PTYXIS_VERSION".into(), "50.1".into()), + ("PATH".into(), "/bin".into()), + ]); + + assert!(!env.contains_key("PTYXIS_PROFILE")); + assert!(!env.contains_key("PTYXIS_VERSION")); + assert_eq!(env.get("PATH").map(String::as_str), Some("/bin")); + } + #[test] fn test_splice_kitten_to_socket_matches_absolute_app_bundle_path() { // Regression: when `kitten` isn't on PATH, resolve_terminal_open_argv @@ -3984,6 +4046,8 @@ mod tests { assert!(terminal_preset_supported_on("wttab", "Windows")); assert!(!terminal_preset_supported_on("wttab", "Darwin")); assert!(terminal_preset_supported_on("wezterm", "Windows")); + assert!(terminal_preset_supported_on("ptyxis", "Linux")); + assert!(!terminal_preset_supported_on("ptyxis", "Darwin")); assert!(!terminal_preset_supported_on("nope", "Darwin")); } } diff --git a/src/tui/db.rs b/src/tui/db.rs index b558becd..c0a4d56c 100644 --- a/src/tui/db.rs +++ b/src/tui/db.rs @@ -1295,6 +1295,12 @@ const BUILTIN_PRESETS: &[PresetDef] = &[ app_name: "", platforms: &["Linux"], }, + PresetDef { + name: "ptyxis", + binary: Some("ptyxis"), + app_name: "", + platforms: &["Linux"], + }, PresetDef { name: "konsole", binary: Some("konsole"), diff --git a/tests/test_relay_roundtrip.rs b/tests/test_relay_roundtrip.rs index 77c891e0..0f3d6475 100644 --- a/tests/test_relay_roundtrip.rs +++ b/tests/test_relay_roundtrip.rs @@ -194,6 +194,8 @@ fn hcom_with_dir(cmd: &str, hcom_dir: &str) -> Output { "GHOSTTY_RESOURCES_DIR", "ITERM_SESSION_ID", "ALACRITTY_WINDOW_ID", + "PTYXIS_PROFILE", + "PTYXIS_VERSION", "GNOME_TERMINAL_SCREEN", "KONSOLE_DBUS_WINDOW", "TERMINATOR_UUID",