aboutsummaryrefslogtreecommitdiff
path: root/src/platform/unix_common.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-08-10 01:20:09 +0300
committerGitHub <noreply@github.com>2026-08-10 01:20:09 +0300
commite48d83067a5a9b05dfd0a82761068866ab2d294d (patch)
tree159b92f857f85e40037879c7f32da4957093597b /src/platform/unix_common.rs
parente2aa86a9e8b6445488ef11b2c0a8b4c22a6f79ce (diff)
feat: add configurable tab bar status (#2586)
* feat: show a zoom indicator in the desktop tab bar Reserve the right edge of the tab row for a ZOOM pill while the focused pane is zoomed, matching the accent style of the mode bars. The per-tab Z suffix stays; the pill makes the zoomed state visible at a glance like tmux's status-right flag. * feat: optionally show the hostname in the desktop tab bar Add ui.tab_bar_hostname to display the machine's hostname at the right edge of the tab row, like tmux's #h in status-right. The value resolves where the server renders, so remote sessions show the remote host. Off by default. * fix: strip control characters from the tab bar hostname * fix: hide the hostname when it would squeeze out the tab strip * feat: add configurable tab bar status * fix: harden tab bar status updates * fix: terminate tab bar status process trees * fix: disable status commands on unsupported platforms * fix: skip unchanged status command renders * fix: keep tab bar status opt-in by default --------- Co-authored-by: David Heinemeier Hansson <david@hey.com>
Diffstat (limited to 'src/platform/unix_common.rs')
-rw-r--r--src/platform/unix_common.rs84
1 files changed, 84 insertions, 0 deletions
diff --git a/src/platform/unix_common.rs b/src/platform/unix_common.rs
index 754e2fc2..3004e6da 100644
--- a/src/platform/unix_common.rs
+++ b/src/platform/unix_common.rs
@@ -119,6 +119,90 @@ fn fits_unix_socket_path(path: &Path) -> bool {
path.as_os_str().as_bytes().len() <= 103
}
+/// The machine's node name, as shown by tmux's `#h`.
+pub(crate) fn hostname() -> Option<String> {
+ let mut buffer = [0_u8; 256];
+ let result =
+ unsafe { libc::gethostname(buffer.as_mut_ptr().cast::<libc::c_char>(), buffer.len()) };
+ if result != 0 {
+ return None;
+ }
+ let end = buffer
+ .iter()
+ .position(|&byte| byte == 0)
+ .unwrap_or(buffer.len());
+ let name = String::from_utf8_lossy(&buffer[..end]).into_owned();
+ (!name.is_empty()).then_some(name)
+}
+
+pub(crate) fn local_datetime() -> Option<time::PrimitiveDateTime> {
+ let mut timestamp: libc::time_t = 0;
+ if unsafe { libc::time(&mut timestamp) } == -1 {
+ return None;
+ }
+ let mut local: libc::tm = unsafe { std::mem::zeroed() };
+ if unsafe { libc::localtime_r(&timestamp, &mut local) }.is_null() {
+ return None;
+ }
+ datetime_from_tm(&local)
+}
+
+pub(crate) fn status_commands_supported() -> bool {
+ true
+}
+
+pub(crate) fn configure_status_command(process: &mut std::process::Command) {
+ use std::os::unix::process::CommandExt;
+
+ process.process_group(0);
+}
+
+pub(crate) struct StatusCommandGuard {
+ process_group_id: Option<i32>,
+}
+
+impl StatusCommandGuard {
+ pub(crate) fn new(child: &tokio::process::Child) -> std::io::Result<Self> {
+ let process_id = child
+ .id()
+ .ok_or_else(|| std::io::Error::other("status command has no process id"))?;
+ let process_group_id = i32::try_from(process_id)
+ .map_err(|_| std::io::Error::other("status command process id exceeds i32"))?;
+ Ok(Self {
+ process_group_id: Some(process_group_id),
+ })
+ }
+}
+
+impl Drop for StatusCommandGuard {
+ fn drop(&mut self) {
+ if let Some(process_group_id) = self.process_group_id.take() {
+ // The command was spawned as this process group's leader. Killing the
+ // group also cleans up background descendants on completion/cancellation.
+ unsafe {
+ libc::kill(-process_group_id, libc::SIGKILL);
+ }
+ }
+ }
+}
+
+fn datetime_from_tm(value: &libc::tm) -> Option<time::PrimitiveDateTime> {
+ let month = time::Month::try_from(u8::try_from(value.tm_mon + 1).ok()?).ok()?;
+ let date = time::Date::from_calendar_date(
+ value.tm_year + 1900,
+ month,
+ u8::try_from(value.tm_mday).ok()?,
+ )
+ .ok()?;
+ let time = time::Time::from_hms(
+ u8::try_from(value.tm_hour).ok()?,
+ u8::try_from(value.tm_min).ok()?,
+ u8::try_from(value.tm_sec).ok()?,
+ )
+ .ok()?;
+ Some(time::PrimitiveDateTime::new(date, time))
+}
+
#[cfg(test)]
mod tests {
use super::*;