Ports the parts of Ghostty's IO layer that don't depend on the terminal: - pty.zig: pty pair, window size, mode, child-side setsid/TIOCSCTTY; master is CLOEXEC with IUTF8 set - Command.zig: fork/execvpe with Ghostty's exact child setup (stdio dup2, best-effort chdir, rlimit restore, os/runtime pre-exec hooks, cgroup handling) and exit status decoding - shell_integration.zig for bash/zsh/fish/elvish/nushell plus feature flags; scripts copied verbatim to resources/shell-integration/ - terminfo generator for xterm-ghostty, byte-identical to Zig's encode output (golden file produced with the real Zig code) This crate opts into unsafe_code = "deny" (fork/exec need it) with allow + SAFETY comments on minimal items. Integration left: config-derived option types are local stand-ins; cgroup D-Bus scope creation is a closure hook; Exec/Termio/Thread/ stream_handler wait for ghostty-terminal. Pty now closes both fds on drop (Zig never auto-closed the slave) - to re-check when wiring Exec. Test Plan: - cargo test -p ghostty-termio (52 tests, incl. real pty spawn test expecting "hi\r\n31 97\r\n" from `echo hi; stty size`)
111 lines
3.4 KiB
Nu
111 lines
3.4 KiB
Nu
# Ghostty shell integration
|
|
export module ghostty {
|
|
def has_feature [feature: string] {
|
|
$feature in ($env.GHOSTTY_SHELL_FEATURES | default "" | split row ',')
|
|
}
|
|
|
|
# Wrap `ssh` with Ghostty TERMINFO support
|
|
export def --wrapped ssh [...args] {
|
|
mut ssh_env = {}
|
|
mut ssh_opts = []
|
|
|
|
# `ssh-env`: use xterm-256color and propagate COLORTERM/TERM_PROGRAM vars
|
|
if (has_feature "ssh-env") {
|
|
$ssh_env.TERM = "xterm-256color"
|
|
$ssh_opts = [
|
|
"-o" "SetEnv COLORTERM=truecolor"
|
|
"-o" "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION"
|
|
]
|
|
}
|
|
|
|
# `ssh-terminfo`: auto-install xterm-ghostty terminfo on remote hosts
|
|
if (has_feature "ssh-terminfo") {
|
|
let ghostty = ($env.GHOSTTY_BIN_DIR? | default "") | path join "ghostty"
|
|
|
|
let ssh_cfg = ^ssh -G ...$args
|
|
| lines
|
|
| parse "{key} {value}"
|
|
| where key in ["user" "hostname"]
|
|
| select key value
|
|
| transpose -rd
|
|
| default {user: $env.USER hostname: "localhost"}
|
|
let ssh_id = $"($ssh_cfg.user)@($ssh_cfg.hostname)"
|
|
|
|
if (^$ghostty "+ssh-cache" $"--host=($ssh_id)" | complete | $in.exit_code == 0) {
|
|
$ssh_env.TERM = "xterm-ghostty"
|
|
} else {
|
|
$ssh_env.TERM = "xterm-256color"
|
|
|
|
let terminfo = try {
|
|
^infocmp -0 -x xterm-ghostty
|
|
} catch {
|
|
print -e "infocmp failed, using xterm-256color"
|
|
}
|
|
|
|
if ($terminfo | is-not-empty) {
|
|
print $"Setting up xterm-ghostty terminfo on ($ssh_cfg.hostname)..."
|
|
|
|
let ctrl_path = (
|
|
mktemp -td $"ghostty-ssh-($ssh_cfg.user).XXXXXX"
|
|
| path join "socket"
|
|
)
|
|
|
|
let remote_args = $ssh_opts ++ [
|
|
"-o" "ControlMaster=yes"
|
|
"-o" $"ControlPath=($ctrl_path)"
|
|
"-o" "ControlPersist=60s"
|
|
] ++ $args
|
|
|
|
$terminfo | ^ssh ...$remote_args '
|
|
infocmp xterm-ghostty >/dev/null 2>&1 && exit 0
|
|
command -v tic >/dev/null 2>&1 || exit 1
|
|
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
|
|
exit 1'
|
|
| complete
|
|
| if $in.exit_code == 0 {
|
|
^$ghostty "+ssh-cache" $"--add=($ssh_id)" e>| print -e
|
|
$ssh_env.TERM = "xterm-ghostty"
|
|
$ssh_opts = ($ssh_opts ++ ["-o" $"ControlPath=($ctrl_path)"])
|
|
} else {
|
|
print -e "terminfo install failed, using xterm-256color"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let ssh_args = $ssh_opts ++ $args
|
|
with-env $ssh_env {
|
|
^ssh ...$ssh_args
|
|
}
|
|
}
|
|
|
|
# Wrap `sudo` to preserve Ghostty's TERMINFO environment variable
|
|
export def --wrapped sudo [...args] {
|
|
mut sudo_args = $args
|
|
|
|
if (has_feature "sudo") {
|
|
# Extract just the sudo options (before the command)
|
|
let sudo_options = (
|
|
$args | take until {|arg|
|
|
not (($arg | str starts-with "-") or ($arg | str contains "="))
|
|
}
|
|
)
|
|
|
|
# Prepend TERMINFO preservation flag if not using sudoedit
|
|
if (not ("-e" in $sudo_options or "--edit" in $sudo_options)) {
|
|
$sudo_args = ($args | prepend "--preserve-env=TERMINFO")
|
|
}
|
|
}
|
|
|
|
^sudo ...$sudo_args
|
|
}
|
|
}
|
|
|
|
# Clean up XDG_DATA_DIRS by removing GHOSTTY_SHELL_INTEGRATION_XDG_DIR
|
|
if 'GHOSTTY_SHELL_INTEGRATION_XDG_DIR' in $env {
|
|
if 'XDG_DATA_DIRS' in $env {
|
|
$env.XDG_DATA_DIRS = ($env.XDG_DATA_DIRS | str replace $"($env.GHOSTTY_SHELL_INTEGRATION_XDG_DIR):" "")
|
|
}
|
|
hide-env GHOSTTY_SHELL_INTEGRATION_XDG_DIR
|
|
}
|