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
|
## 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.
|
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
|
`tabbed-wlroots "foot -e htop" "firefox -no-remote"`. With no arguments, a
|
||||||
single terminal tab is opened (see `$TERMINAL` below). `tabbed-wlroots -h`
|
single terminal tab is opened (see `$TERMINAL` below). `--start-cmd-index
|
||||||
prints this usage.
|
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
|
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
|
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
|
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
|
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
|
"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
|
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
|
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_w, mode_req_h;
|
||||||
static int mode_req_refresh;
|
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 ===================== */
|
/* ===================== Host-side (nested) state ===================== */
|
||||||
|
|
||||||
/* One cached ASCII glyph in the overlay's font atlas texture. */
|
/* 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
|
/* Alt+Shift+T opens a terminal named by $TERMINAL when set, else the
|
||||||
alacritty default. Every command-line argument is one tab launcher,
|
alacritty default. Every command-line argument is one tab launcher,
|
||||||
except --screen-mode WxH[@Hz], which selects the native output mode and
|
except --screen-mode WxH[@Hz], which selects the native output mode and
|
||||||
is consumed here, and --modifier alt|super, which picks the modifier
|
is consumed here, --modifier alt|super, which picks the modifier
|
||||||
prefix for the tab-management chords. Handle all of this up front so a
|
prefix for the tab-management chords, and --start-cmd-index N, which
|
||||||
missing command (or a bad request) can be reported and the process can
|
picks the command (1-based) that starts focused. Handle all of this up
|
||||||
exit before any window is created. */
|
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");
|
const char *env_term = getenv("TERMINAL");
|
||||||
if (env_term && env_term[0])
|
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) {
|
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"usage: %s [--screen-mode WxH[@Hz]] [--modifier alt|super] "
|
"usage: %s [--screen-mode WxH[@Hz]] [--modifier alt|super] "
|
||||||
"[prog...]\n"
|
"[--start-cmd-index N] [prog...]\n"
|
||||||
" Select the native output mode (standalone only): "
|
" Select the native output mode (standalone only): "
|
||||||
"e.g. --screen-mode 1920x1080\n"
|
"e.g. --screen-mode 1920x1080\n"
|
||||||
" or --screen-mode 1920x1080@60. A request nothing "
|
" or --screen-mode 1920x1080@60. A request nothing "
|
||||||
|
|
@ -5372,6 +5378,8 @@ int main(int argc, char *argv[]) {
|
||||||
" --modifier picks the tab-management modifier "
|
" --modifier picks the tab-management modifier "
|
||||||
"(default alt); 'super' suits standalone/DRM and "
|
"(default alt); 'super' suits standalone/DRM and "
|
||||||
"labwc-style hosts.\n"
|
"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 "
|
" Each <prog> is one shell command; quote it when it "
|
||||||
"takes args, e.g. \"firefox -no-remote\".\n"
|
"takes args, e.g. \"firefox -no-remote\".\n"
|
||||||
" With no arguments, a single terminal tab opens "
|
" With no arguments, a single terminal tab opens "
|
||||||
|
|
@ -5423,6 +5431,39 @@ int main(int argc, char *argv[]) {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
continue;
|
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 (val) {
|
||||||
if (!parse_screen_mode(val)) {
|
if (!parse_screen_mode(val)) {
|
||||||
|
|
@ -5443,6 +5484,13 @@ int main(int argc, char *argv[]) {
|
||||||
default_tab = true;
|
default_tab = true;
|
||||||
total_apps = 1;
|
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++) {
|
for (int i = 0; i < total_apps; i++) {
|
||||||
const char *cmd = default_tab ? term_cmd : appv[i];
|
const char *cmd = default_tab ? term_cmd : appv[i];
|
||||||
if (!cmd_in_path(cmd)) {
|
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
|
black, titled by its command line, until the client maps. The user
|
||||||
waits only on that one tab. */
|
waits only on that one tab. --start-cmd-index picks it (1-based);
|
||||||
set_active_app(server.apps[total_apps - 1]);
|
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
|
/* Fork every command-line command at once; the process-group match in
|
||||||
handle_new_toplevel gives each its own slot on arrival. */
|
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"
|
"compositor exited after dropping the never-connecting tab"
|
||||||
finally:
|
finally:
|
||||||
s.close()
|
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