rounded panes, cwd fix mac

This commit is contained in:
Gregor Lohaus
2026-09-08 04:18:01 +02:00
parent d8bfbf0294
commit 349f38b31c
4 changed files with 106 additions and 10 deletions
+1 -1
View File
@@ -1 +1 @@
/nix/store/6vj9g5xzxd0whdnxd0zsg02m1rwsrlrn-slerm-0.1.0
/nix/store/3b7xvjwsg42myksf85lyj1x89269p0sb-slerm-0.1.0
+18 -3
View File
@@ -6,7 +6,7 @@ use crate::{
render::color,
};
use anyhow::{Result, ensure};
use egui::{Color32, Rect, Stroke, vec2};
use egui::{Color32, CornerRadius, Rect, Stroke, vec2};
use std::{
collections::{BTreeMap, HashSet},
path::PathBuf,
@@ -543,6 +543,9 @@ fn grab_keys(ctx: &egui::Context) {
});
}
/// Corner rounding shared by a pane's border and its title bar.
const CORNER: u8 = 5;
fn show_pane(
ui: &mut egui::Ui,
id: PaneId,
@@ -553,9 +556,21 @@ fn show_pane(
) -> bool {
let accent = color(config::rgb(&config.accent).unwrap());
let title_rect = Rect::from_min_size(rect.min, vec2(rect.width(), 25.0_f32.min(rect.height())));
// The title bar only rounds off the top of the pane, unless the pane is so
// short that the bar is all there is of it.
let bottom = if title_rect.height() < rect.height() {
0
} else {
CORNER
};
ui.painter().rect_filled(
title_rect,
0,
CornerRadius {
nw: CORNER,
ne: CORNER,
sw: bottom,
se: bottom,
},
Color32::from_gray(if active { 43 } else { 31 }),
);
ui.painter()
@@ -585,7 +600,7 @@ fn show_pane(
}
ui.painter().rect_stroke(
rect,
0,
CORNER,
Stroke::new(
1.0_f32,
if active {
+18 -2
View File
@@ -46,13 +46,29 @@ fn local_pwd(pwd: &str) -> Option<PathBuf> {
return pwd.starts_with('/').then(|| PathBuf::from(pwd));
};
let (host, path) = rest.split_once('/')?;
// A shell may report either the short name or the FQDN, so compare the leading
// label of each.
let short = |name: &str| name.split('.').next().unwrap_or(name).to_ascii_lowercase();
let local = host.is_empty()
|| host.eq_ignore_ascii_case("localhost")
|| std::fs::read_to_string("/proc/sys/kernel/hostname")
.is_ok_and(|name| name.trim().eq_ignore_ascii_case(host));
|| hostname().is_some_and(|name| short(&name) == short(host));
local.then(|| PathBuf::from(percent_decode(&format!("/{path}"))))
}
fn hostname() -> Option<String> {
unsafe extern "C" {
fn gethostname(name: *mut std::ffi::c_char, len: usize) -> std::ffi::c_int;
}
let mut buf = [0u8; 256];
// SAFETY: at most `len` bytes are written, leaving the final byte of `buf` as
// the terminator even when the name is truncated.
if unsafe { gethostname(buf.as_mut_ptr().cast(), buf.len() - 1) } != 0 {
return None;
}
let end = buf.iter().position(|&b| b == 0)?;
String::from_utf8(buf[..end].to_vec()).ok()
}
fn percent_decode(path: &str) -> String {
let mut out = Vec::with_capacity(path.len());
let mut bytes = path.bytes();
+69 -4
View File
@@ -111,11 +111,10 @@ impl Pty {
})
}
/// The working directory of the foreground process group, via /proc. Shells
/// that emit OSC 7 are reported by the terminal instead; this covers the rest.
/// The working directory of the foreground process group. Shells that emit
/// OSC 7 are reported by the terminal instead; this covers the rest.
pub fn cwd(&self) -> Option<PathBuf> {
let pid = self.master.process_group_leader()?;
std::fs::read_link(format!("/proc/{pid}/cwd")).ok()
process_cwd(self.master.process_group_leader()?)
}
pub fn resize(&self, cols: u16, rows: u16, cw: u32, ch: u32) -> Result<()> {
@@ -129,6 +128,72 @@ impl Pty {
}
}
#[cfg(target_os = "linux")]
fn process_cwd(pid: i32) -> Option<PathBuf> {
std::fs::read_link(format!("/proc/{pid}/cwd")).ok()
}
/// macOS has no /proc, so the current directory comes from `proc_pidinfo`.
#[cfg(target_os = "macos")]
fn process_cwd(pid: i32) -> Option<PathBuf> {
use std::{ffi::CStr, os::unix::ffi::OsStrExt};
// <sys/proc_info.h>. `vinfo_stat` is spelled as its 136 bytes of padding
// because only the path that follows it is of interest here.
#[repr(C)]
struct VnodeInfo {
stat: [u64; 17],
vtype: i32,
pad: i32,
fsid: [i32; 2],
}
#[repr(C)]
struct VnodeInfoPath {
info: VnodeInfo,
path: [std::ffi::c_char; 1024],
}
#[repr(C)]
struct VnodePathInfo {
cdir: VnodeInfoPath,
rdir: VnodeInfoPath,
}
const PROC_PIDVNODEPATHINFO: i32 = 9;
unsafe extern "C" {
fn proc_pidinfo(
pid: i32,
flavor: i32,
arg: u64,
buffer: *mut std::ffi::c_void,
buffersize: i32,
) -> i32;
}
let size = size_of::<VnodePathInfo>();
// SAFETY: the call writes at most `size` bytes into `info`, and reports how
// many it wrote. A short write leaves the path unset, so it is rejected.
unsafe {
let mut info = std::mem::zeroed::<VnodePathInfo>();
let written = proc_pidinfo(
pid,
PROC_PIDVNODEPATHINFO,
0,
(&raw mut info).cast(),
size as i32,
);
if written < size as i32 {
return None;
}
let path = CStr::from_ptr(info.cdir.path.as_ptr());
(!path.is_empty()).then(|| PathBuf::from(std::ffi::OsStr::from_bytes(path.to_bytes())))
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn process_cwd(_pid: i32) -> Option<PathBuf> {
None
}
impl Drop for Pty {
fn drop(&mut self) {
let _ = self.shutdown.send(());