rounded panes, cwd fix mac
This commit is contained in:
@@ -1 +1 @@
|
|||||||
/nix/store/6vj9g5xzxd0whdnxd0zsg02m1rwsrlrn-slerm-0.1.0
|
/nix/store/3b7xvjwsg42myksf85lyj1x89269p0sb-slerm-0.1.0
|
||||||
+18
-3
@@ -6,7 +6,7 @@ use crate::{
|
|||||||
render::color,
|
render::color,
|
||||||
};
|
};
|
||||||
use anyhow::{Result, ensure};
|
use anyhow::{Result, ensure};
|
||||||
use egui::{Color32, Rect, Stroke, vec2};
|
use egui::{Color32, CornerRadius, Rect, Stroke, vec2};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashSet},
|
collections::{BTreeMap, HashSet},
|
||||||
path::PathBuf,
|
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(
|
fn show_pane(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
id: PaneId,
|
id: PaneId,
|
||||||
@@ -553,9 +556,21 @@ fn show_pane(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
let accent = color(config::rgb(&config.accent).unwrap());
|
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())));
|
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(
|
ui.painter().rect_filled(
|
||||||
title_rect,
|
title_rect,
|
||||||
0,
|
CornerRadius {
|
||||||
|
nw: CORNER,
|
||||||
|
ne: CORNER,
|
||||||
|
sw: bottom,
|
||||||
|
se: bottom,
|
||||||
|
},
|
||||||
Color32::from_gray(if active { 43 } else { 31 }),
|
Color32::from_gray(if active { 43 } else { 31 }),
|
||||||
);
|
);
|
||||||
ui.painter()
|
ui.painter()
|
||||||
@@ -585,7 +600,7 @@ fn show_pane(
|
|||||||
}
|
}
|
||||||
ui.painter().rect_stroke(
|
ui.painter().rect_stroke(
|
||||||
rect,
|
rect,
|
||||||
0,
|
CORNER,
|
||||||
Stroke::new(
|
Stroke::new(
|
||||||
1.0_f32,
|
1.0_f32,
|
||||||
if active {
|
if active {
|
||||||
|
|||||||
+18
-2
@@ -46,13 +46,29 @@ fn local_pwd(pwd: &str) -> Option<PathBuf> {
|
|||||||
return pwd.starts_with('/').then(|| PathBuf::from(pwd));
|
return pwd.starts_with('/').then(|| PathBuf::from(pwd));
|
||||||
};
|
};
|
||||||
let (host, path) = rest.split_once('/')?;
|
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()
|
let local = host.is_empty()
|
||||||
|| host.eq_ignore_ascii_case("localhost")
|
|| host.eq_ignore_ascii_case("localhost")
|
||||||
|| std::fs::read_to_string("/proc/sys/kernel/hostname")
|
|| hostname().is_some_and(|name| short(&name) == short(host));
|
||||||
.is_ok_and(|name| name.trim().eq_ignore_ascii_case(host));
|
|
||||||
local.then(|| PathBuf::from(percent_decode(&format!("/{path}"))))
|
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 {
|
fn percent_decode(path: &str) -> String {
|
||||||
let mut out = Vec::with_capacity(path.len());
|
let mut out = Vec::with_capacity(path.len());
|
||||||
let mut bytes = path.bytes();
|
let mut bytes = path.bytes();
|
||||||
|
|||||||
+69
-4
@@ -111,11 +111,10 @@ impl Pty {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The working directory of the foreground process group, via /proc. Shells
|
/// The working directory of the foreground process group. Shells that emit
|
||||||
/// that emit OSC 7 are reported by the terminal instead; this covers the rest.
|
/// OSC 7 are reported by the terminal instead; this covers the rest.
|
||||||
pub fn cwd(&self) -> Option<PathBuf> {
|
pub fn cwd(&self) -> Option<PathBuf> {
|
||||||
let pid = self.master.process_group_leader()?;
|
process_cwd(self.master.process_group_leader()?)
|
||||||
std::fs::read_link(format!("/proc/{pid}/cwd")).ok()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&self, cols: u16, rows: u16, cw: u32, ch: u32) -> Result<()> {
|
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 {
|
impl Drop for Pty {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let _ = self.shutdown.send(());
|
let _ = self.shutdown.send(());
|
||||||
|
|||||||
Reference in New Issue
Block a user