Add --screen-mode WxH[@Hz] to select the native output mode
The RPi5 projector reports a 4K preferred mode although it is really 1080p; previously only wlr_output_preferred_mode() was tried. --screen-mode picks an exact resolution (highest refresh among matches, or an exact @Hz when a refresh is given) from the output's mode list and, if the request cannot be satisfied, prints every reported mode and exits before launching anything. main(): consumes '--screen-mode <val>' and '--screen-mode=<val>' while compacting the app list so the flag never becomes a tab launcher; malformed values exit rc 2 with a usage hint. handle_new_output(): resolution/@Hz match with list-and-exit fallback. On the headless backend the request sizes the output, which also makes the flag testable. tests: screen_mode_sets_output_size (dump PPM == 480x320), not consumed as an app (both tabs boot, no 'starts with -' warning), bad values exit rc 2.
This commit is contained in:
parent
61c863a08b
commit
6ccb3e8d12
2 changed files with 182 additions and 11 deletions
142
tabbed-wlroots.c
142
tabbed-wlroots.c
|
|
@ -180,6 +180,11 @@ struct tabbed_server {
|
|||
struct wl_list ptrdevs;
|
||||
};
|
||||
|
||||
/* Requested output resolution from --screen-mode WxH[@Hz]; 0/0/0 means "let
|
||||
the output pick its preferred mode". Refresh is stored in mHz. */
|
||||
static int mode_req_w, mode_req_h;
|
||||
static int mode_req_refresh;
|
||||
|
||||
/* ===================== Host-side (nested) state ===================== */
|
||||
|
||||
/* One cached ASCII glyph in the overlay's font atlas texture. */
|
||||
|
|
@ -1666,8 +1671,53 @@ server.output = output;
|
|||
wlr_output_state_init(&state);
|
||||
wlr_output_state_set_enabled(&state, true);
|
||||
struct wlr_output_mode *mode = NULL;
|
||||
if (server.standalone && !server.standalone_headless)
|
||||
mode = wlr_output_preferred_mode(output);
|
||||
if (server.standalone && !server.standalone_headless) {
|
||||
if (mode_req_w > 0) {
|
||||
/* --screen-mode WxH[@Hz]: pick an exact-resolution mode, highest
|
||||
refresh when none was requested. A request the output can't
|
||||
satisfy is a startup error: print every reported mode and exit
|
||||
(before any client launches or the socket is advertised). */
|
||||
struct wlr_output_mode *m;
|
||||
int best_refresh = 0;
|
||||
wl_list_for_each(m, &output->modes, link) {
|
||||
if (m->width != mode_req_w || m->height != mode_req_h)
|
||||
continue;
|
||||
if (mode_req_refresh > 0) {
|
||||
if (m->refresh == mode_req_refresh) {
|
||||
mode = m;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (m->refresh > best_refresh) {
|
||||
best_refresh = m->refresh;
|
||||
mode = m;
|
||||
}
|
||||
}
|
||||
if (!mode) {
|
||||
fprintf(stderr, "tabbed-wlroots: no %dx%d",
|
||||
mode_req_w, mode_req_h);
|
||||
if (mode_req_refresh > 0)
|
||||
fprintf(stderr, "@%u.%03u", mode_req_refresh / 1000,
|
||||
mode_req_refresh % 1000);
|
||||
if (wl_list_empty(&output->modes))
|
||||
fprintf(stderr, " mode; output reports no modes\n");
|
||||
else
|
||||
fprintf(stderr, " mode; available:\n");
|
||||
wl_list_for_each(m, &output->modes, link) {
|
||||
fprintf(stderr, " %dx%d@%u.%03u%s%s\n",
|
||||
m->width, m->height, m->refresh / 1000,
|
||||
m->refresh % 1000,
|
||||
m->preferred ? " (preferred)" : "",
|
||||
(m->width == mode_req_w && m->height == mode_req_h)
|
||||
? " (requested size)" : "");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
mode = wlr_output_preferred_mode(output);
|
||||
}
|
||||
}
|
||||
if (mode) {
|
||||
w = mode->width;
|
||||
h = mode->height;
|
||||
|
|
@ -4528,6 +4578,37 @@ static bool backends_include(const char *env, const char *name) {
|
|||
|
||||
/* ===================== Main ===================== */
|
||||
|
||||
/* Parse a --screen-mode value: WxH or WxH@Hz (the Hz is matched at the
|
||||
mode's mHz precision, so "60" == 60.000). Stores into mode_req_w/h/
|
||||
refresh; returns false on a malformed value. */
|
||||
static bool parse_screen_mode(const char *val) {
|
||||
int w = 0, h = 0;
|
||||
const char *p = val;
|
||||
for (; *p >= '0' && *p <= '9'; p++)
|
||||
w = w * 10 + (*p - '0');
|
||||
if (*p != 'x')
|
||||
return false;
|
||||
p++;
|
||||
for (; *p >= '0' && *p <= '9'; p++)
|
||||
h = h * 10 + (*p - '0');
|
||||
if (w <= 0 || h <= 0)
|
||||
return false;
|
||||
unsigned refresh_mhz = 0;
|
||||
if (*p == '@') {
|
||||
char *end = NULL;
|
||||
double hz = strtod(p + 1, &end);
|
||||
if (end == p + 1 || hz <= 0.0 || *end != '\0')
|
||||
return false;
|
||||
refresh_mhz = (unsigned)(hz * 1000.0 + 0.5);
|
||||
} else if (*p != '\0') {
|
||||
return false;
|
||||
}
|
||||
mode_req_w = w;
|
||||
mode_req_h = h;
|
||||
mode_req_refresh = (int)refresh_mhz;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
snprintf(tabbed_socket, sizeof(tabbed_socket), "tabbed-wayland-%d",
|
||||
(int)getpid());
|
||||
|
|
@ -4550,27 +4631,63 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
/* Ctrl+Shift+T opens a terminal named by $TERMINAL when set, else the
|
||||
alacritty default. Every command-line argument is one tab launcher.
|
||||
Handle all of this up front so a missing command can be reported and
|
||||
the process can exit before any window is created. */
|
||||
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. 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])
|
||||
term_cmd = env_term;
|
||||
}
|
||||
char **appv = calloc((size_t)argc, sizeof(*appv));
|
||||
if (!appv) {
|
||||
fprintf(stderr, "tabbed-wlroots: out of memory\n");
|
||||
return 1;
|
||||
}
|
||||
int napps = 0;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
fprintf(stderr,
|
||||
"usage: %s [prog...]\n"
|
||||
"usage: %s [--screen-mode WxH[@Hz]] [prog...]\n"
|
||||
" Select the native output mode (standalone only): "
|
||||
"e.g. --screen-mode 1920x1080\n"
|
||||
" or --screen-mode 1920x1080@60. A request nothing "
|
||||
"supports prints every mode and exits.\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 "
|
||||
"($TERMINAL, default alacritty).\n"
|
||||
" -h / --help print this help.\n", argv[0]);
|
||||
free(appv);
|
||||
return 0;
|
||||
}
|
||||
const char *val = NULL;
|
||||
if (strcmp(argv[i], "--screen-mode") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
fprintf(stderr, "tabbed-wlroots: --screen-mode needs a value, "
|
||||
"e.g. --screen-mode 1920x1080\n");
|
||||
free(appv);
|
||||
return 2;
|
||||
}
|
||||
val = argv[++i];
|
||||
} else if (strncmp(argv[i], "--screen-mode=", 14) == 0) {
|
||||
val = argv[i] + 14;
|
||||
}
|
||||
if (val) {
|
||||
if (!parse_screen_mode(val)) {
|
||||
fprintf(stderr, "tabbed-wlroots: bad --screen-mode '%s' "
|
||||
"(use WxH or WxH@Hz, e.g. 1920x1080 or "
|
||||
"1920x1080@60)\n", val);
|
||||
free(appv);
|
||||
return 2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
appv[napps++] = argv[i];
|
||||
}
|
||||
int total_apps = argc - 1;
|
||||
int total_apps = napps;
|
||||
bool default_tab = false;
|
||||
if (total_apps == 0) {
|
||||
/* No arguments: open one terminal tab, as Ctrl+Shift+T would. */
|
||||
|
|
@ -4578,10 +4695,11 @@ int main(int argc, char *argv[]) {
|
|||
total_apps = 1;
|
||||
}
|
||||
for (int i = 0; i < total_apps; i++) {
|
||||
const char *cmd = default_tab ? term_cmd : argv[i + 1];
|
||||
const char *cmd = default_tab ? term_cmd : appv[i];
|
||||
if (!cmd_in_path(cmd)) {
|
||||
fprintf(stderr, "tabbed-wlroots: '%s' not found in PATH%s\n",
|
||||
cmd, default_tab ? " (set $TERMINAL)" : "");
|
||||
free(appv);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -4820,8 +4938,10 @@ int main(int argc, char *argv[]) {
|
|||
outputs of its own, so add ours manually; DRM emits its outputs via
|
||||
new_output. new_output fires during backend start. */
|
||||
if (!server.standalone || server.standalone_headless) {
|
||||
host.width = 1280;
|
||||
host.height = 800;
|
||||
int w = server.standalone && mode_req_w > 0 ? mode_req_w : 1280;
|
||||
int h = server.standalone && mode_req_h > 0 ? mode_req_h : 800;
|
||||
host.width = w;
|
||||
host.height = h;
|
||||
wlr_headless_add_output(server.backend, host.width, host.height);
|
||||
}
|
||||
|
||||
|
|
@ -4861,7 +4981,7 @@ int main(int argc, char *argv[]) {
|
|||
remove_socket_file();
|
||||
return 1;
|
||||
}
|
||||
app->cmd = strdup(default_tab ? term_cmd : argv[i + 1]);
|
||||
app->cmd = strdup(default_tab ? term_cmd : appv[i]);
|
||||
app->pid = -1;
|
||||
server.apps[i] = app;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -718,6 +718,57 @@ def test_standalone_headless_last_tab_exits_cleanly(standalone_session):
|
|||
assert rc == 0, f"rc={rc} (139 = SIGSEGV); log tail:\n{s.read_log()[-800:]}"
|
||||
|
||||
|
||||
def test_standalone_headless_screen_mode_sets_output_size():
|
||||
"""--screen-mode WxH sizes the output; on the headless backend it becomes
|
||||
the add_output() size, observable through the dumped frame dimensions."""
|
||||
s = _spawn_standalone({}, "--screen-mode", "480x320", "build/wltest '#ff0000'")
|
||||
try:
|
||||
s.wait_app_active(0)
|
||||
assert s.proc.poll() is None, f"log tail:\n{s.read_log()[-400:]}"
|
||||
w, h, _ = s.ppm_rgb(s.dump_host())
|
||||
assert (w, h) == (480, 320), \
|
||||
f"output should honour --screen-mode 480x320, got {w}x{h}"
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def test_standalone_headless_screen_mode_not_consumed_as_app():
|
||||
"""--screen-mode is stripped from argv, so it must not become a tab
|
||||
launcher (no 'starts with -' warning) while both real apps still boot."""
|
||||
s = _spawn_standalone({}, "--screen-mode", "640x480",
|
||||
"build/wltest '#ff0000'", "build/wltest '#0000ff'")
|
||||
try:
|
||||
s.wait_any_active()
|
||||
assert s.proc.poll() is None, f"log tail:\n{s.read_log()[-400:]}"
|
||||
assert "starts with '-'" not in s.read_log(), \
|
||||
f"--screen-mode leaked into the app list:\n{s.read_log()[-400:]}"
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
def test_standalone_headless_screen_mode_bad_value_exits(tmp_path):
|
||||
"""A malformed --screen-mode value is a startup error (rc 2) naming the
|
||||
problem, before any window or backend work."""
|
||||
env = dict(os.environ)
|
||||
for case in (["--screen-mode"], ["--screen-mode=abc"], ["--screen-mode", "1280"]):
|
||||
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,
|
||||
)
|
||||
out, _ = proc.communicate(timeout=15)
|
||||
assert proc.returncode == 2, \
|
||||
f"--screen-mode {case} should exit rc 2; rc={proc.returncode}\n{out}"
|
||||
assert b"--screen-mode" in out, \
|
||||
f"error should mention --screen-mode:\n{out}"
|
||||
|
||||
|
||||
def test_standalone_headless_overlay_font_initialized(standalone_session):
|
||||
"""Standalone builds the overlay glyph atlas with the wlr renderer, so tab
|
||||
titles are drawn next to their icons. Regressed when overlay_font_init()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue