| 110 |
110 |
|
.iter()
|
| 111 |
111 |
|
.position(|a| a == "--exec")
|
| 112 |
112 |
|
.and_then(|i| args.get(i + 1).cloned());
|
|
113 |
+ |
// `-e PROGRAM [ARGS...]` takes the rest of argv as a program and its
|
|
114 |
+ |
// arguments, with no shell in between. This is the xterm convention, and
|
|
115 |
+ |
// it is not optional for a terminal that means to be somebody's
|
|
116 |
+ |
// `$TERMINAL`: every desktop entry with `Terminal=true`, and every script
|
|
117 |
+ |
// that spawns a TUI, writes `$TERMINAL -e prog arg`. A terminal that
|
|
118 |
+ |
// ignores it opens a bare shell and looks like the launcher is broken.
|
|
119 |
+ |
//
|
|
120 |
+ |
// Distinct from `--exec` on purpose: that one is a single string handed to
|
|
121 |
+ |
// `sh -c`, which is what a benchmark wants and what a launcher must not
|
|
122 |
+ |
// have, because the arguments would need quoting nobody applies.
|
|
123 |
+ |
let exec_argv: Option<Vec<String>> = args
|
|
124 |
+ |
.iter()
|
|
125 |
+ |
.position(|a| a == "-e")
|
|
126 |
+ |
.map(|i| args[i + 1..].to_vec())
|
|
127 |
+ |
.filter(|argv| !argv.is_empty());
|
| 113 |
128 |
|
// `--record PATH` tees the PTY output byte-for-byte to PATH, for
|
| 114 |
129 |
|
// feeding into the kitty-graphics-testkit corpus via capture-apc.
|
| 115 |
130 |
|
let record_path = args
|
| 123 |
138 |
|
.position(|a| a == "--theme")
|
| 124 |
139 |
|
.and_then(|i| args.get(i + 1).cloned());
|
| 125 |
140 |
|
let palette = Palette::load(&Config::load().with_theme(theme_arg));
|
| 126 |
|
- |
let (spawn_cmd, spawn_args): (String, Vec<String>) = match &exec_cmd {
|
| 127 |
|
- |
Some(cmd) => ("/bin/sh".into(), vec!["-c".into(), cmd.clone()]),
|
| 128 |
|
- |
None => (
|
| 129 |
|
- |
std::env::var("SHELL").unwrap_or_else(|_| "/bin/bash".into()),
|
| 130 |
|
- |
Vec::new(),
|
| 131 |
|
- |
),
|
| 132 |
|
- |
};
|
|
141 |
+ |
let (spawn_cmd, spawn_args) =
|
|
142 |
+ |
spawn_target(exec_argv, exec_cmd.clone(), std::env::var("SHELL").ok());
|
| 133 |
143 |
|
let spawn_args_refs: Vec<&str> = spawn_args.iter().map(String::as_str).collect();
|
| 134 |
144 |
|
let cols_initial = grid_cols(INITIAL.0);
|
| 135 |
145 |
|
let rows_initial = grid_rows(INITIAL.1);
|
| 574 |
584 |
|
}
|
| 575 |
585 |
|
}
|
| 576 |
586 |
|
|
|
587 |
+ |
/// What to spawn on the PTY, given the two exec flags and `$SHELL`.
|
|
588 |
+ |
///
|
|
589 |
+ |
/// `-e` wins over `--exec`: it is the one a launcher passes, so if both
|
|
590 |
+ |
/// somehow arrive the launcher's intent is the one to honour. With neither,
|
|
591 |
+ |
/// the user's login shell, and `/bin/bash` if even that is unset — a terminal
|
|
592 |
+ |
/// that opens no shell is not a fallback anyone can use.
|
|
593 |
+ |
fn spawn_target(
|
|
594 |
+ |
exec_argv: Option<Vec<String>>,
|
|
595 |
+ |
exec_cmd: Option<String>,
|
|
596 |
+ |
shell: Option<String>,
|
|
597 |
+ |
) -> (String, Vec<String>) {
|
|
598 |
+ |
match (exec_argv, exec_cmd) {
|
|
599 |
+ |
(Some(argv), _) => {
|
|
600 |
+ |
let mut argv = argv.into_iter();
|
|
601 |
+ |
// The filter on the parse guarantees a first element.
|
|
602 |
+ |
let program = argv.next().unwrap_or_else(|| "/bin/sh".into());
|
|
603 |
+ |
(program, argv.collect())
|
|
604 |
+ |
}
|
|
605 |
+ |
(None, Some(cmd)) => ("/bin/sh".into(), vec!["-c".into(), cmd]),
|
|
606 |
+ |
(None, None) => (shell.unwrap_or_else(|| "/bin/bash".into()), Vec::new()),
|
|
607 |
+ |
}
|
|
608 |
+ |
}
|
|
609 |
+ |
|
| 577 |
610 |
|
fn grid_cols(px_w: u32) -> u16 {
|
| 578 |
611 |
|
let usable = (px_w as f32 - 2.0 * PAD_X).max(CELL_ADVANCE);
|
| 579 |
612 |
|
(usable / CELL_ADVANCE) as u16
|
| 1729 |
1762 |
|
Point::new(row, col)
|
| 1730 |
1763 |
|
}
|
| 1731 |
1764 |
|
|
|
1765 |
+ |
fn argv(items: &[&str]) -> Vec<String> {
|
|
1766 |
+ |
items.iter().map(|s| (*s).to_string()).collect()
|
|
1767 |
+ |
}
|
|
1768 |
+ |
|
|
1769 |
+ |
#[test]
|
|
1770 |
+ |
fn with_no_flags_the_login_shell_runs() {
|
|
1771 |
+ |
let (cmd, args) = spawn_target(None, None, Some("/usr/bin/nu".into()));
|
|
1772 |
+ |
assert_eq!(cmd, "/usr/bin/nu");
|
|
1773 |
+ |
assert!(args.is_empty());
|
|
1774 |
+ |
}
|
|
1775 |
+ |
|
|
1776 |
+ |
#[test]
|
|
1777 |
+ |
fn a_missing_shell_still_opens_something() {
|
|
1778 |
+ |
let (cmd, _) = spawn_target(None, None, None);
|
|
1779 |
+ |
assert_eq!(cmd, "/bin/bash");
|
|
1780 |
+ |
}
|
|
1781 |
+ |
|
|
1782 |
+ |
#[test]
|
|
1783 |
+ |
fn dash_e_takes_the_program_and_its_arguments_verbatim() {
|
|
1784 |
+ |
// What a .desktop entry with Terminal=true produces, via alloy-menu.
|
|
1785 |
+ |
let (cmd, args) = spawn_target(Some(argv(&["helix", "/etc/fstab"])), None, None);
|
|
1786 |
+ |
assert_eq!(cmd, "helix");
|
|
1787 |
+ |
assert_eq!(args, argv(&["/etc/fstab"]));
|
|
1788 |
+ |
}
|
|
1789 |
+ |
|
|
1790 |
+ |
#[test]
|
|
1791 |
+ |
fn dash_e_does_not_go_through_a_shell() {
|
|
1792 |
+ |
// The arguments are argv entries, not a string to be re-split, so
|
|
1793 |
+ |
// anything the shell would have mangled arrives intact.
|
|
1794 |
+ |
let (cmd, args) = spawn_target(Some(argv(&["grep", "a b", "*.txt"])), None, None);
|
|
1795 |
+ |
assert_eq!(cmd, "grep");
|
|
1796 |
+ |
assert_eq!(args, argv(&["a b", "*.txt"]));
|
|
1797 |
+ |
}
|
|
1798 |
+ |
|
|
1799 |
+ |
#[test]
|
|
1800 |
+ |
fn exec_runs_its_string_through_a_shell() {
|
|
1801 |
+ |
let (cmd, args) = spawn_target(None, Some("ls | wc -l".into()), None);
|
|
1802 |
+ |
assert_eq!(cmd, "/bin/sh");
|
|
1803 |
+ |
assert_eq!(args, argv(&["-c", "ls | wc -l"]));
|
|
1804 |
+ |
}
|
|
1805 |
+ |
|
|
1806 |
+ |
#[test]
|
|
1807 |
+ |
fn dash_e_wins_over_exec() {
|
|
1808 |
+ |
let (cmd, _) = spawn_target(Some(argv(&["btop"])), Some("ls".into()), None);
|
|
1809 |
+ |
assert_eq!(cmd, "btop");
|
|
1810 |
+ |
}
|
|
1811 |
+ |
|
| 1732 |
1812 |
|
#[test]
|
| 1733 |
1813 |
|
fn the_origin_cell_starts_after_the_padding() {
|
| 1734 |
1814 |
|
assert_eq!(
|