rounded panes, cwd fix mac
This commit is contained in:
+18
-3
@@ -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
@@ -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
@@ -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(());
|
||||
|
||||
Reference in New Issue
Block a user