add --start-cmd-index to choose the startup-focused command
The last command-line command is active-and-black from the first frame by default; --start-cmd-index N (1-based) instead makes command N the one the program starts focused on. The 1-based user value is converted to the program's 0-based index as soon as it is parsed; a value outside 1..N is a startup error (rc 2) naming the flag.
This commit is contained in:
parent
a543576d45
commit
9683f17719
3 changed files with 115 additions and 12 deletions
12
README.md
12
README.md
|
|
@ -64,13 +64,14 @@ install works too.
|
|||
## Usage
|
||||
|
||||
```
|
||||
tabbed-wlroots [prog...]
|
||||
tabbed-wlroots [--start-cmd-index N] [prog...]
|
||||
```
|
||||
|
||||
Each `<prog>` is one shell command; quote it when it takes arguments, e.g.
|
||||
`tabbed-wlroots "foot -e htop" "firefox -no-remote"`. With no arguments, a
|
||||
single terminal tab is opened (see `$TERMINAL` below). `tabbed-wlroots -h`
|
||||
prints this usage.
|
||||
single terminal tab is opened (see `$TERMINAL` below). `--start-cmd-index
|
||||
N` starts focused on command `N` (1-based; the last command by default).
|
||||
`tabbed-wlroots -h` prints this usage.
|
||||
|
||||
Every argument starts a new tab. There is no tab-count limit. Client apps run
|
||||
as children of tabbed-wlroots and connect to its private Wayland socket, so
|
||||
|
|
@ -106,7 +107,10 @@ the **last** command's tab is the active one, so you wait only on that
|
|||
single tab: when it maps, the window reveals it and nothing steals focus
|
||||
before that, so you don't flicker through every intermediate tab. This
|
||||
"last command wins" rule also means the tab you see on startup is always
|
||||
the one from the last argument.
|
||||
the one from the last argument. Pass `--start-cmd-index N` to start focused
|
||||
on a different command instead: `N` is 1-based (the first command is 1),
|
||||
and that command's tab is black-and-active from the first frame just like
|
||||
the default last one.
|
||||
|
||||
If a command never connects — e.g. `sleep 999` — the rest of the command
|
||||
line still starts (they all launched together) and the window shows the
|
||||
|
|
|
|||
|
|
@ -232,6 +232,11 @@ struct tabbed_server {
|
|||
static int mode_req_w, mode_req_h;
|
||||
static int mode_req_refresh;
|
||||
|
||||
/* The command-line command to start focused (--start-cmd-index), 0-based as
|
||||
soon as the (1-based) user value is parsed. -1 (unset) means the last
|
||||
command, keeping the startup contract. */
|
||||
static int start_cmd_index = -1;
|
||||
|
||||
/* ===================== Host-side (nested) state ===================== */
|
||||
|
||||
/* One cached ASCII glyph in the overlay's font atlas texture. */
|
||||
|
|
@ -5345,10 +5350,11 @@ int main(int argc, char *argv[]) {
|
|||
/* Alt+Shift+T opens a terminal named by $TERMINAL when set, else the
|
||||
alacritty default. Every command-line argument is one tab launcher,
|
||||
except --screen-mode WxH[@Hz], which selects the native output mode and
|
||||
is consumed here, and --modifier alt|super, which picks the modifier
|
||||
prefix for the tab-management chords. Handle all of this up front so a
|
||||
missing command (or a bad request) can be reported and the process can
|
||||
exit before any window is created. */
|
||||
is consumed here, --modifier alt|super, which picks the modifier
|
||||
prefix for the tab-management chords, and --start-cmd-index N, which
|
||||
picks the command (1-based) that starts focused. Handle all of this up
|
||||
front so a missing command (or a bad request) can be reported and the
|
||||
process can exit before any window is created. */
|
||||
{
|
||||
const char *env_term = getenv("TERMINAL");
|
||||
if (env_term && env_term[0])
|
||||
|
|
@ -5364,7 +5370,7 @@ int main(int argc, char *argv[]) {
|
|||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
fprintf(stderr,
|
||||
"usage: %s [--screen-mode WxH[@Hz]] [--modifier alt|super] "
|
||||
"[prog...]\n"
|
||||
"[--start-cmd-index N] [prog...]\n"
|
||||
" Select the native output mode (standalone only): "
|
||||
"e.g. --screen-mode 1920x1080\n"
|
||||
" or --screen-mode 1920x1080@60. A request nothing "
|
||||
|
|
@ -5372,6 +5378,8 @@ int main(int argc, char *argv[]) {
|
|||
" --modifier picks the tab-management modifier "
|
||||
"(default alt); 'super' suits standalone/DRM and "
|
||||
"labwc-style hosts.\n"
|
||||
" --start-cmd-index picks the command (1-based) that "
|
||||
"starts focused; without it the last command does.\n"
|
||||
" Each <prog> is one shell command; quote it when it "
|
||||
"takes args, e.g. \"firefox -no-remote\".\n"
|
||||
" With no arguments, a single terminal tab opens "
|
||||
|
|
@ -5423,6 +5431,39 @@ int main(int argc, char *argv[]) {
|
|||
return 2;
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(argv[i], "--start-cmd-index") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fprintf(stderr, "tabbed-wlroots: --start-cmd-index needs a "
|
||||
"value, e.g. --start-cmd-index 2\n");
|
||||
free(appv);
|
||||
return 2;
|
||||
}
|
||||
const char *n = argv[++i];
|
||||
char *end = NULL;
|
||||
long idx = strtol(n, &end, 10);
|
||||
if (end == n || *end != '\0' || idx < 1 || idx > 2147483647L) {
|
||||
fprintf(stderr, "tabbed-wlroots: bad --start-cmd-index '%s' "
|
||||
"(use a 1-based command number, e.g. --start-cmd-index "
|
||||
"2)\n", n);
|
||||
free(appv);
|
||||
return 2;
|
||||
}
|
||||
/* Convert to the program's 0-based index as soon as possible. */
|
||||
start_cmd_index = (int)idx - 1;
|
||||
continue;
|
||||
} else if (strncmp(argv[i], "--start-cmd-index=",
|
||||
sizeof("--start-cmd-index=") - 1) == 0) {
|
||||
const char *n = argv[i] + sizeof("--start-cmd-index=") - 1;
|
||||
char *end = NULL;
|
||||
long idx = strtol(n, &end, 10);
|
||||
if (end == n || *end != '\0' || idx < 1 || idx > 2147483647L) {
|
||||
fprintf(stderr, "tabbed-wlroots: bad --start-cmd-index '%s' "
|
||||
"(use a 1-based command number)\n", n);
|
||||
free(appv);
|
||||
return 2;
|
||||
}
|
||||
start_cmd_index = (int)idx - 1;
|
||||
continue;
|
||||
}
|
||||
if (val) {
|
||||
if (!parse_screen_mode(val)) {
|
||||
|
|
@ -5443,6 +5484,13 @@ int main(int argc, char *argv[]) {
|
|||
default_tab = true;
|
||||
total_apps = 1;
|
||||
}
|
||||
if (start_cmd_index >= 0 && start_cmd_index >= total_apps) {
|
||||
fprintf(stderr, "tabbed-wlroots: --start-cmd-index %d is out of range: "
|
||||
"1..%d (%d command%s)\n", start_cmd_index + 1, total_apps,
|
||||
total_apps, total_apps == 1 ? "" : "s");
|
||||
free(appv);
|
||||
return 2;
|
||||
}
|
||||
for (int i = 0; i < total_apps; i++) {
|
||||
const char *cmd = default_tab ? term_cmd : appv[i];
|
||||
if (!cmd_in_path(cmd)) {
|
||||
|
|
@ -5758,10 +5806,13 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
/* The last command-line command is active from the very first frame:
|
||||
/* The selected command-line command is active from the very first frame:
|
||||
black, titled by its command line, until the client maps. The user
|
||||
waits only on that one tab. */
|
||||
set_active_app(server.apps[total_apps - 1]);
|
||||
waits only on that one tab. --start-cmd-index picks it (1-based);
|
||||
without the flag the last command is the one shown, so the startup tab
|
||||
is always the one the caller can count on. */
|
||||
int focus_idx = start_cmd_index >= 0 ? start_cmd_index : total_apps - 1;
|
||||
set_active_app(server.apps[focus_idx]);
|
||||
|
||||
/* Fork every command-line command at once; the process-group match in
|
||||
handle_new_toplevel gives each its own slot on arrival. */
|
||||
|
|
|
|||
|
|
@ -1805,3 +1805,51 @@ def test_command_never_connecting_drops_tab():
|
|||
"compositor exited after dropping the never-connecting tab"
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def test_start_cmd_index_selects_command():
|
||||
"""--start-cmd-index N (1-based) starts focused on command N instead of
|
||||
the default last command: index 1 makes the first command the active,
|
||||
black tab from the very first frame, revealed when it maps."""
|
||||
if not os.environ.get("WAYLAND_DISPLAY"):
|
||||
pytest.skip("needs a live host Wayland session (WAYLAND_DISPLAY)")
|
||||
s = _nested_session(f"{WL} '{RED}'", f"{WL} '{GREEN}'", f"{WL} '{BLUE}'",
|
||||
extra_args=["--start-cmd-index", "1"])
|
||||
try:
|
||||
assert s.wait_any_active() == 0, \
|
||||
"--start-cmd-index 1 should make the first command active"
|
||||
assert s.proc.poll() is None
|
||||
finally:
|
||||
s.close()
|
||||
if s.proc.poll() is None:
|
||||
s.proc.terminate()
|
||||
|
||||
|
||||
def test_start_cmd_index_errors():
|
||||
"""A malformed or out-of-range --start-cmd-index is a startup error (rc 2)
|
||||
naming the flag, before any window or backend work. The index is 1-based,
|
||||
so 0 is invalid and a value beyond the command count is out of range."""
|
||||
env = dict(os.environ)
|
||||
cases = (["--start-cmd-index"],
|
||||
["--start-cmd-index=abc"],
|
||||
["--start-cmd-index", "0"],
|
||||
["--start-cmd-index", "4", "build/wltest '#ff0000'",
|
||||
"build/wltest '#00ff00'", "build/wltest '#0000ff'"])
|
||||
for case in cases:
|
||||
proc = subprocess.Popen(
|
||||
["env", "-i",
|
||||
"PATH=" + env["PATH"],
|
||||
"HOME=" + env.get("HOME", "/"),
|
||||
"XDG_RUNTIME_DIR=" + env.get("XDG_RUNTIME_DIR", "/tmp"),
|
||||
"TABBED_MODE=standalone",
|
||||
"WLR_BACKENDS=headless",
|
||||
"stdbuf", "-oL",
|
||||
BIN] + case,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
preexec_fn=_preexec_death(os.getpid()),
|
||||
)
|
||||
out, _ = proc.communicate(timeout=15)
|
||||
assert proc.returncode == 2, \
|
||||
f"--start-cmd-index {case} should exit rc 2; rc={proc.returncode}\n{out}"
|
||||
assert b"--start-cmd-index" in out, \
|
||||
f"error should mention --start-cmd-index:\n{out}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue