diff --git a/codec/src/lib.rs b/codec/src/lib.rs index 0f6658fb5..c035243e3 100644 --- a/codec/src/lib.rs +++ b/codec/src/lib.rs @@ -431,7 +431,7 @@ macro_rules! pdu { /// The overall version of the codec. /// This must be bumped when backwards incompatible changes /// are made to the types and protocol. -pub const CODEC_VERSION: usize = 40; +pub const CODEC_VERSION: usize = 41; // Defines the Pdu enum. // Each struct has an explicit identifying number. diff --git a/docs/changelog.md b/docs/changelog.md index 73a172001..f14fefaf8 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -64,6 +64,9 @@ As features stabilize some brief notes about them will accumulate here. * [wezterm cli rename-workspace](cli/cli/rename-workspace.md). #2787 * [wezterm.mux.rename_workspace](config/lua/wezterm.mux/rename_workspace.md). #2787 * [wezterm cli get-pane-direction](cli/cli/get-pane-direction.md) +* [pane:get_tty_name()](config/lua/pane/get_tty_name.md) and + [PaneInformation.tty_name](config/lua/PaneInformation.md) to reason about the + tty name on local unix systems. #### Fixed * mux: Stale remote window mapping could prevent spawning new tabs in remote domain. #2759 diff --git a/docs/config/lua/PaneInformation.md b/docs/config/lua/PaneInformation.md index 41e0f2604..34c78a37b 100644 --- a/docs/config/lua/PaneInformation.md +++ b/docs/config/lua/PaneInformation.md @@ -126,3 +126,7 @@ end) return config ``` +{{since('nightly')}} + +The `tty_name` field returns the tty name with the same constraints as described +in [pane:get_tty_name()](../pane/get_tty_name.md). diff --git a/docs/config/lua/pane/get_tty_name.md b/docs/config/lua/pane/get_tty_name.md new file mode 100644 index 000000000..0c705bd58 --- /dev/null +++ b/docs/config/lua/pane/get_tty_name.md @@ -0,0 +1,27 @@ +# `pane:get_tty_name()` + +{{since('nightly')}} + +Returns the tty device name, or `nil` if the name is unavailable. + +* This information is only available for local panes. Multiplexer panes do not report this information. Similarly, if you are using eg: `ssh` to connect to a remote host, you won't be able to access the name of the remote process that is running. +* This information is only available on unix systems. Windows systems do not have an equivalent concept. + +This example sets the right status to show the tty name: + +```lua +local wezterm = require 'wezterm' + +wezterm.on('update-status', function(window, pane) + local tty = pane:get_tty_name() + if tty then + window:set_right_status(tty) + else + window:set_right_status '' + end +end) + +return {} +``` + + diff --git a/lua-api-crates/mux/src/pane.rs b/lua-api-crates/mux/src/pane.rs index 547e0bca8..f0d919e16 100644 --- a/lua-api-crates/mux/src/pane.rs +++ b/lua-api-crates/mux/src/pane.rs @@ -403,6 +403,12 @@ impl UserData for MuxPane { tab.set_active_pane(&pane); Ok(()) }); + + methods.add_method("get_tty_name", move |_lua, this, ()| { + let mux = Mux::get(); + let pane = this.resolve(&mux)?; + Ok(pane.tty_name()) + }); } } diff --git a/mux/src/localpane.rs b/mux/src/localpane.rs index 8bcab020a..7620a4ca2 100644 --- a/mux/src/localpane.rs +++ b/mux/src/localpane.rs @@ -457,6 +457,11 @@ impl Pane for LocalPane { .or_else(|| self.divine_current_working_dir()) } + fn tty_name(&self) -> Option { + let name = self.pty.lock().tty_name()?; + Some(name.to_string_lossy().into_owned()) + } + fn get_foreground_process_info(&self) -> Option { #[cfg(unix)] if let Some(pid) = self.pty.lock().process_group_leader() { diff --git a/mux/src/pane.rs b/mux/src/pane.rs index fd39dab58..5657f60a1 100644 --- a/mux/src/pane.rs +++ b/mux/src/pane.rs @@ -345,6 +345,10 @@ pub trait Pane: Downcast + Send + Sync { None } + fn tty_name(&self) -> Option { + None + } + fn trickle_paste(&self, text: String) -> anyhow::Result<()> { if text.len() <= PASTE_CHUNK_SIZE { // Send it all now diff --git a/mux/src/ssh.rs b/mux/src/ssh.rs index 14dbc8afc..b1385a484 100644 --- a/mux/src/ssh.rs +++ b/mux/src/ssh.rs @@ -996,6 +996,11 @@ impl portable_pty::MasterPty for WrappedSshPty { fn as_raw_fd(&self) -> Option { None } + + #[cfg(unix)] + fn tty_name(&self) -> Option { + None + } } impl std::io::Write for PtyWriter { diff --git a/mux/src/tab.rs b/mux/src/tab.rs index 22d581735..1d9163182 100644 --- a/mux/src/tab.rs +++ b/mux/src/tab.rs @@ -277,6 +277,7 @@ fn pane_tree( physical_top: dims.physical_top, left_col, top_row, + tty_name: pane.tty_name(), }) } } @@ -2110,6 +2111,7 @@ pub struct PaneEntry { pub physical_top: StableRowIndex, pub top_row: usize, pub left_col: usize, + pub tty_name: Option, } #[derive(Deserialize, Clone, Serialize, PartialEq, Debug)] diff --git a/mux/src/tmux_pty.rs b/mux/src/tmux_pty.rs index d4525641e..91f075d3d 100644 --- a/mux/src/tmux_pty.rs +++ b/mux/src/tmux_pty.rs @@ -161,4 +161,9 @@ impl MasterPty for TmuxPty { fn as_raw_fd(&self) -> Option { None } + + #[cfg(unix)] + fn tty_name(&self) -> Option { + None + } } diff --git a/pty/src/lib.rs b/pty/src/lib.rs index 6d1136f71..61934c6f4 100644 --- a/pty/src/lib.rs +++ b/pty/src/lib.rs @@ -113,6 +113,9 @@ pub trait MasterPty { #[cfg(unix)] fn as_raw_fd(&self) -> Option; + #[cfg(unix)] + fn tty_name(&self) -> Option; + /// If applicable to the type of the tty, return the termios /// associated with the stream #[cfg(unix)] diff --git a/pty/src/serial.rs b/pty/src/serial.rs index 63ad7e298..a525af0c6 100644 --- a/pty/src/serial.rs +++ b/pty/src/serial.rs @@ -17,6 +17,7 @@ use serial::{ use std::cell::RefCell; use std::ffi::{OsStr, OsString}; use std::io::{Read, Result as IoResult, Write}; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -245,6 +246,11 @@ impl MasterPty for Master { fn as_raw_fd(&self) -> Option { None } + + #[cfg(unix)] + fn tty_name(&self) -> Option { + None + } } struct Reader { diff --git a/pty/src/unix.rs b/pty/src/unix.rs index 094e07a3b..cbe0f76fb 100644 --- a/pty/src/unix.rs +++ b/pty/src/unix.rs @@ -5,9 +5,12 @@ use anyhow::{bail, Error}; use filedescriptor::FileDescriptor; use libc::{self, winsize}; use std::cell::RefCell; +use std::ffi::OsStr; use std::io::{Read, Write}; +use std::os::unix::ffi::OsStrExt; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::os::unix::process::CommandExt; +use std::path::PathBuf; use std::{io, mem, ptr}; pub use std::os::unix::io::RawFd; @@ -42,9 +45,12 @@ fn openpty(size: PtySize) -> anyhow::Result<(UnixMasterPty, UnixSlavePty)> { bail!("failed to openpty: {:?}", io::Error::last_os_error()); } + let tty_name = tty_name(slave); + let master = UnixMasterPty { fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(master) }), took_writer: RefCell::new(false), + tty_name, }; let slave = UnixSlavePty { fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(slave) }), @@ -98,6 +104,33 @@ impl Read for PtyFd { } } +fn tty_name(fd: RawFd) -> Option { + let mut buf = vec![0 as std::ffi::c_char; 128]; + + loop { + let res = unsafe { libc::ttyname_r(fd, buf.as_mut_ptr(), buf.len()) }; + + if res == libc::ERANGE { + if buf.len() > 64 * 1024 { + // on macOS, if the buf is "too big", ttyname_r can + // return ERANGE, even though that is supposed to + // indicate buf is "too small". + return None; + } + buf.resize(buf.len() * 2, 0 as std::ffi::c_char); + continue; + } + + return if res == 0 { + let cstr = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) }; + let osstr = OsStr::from_bytes(cstr.to_bytes()); + Some(PathBuf::from(osstr)) + } else { + None + }; + } +} + /// On Big Sur, Cocoa leaks various file descriptors to child processes, /// so we need to make a pass through the open descriptors beyond just the /// stdio descriptors and close them all out. @@ -269,6 +302,7 @@ impl PtyFd { struct UnixMasterPty { fd: PtyFd, took_writer: RefCell, + tty_name: Option, } /// Represents the slave end of a pty. @@ -332,6 +366,10 @@ impl MasterPty for UnixMasterPty { Some(self.fd.0.as_raw_fd()) } + fn tty_name(&self) -> Option { + self.tty_name.clone() + } + fn process_group_leader(&self) -> Option { match unsafe { libc::tcgetpgrp(self.fd.0.as_raw_fd()) } { pid if pid > 0 => Some(pid), diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index 7cd525bb8..e6298b582 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -289,6 +289,15 @@ impl UserData for PaneInformation { None => Ok("".to_string()), } }); + fields.add_field_method_get("tty_name", |_, this| { + let mut name = None; + if let Some(mux) = Mux::try_get() { + if let Some(pane) = mux.get_pane(this.pane_id) { + name = pane.tty_name(); + } + } + Ok(name) + }); fields.add_field_method_get("current_working_dir", |_, this| { let mut name = None; if let Some(mux) = Mux::try_get() { diff --git a/wezterm-ssh/src/pty.rs b/wezterm-ssh/src/pty.rs index 7bb981c99..9136b29d8 100644 --- a/wezterm-ssh/src/pty.rs +++ b/wezterm-ssh/src/pty.rs @@ -82,6 +82,11 @@ impl portable_pty::MasterPty for SshPty { fn as_raw_fd(&self) -> Option { None } + + #[cfg(unix)] + fn tty_name(&self) -> Option { + None + } } #[derive(Debug)] diff --git a/wezterm/src/cli/list.rs b/wezterm/src/cli/list.rs index b7f422d1a..35ab4fc21 100644 --- a/wezterm/src/cli/list.rs +++ b/wezterm/src/cli/list.rs @@ -138,6 +138,7 @@ struct CliListResultItem { window_title: String, is_active: bool, is_zoomed: bool, + tty_name: Option, } impl CliListResultItem { @@ -155,6 +156,7 @@ impl CliListResultItem { top_row, is_active_pane, is_zoomed_pane, + tty_name, size: TerminalSize { rows, @@ -194,6 +196,7 @@ impl CliListResultItem { window_title: window_title.to_string(), is_active: is_active_pane, is_zoomed: is_zoomed_pane, + tty_name, } } }