v2 release prep: installer, README, packaging, notes; retire the v1 scripts
Installer rewritten around a preflight: distro, package manager, display manager, window manager, terminal, tmux, cargo, git, screen locker, touch device, device permissions and free disk are all checked BEFORE anything is installed, and the total cost is printed once for a single confirmation. Prompts read /dev/tty so they still work when the script is piped from curl, and fall back to defaults with a notice when there is no terminal at all. Several "[ test ] && action" statements were set -e landmines: under set -e an AND-OR list that ends up false aborts the script, so a box with no lightdm, no i3 or nothing to install would have exited silently partway through detection -- which is exactly the fresh-Debian case the installer exists for. Rewritten as if-statements and verified against a stripped PATH with no tmux, cargo, git or package manager present. Also fixed cargo detection reporting blank instead of NOT INSTALLED: the status of `cargo --version | cut` is cut's, and cut succeeds on empty input, so the fallback never fired. Device access now defaults to a udev rule matching touchscreens only, rather than the input group, which grants access to every input device including the keyboard and needs a full logout. README rewritten for someone who has not seen the project: what the photo shows, the hardware, install, then a config built up step by step, each step with the YAML and the resulting map. Every example is verified verbatim against the binary, and every relative link resolves. The mechanism and the reasoning move to notes/: DESIGN.md, HARDWARE-NOTES.md, V1-BASH.md, TODO.md. cad/README.md was a verbatim copy of the one inside geeekpi_rack_adapter_release_v1/, so every path in it -- including the screenshot -- was broken from where it sits. Corrected to its own level, and it now states once that the 9-inch screen, the 10-inch mini-rack mount and the 19-inch rack are three different measurements. The v1 shell implementation is removed; it stays recoverable at tag v1.2 and notes/V1-BASH.md carries the setting-by-setting migration table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+31
-8
@@ -48,9 +48,11 @@ impl std::str::FromStr for Coord {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let t = s.trim();
|
||||
// Split on the separator 'x', which cannot be part of either number.
|
||||
let (row, col) = t
|
||||
.split_once('x')
|
||||
.ok_or_else(|| format!("{t:?} is not a coordinate -- expected \"<row>x<col>\", e.g. \"0x0\" or \"-1x0\""))?;
|
||||
let (row, col) = t.split_once('x').ok_or_else(|| {
|
||||
format!(
|
||||
"{t:?} is not a coordinate -- expected \"<row>x<col>\", e.g. \"0x0\" or \"-1x0\""
|
||||
)
|
||||
})?;
|
||||
let parse = |part: &str, which: &str| -> Result<i32, String> {
|
||||
part.trim().parse::<i32>().map_err(|_| {
|
||||
format!("{t:?} is not a coordinate -- the {which} {part:?} is not a whole number")
|
||||
@@ -122,22 +124,43 @@ mod tests {
|
||||
assert_eq!(Coord::new(1, -2).window_name(), "r1cm2");
|
||||
for c in [Coord::new(0, 0), Coord::new(-1, -1), Coord::new(9, 9)] {
|
||||
let n = c.window_name();
|
||||
assert!(!n.contains(':') && !n.contains('.') && !n.contains('-'), "{n}");
|
||||
assert!(
|
||||
!n.contains(':') && !n.contains('.') && !n.contains('-'),
|
||||
"{n}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unquoted_hex_coordinate_gets_a_useful_error() {
|
||||
// This is what YAML actually hands us for `at: 0x0`.
|
||||
let err = serde_yaml::from_str::<Coord>("0x0").unwrap_err().to_string();
|
||||
let err = serde_yaml::from_str::<Coord>("0x0")
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(err.contains("hexadecimal"), "unhelpful error: {err}");
|
||||
assert!(err.contains("at: \"0x0\""), "error should show the fix: {err}");
|
||||
assert!(
|
||||
err.contains("at: \"0x0\""),
|
||||
"error should show the fix: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sorts_by_row_then_column() {
|
||||
let mut v = vec![Coord::new(1, 0), Coord::new(-1, 5), Coord::new(0, 2), Coord::new(0, -1)];
|
||||
let mut v = vec![
|
||||
Coord::new(1, 0),
|
||||
Coord::new(-1, 5),
|
||||
Coord::new(0, 2),
|
||||
Coord::new(0, -1),
|
||||
];
|
||||
v.sort();
|
||||
assert_eq!(v, vec![Coord::new(-1, 5), Coord::new(0, -1), Coord::new(0, 2), Coord::new(1, 0)]);
|
||||
assert_eq!(
|
||||
v,
|
||||
vec![
|
||||
Coord::new(-1, 5),
|
||||
Coord::new(0, -1),
|
||||
Coord::new(0, 2),
|
||||
Coord::new(1, 0)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -360,9 +360,7 @@ impl Config {
|
||||
or a comma-separated string"
|
||||
.into(),
|
||||
),
|
||||
Some(g) if g.0.is_empty() => {
|
||||
wrong("socktop_group is empty".into())
|
||||
}
|
||||
Some(g) if g.0.is_empty() => wrong("socktop_group is empty".into()),
|
||||
_ => {}
|
||||
}
|
||||
if s.url.is_some() {
|
||||
|
||||
+6
-2
@@ -62,13 +62,17 @@ pub fn listen(session: &str, tx: Sender<Ctl>) -> Result<UnixListener> {
|
||||
let listener = UnixListener::bind(&path)
|
||||
.with_context(|| format!("cannot create control socket at {}", path.display()))?;
|
||||
|
||||
let accepting = listener.try_clone().context("cannot clone control socket")?;
|
||||
let accepting = listener
|
||||
.try_clone()
|
||||
.context("cannot clone control socket")?;
|
||||
std::thread::spawn(move || {
|
||||
for stream in accepting.incoming() {
|
||||
let Ok(stream) = stream else { continue };
|
||||
let reply = stream.try_clone().ok();
|
||||
let mut lines = BufReader::new(stream).lines();
|
||||
let Some(Ok(line)) = lines.next() else { continue };
|
||||
let Some(Ok(line)) = lines.next() else {
|
||||
continue;
|
||||
};
|
||||
match parse_move(&line) {
|
||||
Some(m) => {
|
||||
if tx.send(Ctl::Go(m, reply)).is_err() {
|
||||
|
||||
+5
-1
@@ -32,7 +32,11 @@ pub fn run(cfg: &Config) -> Result<()> {
|
||||
println!("Watching {}", cfg.touch.device);
|
||||
println!(
|
||||
" panel {}x{}, threshold {}px, leniency {}\u{b0}, contacts accepted: {:?}",
|
||||
cfg.touch.width, cfg.touch.height, cfg.touch.threshold, cfg.touch.leniency, cfg.touch.fingers
|
||||
cfg.touch.width,
|
||||
cfg.touch.height,
|
||||
cfg.touch.threshold,
|
||||
cfg.touch.leniency,
|
||||
cfg.touch.fingers
|
||||
);
|
||||
println!(
|
||||
" grab: {}\n",
|
||||
|
||||
+47
-14
@@ -71,18 +71,23 @@ impl Grid {
|
||||
let mut cells: Vec<Cell> =
|
||||
cells.iter().filter(|c| c.coord.row == r).cloned().collect();
|
||||
cells.sort_by_key(|c| c.coord.col);
|
||||
Row { row: r, cells, cursor: 0, visited: false }
|
||||
Row {
|
||||
row: r,
|
||||
cells,
|
||||
cursor: 0,
|
||||
visited: false,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Start at 0x0 when it exists; otherwise the leftmost cell of row 0, and
|
||||
// failing that the top-left of the whole grid.
|
||||
let cursor = rows
|
||||
.iter()
|
||||
.position(|r| r.row == 0)
|
||||
.unwrap_or(0);
|
||||
let cursor = rows.iter().position(|r| r.row == 0).unwrap_or(0);
|
||||
let mut grid = Grid { rows, cursor };
|
||||
let start_col = grid.rows[cursor].cells.iter().position(|c| c.coord.col == 0);
|
||||
let start_col = grid.rows[cursor]
|
||||
.cells
|
||||
.iter()
|
||||
.position(|c| c.coord.col == 0);
|
||||
grid.rows[cursor].cursor = start_col.unwrap_or(0);
|
||||
grid.rows[cursor].visited = true;
|
||||
Ok(grid)
|
||||
@@ -104,7 +109,11 @@ impl Grid {
|
||||
|
||||
pub fn position(&self, changed: bool) -> Position {
|
||||
let c = self.cell();
|
||||
Position { coord: c.coord, screen: c.cursor, changed }
|
||||
Position {
|
||||
coord: c.coord,
|
||||
screen: c.cursor,
|
||||
changed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply(&mut self, m: Move) -> Position {
|
||||
@@ -218,7 +227,10 @@ mod tests {
|
||||
kind: MonitorType::Socktop,
|
||||
label: format!("{coord}"),
|
||||
panes: (0..n)
|
||||
.map(|i| Pane { title: format!("{coord}#{i}"), command: vec!["true".into()] })
|
||||
.map(|i| Pane {
|
||||
title: format!("{coord}#{i}"),
|
||||
command: vec!["true".into()],
|
||||
})
|
||||
.collect(),
|
||||
layout: Layout::Tiled,
|
||||
cursor: 0,
|
||||
@@ -319,8 +331,14 @@ mod tests {
|
||||
let mut sparse = Grid::new(vec![cell("0x0", 1), cell("0x5", 1), cell("0x99", 1)]).unwrap();
|
||||
let mut dense = Grid::new(vec![cell("0x0", 1), cell("0x1", 1), cell("0x2", 1)]).unwrap();
|
||||
let moves = [Move::Forward, Move::Forward, Move::Back, Move::Forward];
|
||||
let s: Vec<_> = walk(&mut sparse, &moves).iter().map(|p| p.split('@').nth(1).unwrap().to_string()).collect();
|
||||
let d: Vec<_> = walk(&mut dense, &moves).iter().map(|p| p.split('@').nth(1).unwrap().to_string()).collect();
|
||||
let s: Vec<_> = walk(&mut sparse, &moves)
|
||||
.iter()
|
||||
.map(|p| p.split('@').nth(1).unwrap().to_string())
|
||||
.collect();
|
||||
let d: Vec<_> = walk(&mut dense, &moves)
|
||||
.iter()
|
||||
.map(|p| p.split('@').nth(1).unwrap().to_string())
|
||||
.collect();
|
||||
assert_eq!(s, d);
|
||||
assert_eq!(sparse.cell().coord, Coord::new(0, 99));
|
||||
assert_eq!(dense.cell().coord, Coord::new(0, 2));
|
||||
@@ -330,7 +348,10 @@ mod tests {
|
||||
fn columns_out_of_order_in_the_file_still_sort() {
|
||||
let mut g = Grid::new(vec![cell("0x2", 1), cell("0x0", 1), cell("0x1", 1)]).unwrap();
|
||||
assert_eq!(at(&g), "0x0@0");
|
||||
assert_eq!(walk(&mut g, &[Move::Forward, Move::Forward]), ["0x1@0", "0x2@0"]);
|
||||
assert_eq!(
|
||||
walk(&mut g, &[Move::Forward, Move::Forward]),
|
||||
["0x1@0", "0x2@0"]
|
||||
);
|
||||
}
|
||||
|
||||
// -- vertical ------------------------------------------------------------
|
||||
@@ -381,7 +402,11 @@ mod tests {
|
||||
["0x0@1", "0x0@2", "0x1@0", "0x1@1", "0x1@2"]
|
||||
);
|
||||
|
||||
assert_eq!(walk(&mut g, &[Move::Up]), ["-1x0@0"], "snapped on first entry");
|
||||
assert_eq!(
|
||||
walk(&mut g, &[Move::Up]),
|
||||
["-1x0@0"],
|
||||
"snapped on first entry"
|
||||
);
|
||||
assert_eq!(
|
||||
walk(&mut g, &[Move::Down]),
|
||||
["0x1@2"],
|
||||
@@ -400,7 +425,11 @@ mod tests {
|
||||
.unwrap();
|
||||
g.apply(Move::Forward); // to 0x8
|
||||
assert_eq!(at(&g), "0x8@0");
|
||||
assert_eq!(walk(&mut g, &[Move::Up]), ["-1x9@0"], "9 is nearer to 8 than 1");
|
||||
assert_eq!(
|
||||
walk(&mut g, &[Move::Up]),
|
||||
["-1x9@0"],
|
||||
"9 is nearer to 8 than 1"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -434,7 +463,11 @@ mod tests {
|
||||
assert_eq!(at(&g), "0x0@0");
|
||||
g.apply(Move::Forward); // now at 0x1
|
||||
g.apply(Move::Up);
|
||||
assert_eq!(walk(&mut g, &[Move::Down]), ["0x1@0"], "memory follows the move");
|
||||
assert_eq!(
|
||||
walk(&mut g, &[Move::Down]),
|
||||
["0x1@0"],
|
||||
"memory follows the move"
|
||||
);
|
||||
}
|
||||
|
||||
// -- start position ------------------------------------------------------
|
||||
|
||||
+48
-14
@@ -120,7 +120,10 @@ impl Touchpanel {
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(Self { device, cfg: cfg.clone() })
|
||||
Ok(Self {
|
||||
device,
|
||||
cfg: cfg.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Blocking gesture loop. Calls `on_event` for every completed gesture,
|
||||
@@ -144,7 +147,11 @@ impl Touchpanel {
|
||||
} else {
|
||||
slots.insert(
|
||||
current,
|
||||
Slot { start: (0, 0), last: (0, 0), active: true },
|
||||
Slot {
|
||||
start: (0, 0),
|
||||
last: (0, 0),
|
||||
active: true,
|
||||
},
|
||||
);
|
||||
peak = peak.max(slots.values().filter(|s| s.active).count());
|
||||
}
|
||||
@@ -161,8 +168,7 @@ impl Touchpanel {
|
||||
|
||||
// The gesture ends when the last contact lifts.
|
||||
if !slots.is_empty() && slots.values().all(|s| !s.active) {
|
||||
let tracks: Vec<Track> =
|
||||
slots.values().map(|s| (s.start, s.last)).collect();
|
||||
let tracks: Vec<Track> = slots.values().map(|s| (s.start, s.last)).collect();
|
||||
if let Some(ev) = classify(&self.cfg, &tracks, peak) {
|
||||
if !on_event(ev) {
|
||||
return Ok(());
|
||||
@@ -173,7 +179,6 @@ impl Touchpanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// One contact's journey: where it landed and where it lifted.
|
||||
@@ -190,7 +195,10 @@ pub fn classify(cfg: &Touch, tracks: &[Track], peak: usize) -> Option<Event> {
|
||||
return None;
|
||||
}
|
||||
let (dx, dy) = tracks.iter().fold((0.0, 0.0), |(ax, ay), (start, last)| {
|
||||
(ax + (last.0 - start.0) as f64 / n, ay + (last.1 - start.1) as f64 / n)
|
||||
(
|
||||
ax + (last.0 - start.0) as f64 / n,
|
||||
ay + (last.1 - start.1) as f64 / n,
|
||||
)
|
||||
});
|
||||
|
||||
let travel = (dx * dx + dy * dy).sqrt();
|
||||
@@ -207,28 +215,44 @@ pub fn classify(cfg: &Touch, tracks: &[Track], peak: usize) -> Option<Event> {
|
||||
if travel < cfg.threshold as f64 {
|
||||
return Some(Event::Discarded(
|
||||
direction,
|
||||
Rejected::TooShort { travel, threshold: cfg.threshold },
|
||||
Rejected::TooShort {
|
||||
travel,
|
||||
threshold: cfg.threshold,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
// Angle away from the dominant axis.
|
||||
let (along, across) = if horizontal { (dx.abs(), dy.abs()) } else { (dy.abs(), dx.abs()) };
|
||||
let (along, across) = if horizontal {
|
||||
(dx.abs(), dy.abs())
|
||||
} else {
|
||||
(dy.abs(), dx.abs())
|
||||
};
|
||||
let degrees = across.atan2(along).to_degrees();
|
||||
if degrees > cfg.leniency as f64 {
|
||||
return Some(Event::Discarded(
|
||||
direction,
|
||||
Rejected::OffAxis { degrees, leniency: cfg.leniency },
|
||||
Rejected::OffAxis {
|
||||
degrees,
|
||||
leniency: cfg.leniency,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if !cfg.fingers.contains(&peak) {
|
||||
return Some(Event::Discarded(
|
||||
direction,
|
||||
Rejected::WrongFingerCount { saw: peak, want: cfg.fingers.clone() },
|
||||
Rejected::WrongFingerCount {
|
||||
saw: peak,
|
||||
want: cfg.fingers.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Some(Event::Swipe(Swipe { direction, fingers: peak }))
|
||||
Some(Event::Swipe(Swipe {
|
||||
direction,
|
||||
fingers: peak,
|
||||
}))
|
||||
}
|
||||
|
||||
fn update(slots: &mut HashMap<i32, Slot>, current: i32, f: impl Fn(&mut (i32, i32))) {
|
||||
@@ -251,7 +275,9 @@ pub fn list_touchscreens() -> Vec<(String, String)> {
|
||||
let entries = std::fs::read_dir(by_id).into_iter().flatten().flatten();
|
||||
for e in entries {
|
||||
let path = e.path();
|
||||
let Ok(dev) = Device::open(&path) else { continue };
|
||||
let Ok(dev) = Device::open(&path) else {
|
||||
continue;
|
||||
};
|
||||
let multitouch = dev
|
||||
.supported_absolute_axes()
|
||||
.is_some_and(|a| a.contains(AbsoluteAxisType::ABS_MT_POSITION_X));
|
||||
@@ -368,7 +394,11 @@ mod tests {
|
||||
// nothing ever fires, because the panel reports 2 contacts and the
|
||||
// config accepts only 1.
|
||||
let cfg = touch(80, 30, vec![1]);
|
||||
match swipe(&cfg, &[track((900, 300), -200, 0), track((905, 305), -200, 0)], 2) {
|
||||
match swipe(
|
||||
&cfg,
|
||||
&[track((900, 300), -200, 0), track((905, 305), -200, 0)],
|
||||
2,
|
||||
) {
|
||||
Event::Discarded(Direction::RL, r @ Rejected::WrongFingerCount { saw, .. }) => {
|
||||
assert_eq!(saw, 2);
|
||||
assert!(r.to_string().contains("touch.fingers"), "{r}");
|
||||
@@ -382,7 +412,11 @@ mod tests {
|
||||
// Otherwise a stray tap on a panel with ghost contacts reports the
|
||||
// finger-count problem, sending you to fix the wrong setting.
|
||||
let cfg = touch(80, 30, vec![1]);
|
||||
match swipe(&cfg, &[track((640, 360), -5, 0), track((641, 361), -5, 0)], 2) {
|
||||
match swipe(
|
||||
&cfg,
|
||||
&[track((640, 360), -5, 0), track((641, 361), -5, 0)],
|
||||
2,
|
||||
) {
|
||||
Event::Discarded(_, Rejected::TooShort { .. }) => {}
|
||||
other => panic!("expected the short-travel reason first: {other:?}"),
|
||||
}
|
||||
|
||||
+19
-7
@@ -104,7 +104,10 @@ fn real_main() -> Result<()> {
|
||||
Cmd::Build => {
|
||||
let (tmux, grid) = build(&cfg)?;
|
||||
tmux.build(&grid)?;
|
||||
println!("session {:?} is up. Attach with: tmux attach -t {}", cfg.session, cfg.session);
|
||||
println!(
|
||||
"session {:?} is up. Attach with: tmux attach -t {}",
|
||||
cfg.session, cfg.session
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Cmd::Attach => {
|
||||
@@ -141,7 +144,11 @@ fn validate(cfg: &Config, path: &std::path::Path) -> Result<()> {
|
||||
for row in &grid.rows {
|
||||
println!("row {}:", row.row);
|
||||
for cell in &row.cells {
|
||||
let start = if cell.coord == config::Coord::new(0, 0) { " <- start" } else { "" };
|
||||
let start = if cell.coord == config::Coord::new(0, 0) {
|
||||
" <- start"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
println!(
|
||||
" {:<7} {:<20} {} screen{}{start}",
|
||||
cell.coord.to_string(),
|
||||
@@ -173,7 +180,10 @@ fn validate(cfg: &Config, path: &std::path::Path) -> Result<()> {
|
||||
}
|
||||
|
||||
if !std::path::Path::new(&cfg.touch.device).exists() {
|
||||
println!("touch: \u{2717} {} is not present right now", cfg.touch.device);
|
||||
println!(
|
||||
"touch: \u{2717} {} is not present right now",
|
||||
cfg.touch.device
|
||||
);
|
||||
println!(" `socktop-swipe doctor --list` shows what is.\n");
|
||||
} else {
|
||||
println!("touch: \u{2713} {}\n", cfg.touch.device);
|
||||
@@ -198,9 +208,7 @@ fn which(bin: &str) -> bool {
|
||||
return std::path::Path::new(bin).is_file();
|
||||
}
|
||||
std::env::var_os("PATH")
|
||||
.map(|p| {
|
||||
std::env::split_paths(&p).any(|dir| dir.join(bin).is_file())
|
||||
})
|
||||
.map(|p| std::env::split_paths(&p).any(|dir| dir.join(bin).is_file()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
@@ -312,7 +320,11 @@ fn drive(tmux: &Tmux, grid: &mut Grid, rx: mpsc::Receiver<Ctl>) -> Result<()> {
|
||||
answer(reply, &format!("error: {e:#}"));
|
||||
return Err(e);
|
||||
}
|
||||
let edge = if pos.changed { "" } else { " (edge, nothing to move to)" };
|
||||
let edge = if pos.changed {
|
||||
""
|
||||
} else {
|
||||
" (edge, nothing to move to)"
|
||||
};
|
||||
answer(
|
||||
reply,
|
||||
&format!("{} {}{edge}", pos.coord, grid.cell().screen_label()),
|
||||
|
||||
+16
-4
@@ -88,7 +88,10 @@ pub fn build_cell(screen: &Screen, bins: &Binaries) -> Result<Cell> {
|
||||
coord: at,
|
||||
kind: screen.kind,
|
||||
label: title.clone(),
|
||||
panes: vec![Pane { title, command: argv }],
|
||||
panes: vec![Pane {
|
||||
title,
|
||||
command: argv,
|
||||
}],
|
||||
layout: screen.layout.unwrap_or_default(),
|
||||
cursor: 0,
|
||||
});
|
||||
@@ -128,7 +131,10 @@ pub fn build_cell(screen: &Screen, bins: &Binaries) -> Result<Cell> {
|
||||
c.extend(extra);
|
||||
(
|
||||
"uptime kuma".to_string(),
|
||||
vec![Pane { title: "uptime kuma".into(), command: c }],
|
||||
vec![Pane {
|
||||
title: "uptime kuma".into(),
|
||||
command: c,
|
||||
}],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -146,7 +152,10 @@ pub fn build_cell(screen: &Screen, bins: &Binaries) -> Result<Cell> {
|
||||
c.extend(extra);
|
||||
(
|
||||
"unifly".to_string(),
|
||||
vec![Pane { title: "unifly".into(), command: c }],
|
||||
vec![Pane {
|
||||
title: "unifly".into(),
|
||||
command: c,
|
||||
}],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -165,5 +174,8 @@ pub fn build_cell(screen: &Screen, bins: &Binaries) -> Result<Cell> {
|
||||
}
|
||||
|
||||
pub fn build_cells(cfg: &Config) -> Result<Vec<Cell>> {
|
||||
cfg.screens.iter().map(|s| build_cell(s, &cfg.binaries)).collect()
|
||||
cfg.screens
|
||||
.iter()
|
||||
.map(|s| build_cell(s, &cfg.binaries))
|
||||
.collect()
|
||||
}
|
||||
|
||||
+65
-14
@@ -81,7 +81,13 @@ impl Tmux {
|
||||
}
|
||||
|
||||
fn zoomed(&self, window: &str) -> Result<bool> {
|
||||
Ok(self.run(&["display-message", "-p", "-t", window, "#{window_zoomed_flag}"])? == "1")
|
||||
Ok(self.run(&[
|
||||
"display-message",
|
||||
"-p",
|
||||
"-t",
|
||||
window,
|
||||
"#{window_zoomed_flag}",
|
||||
])? == "1")
|
||||
}
|
||||
|
||||
fn place(&self, cell: &Cell, first: bool) -> Result<Placed> {
|
||||
@@ -90,19 +96,33 @@ impl Tmux {
|
||||
|
||||
let window = if first {
|
||||
self.run(&[
|
||||
"new-session", "-d", "-s", &self.session, "-n", &name,
|
||||
"-P", "-F", "#{window_id}", &head,
|
||||
"new-session",
|
||||
"-d",
|
||||
"-s",
|
||||
&self.session,
|
||||
"-n",
|
||||
&name,
|
||||
"-P",
|
||||
"-F",
|
||||
"#{window_id}",
|
||||
&head,
|
||||
])?
|
||||
} else {
|
||||
self.run(&[
|
||||
"new-window", "-d", "-t", &self.session, "-n", &name,
|
||||
"-P", "-F", "#{window_id}", &head,
|
||||
"new-window",
|
||||
"-d",
|
||||
"-t",
|
||||
&self.session,
|
||||
"-n",
|
||||
&name,
|
||||
"-P",
|
||||
"-F",
|
||||
"#{window_id}",
|
||||
&head,
|
||||
])?
|
||||
};
|
||||
|
||||
let first_pane = self.run(&[
|
||||
"display-message", "-p", "-t", &window, "#{pane_id}",
|
||||
])?;
|
||||
let first_pane = self.run(&["display-message", "-p", "-t", &window, "#{pane_id}"])?;
|
||||
self.run(&["select-pane", "-t", &first_pane, "-T", &cell.panes[0].title])?;
|
||||
let mut panes = vec![first_pane];
|
||||
|
||||
@@ -110,7 +130,12 @@ impl Tmux {
|
||||
// Split the most recently created pane so creation order matches
|
||||
// the visual order the layout will impose.
|
||||
let id = self.run(&[
|
||||
"split-window", "-t", panes.last().unwrap(), "-P", "-F", "#{pane_id}",
|
||||
"split-window",
|
||||
"-t",
|
||||
panes.last().unwrap(),
|
||||
"-P",
|
||||
"-F",
|
||||
"#{pane_id}",
|
||||
&Self::shell_command(&pane.command),
|
||||
])?;
|
||||
self.run(&["select-pane", "-t", &id, "-T", &pane.title])?;
|
||||
@@ -131,7 +156,11 @@ impl Tmux {
|
||||
/// daemon would cheerfully drive a stale layout.
|
||||
fn adopt(&self, grid: &Grid) -> Result<()> {
|
||||
let listing = self.run(&[
|
||||
"list-windows", "-t", &self.session, "-F", "#{window_id} #{window_name}",
|
||||
"list-windows",
|
||||
"-t",
|
||||
&self.session,
|
||||
"-F",
|
||||
"#{window_id} #{window_name}",
|
||||
])?;
|
||||
let mut by_name: HashMap<&str, &str> = HashMap::new();
|
||||
for line in listing.lines() {
|
||||
@@ -166,7 +195,13 @@ impl Tmux {
|
||||
cell.panes.len()
|
||||
);
|
||||
}
|
||||
placed.insert(cell.coord, Placed { window: (*window).to_owned(), panes });
|
||||
placed.insert(
|
||||
cell.coord,
|
||||
Placed {
|
||||
window: (*window).to_owned(),
|
||||
panes,
|
||||
},
|
||||
);
|
||||
}
|
||||
*self.placed.borrow_mut() = placed;
|
||||
Ok(())
|
||||
@@ -191,7 +226,12 @@ impl Tmux {
|
||||
parts.push(format!("{} \u{25b2}", u.label));
|
||||
}
|
||||
let here = if cell.screens() > 1 {
|
||||
format!(" {} [{}/{}] ", cell.screen_label(), cell.cursor + 1, cell.screens())
|
||||
format!(
|
||||
" {} [{}/{}] ",
|
||||
cell.screen_label(),
|
||||
cell.cursor + 1,
|
||||
cell.screens()
|
||||
)
|
||||
} else {
|
||||
format!(" {} ", cell.screen_label())
|
||||
};
|
||||
@@ -226,7 +266,13 @@ impl Multiplexer for Tmux {
|
||||
|
||||
let s = &self.session;
|
||||
self.run(&["set-option", "-t", s, "pane-border-status", "top"])?;
|
||||
self.run(&["set-option", "-t", s, "pane-border-format", " #{pane_title} "])?;
|
||||
self.run(&[
|
||||
"set-option",
|
||||
"-t",
|
||||
s,
|
||||
"pane-border-format",
|
||||
" #{pane_title} ",
|
||||
])?;
|
||||
|
||||
// Mouse mode MUST stay off. With it on, a touch swipe is also delivered
|
||||
// to tmux as a click-drag: dragging across a pane border resizes it and
|
||||
@@ -298,6 +344,11 @@ impl Multiplexer for Tmux {
|
||||
}
|
||||
|
||||
fn attach_argv(&self) -> Vec<String> {
|
||||
vec!["tmux".into(), "attach".into(), "-t".into(), self.session.clone()]
|
||||
vec![
|
||||
"tmux".into(),
|
||||
"attach".into(),
|
||||
"-t".into(),
|
||||
self.session.clone(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user