add wait-for-mapped option

This commit is contained in:
valoq 2026-05-28 15:22:37 +02:00
parent 4bf60d0fbc
commit 5b87139670
No known key found for this signature in database
9 changed files with 121 additions and 9 deletions

View file

@ -916,6 +916,9 @@ parse_section_main(struct context *ctx)
else if (streq(key, "login-shell")) else if (streq(key, "login-shell"))
return value_to_bool(ctx, &conf->login_shell); return value_to_bool(ctx, &conf->login_shell);
else if (streq(key, "wait-for-mapped"))
return value_to_bool(ctx, &conf->wait_for_mapped);
else if (streq(key, "title")) else if (streq(key, "title"))
return value_to_str(ctx, &conf->title); return value_to_str(ctx, &conf->title);
@ -3592,6 +3595,7 @@ config_load(struct config *conf, const char *conf_path,
.presentation_timings = false, .presentation_timings = false,
.selection_target = SELECTION_TARGET_PRIMARY, .selection_target = SELECTION_TARGET_PRIMARY,
.hold_at_exit = false, .hold_at_exit = false,
.wait_for_mapped = false,
.desktop_notifications = { .desktop_notifications = {
.command = { .command = {
.argv = {.args = NULL}, .argv = {.args = NULL},

View file

@ -237,6 +237,7 @@ struct config {
char32_t *word_delimiters; char32_t *word_delimiters;
bool login_shell; bool login_shell;
bool locked_title; bool locked_title;
bool wait_for_mapped;
struct { struct {
enum conf_size_type type; enum conf_size_type type;

View file

@ -86,6 +86,10 @@ the foot command line
*-L*,*--login-shell* *-L*,*--login-shell*
Start a login shell, by prepending a '-' to argv[0]. Start a login shell, by prepending a '-' to argv[0].
*--wait-for-mapped*
Defer spawning the client application until the window has been
mapped. See *wait-for-mapped* in *foot.ini*(5).
*--pty* *--pty*
Display an existing pty instead of creating one. This is useful Display an existing pty instead of creating one. This is useful
for interacting with VM consoles. for interacting with VM consoles.

View file

@ -43,6 +43,14 @@ empty string to be set, but it must be quoted: *KEY=""*
Boolean. If enabled, the shell will be launched as a login shell, Boolean. If enabled, the shell will be launched as a login shell,
by prepending a '-' to argv[0]. Default: _no_. by prepending a '-' to argv[0]. Default: _no_.
*wait-for-mapped*
Boolean. If enabled, defer spawning the client application until
the window has been mapped and the first frame has been
displayed. The client's first *TIOCGWINSZ* will then return the
final window size, working around startup issues in TUI
applications that mishandle early *SIGWINCH* signals.
Default: _no_.
*term* *term*
Value to set the environment variable *TERM* to. Default: Value to set the environment variable *TERM* to. Default:
_@default_terminfo@_ _@default_terminfo@_

7
main.c
View file

@ -88,6 +88,7 @@ print_usage(const char *prog_name)
" -m,--maximized start in maximized mode\n" " -m,--maximized start in maximized mode\n"
" -F,--fullscreen start in fullscreen mode\n" " -F,--fullscreen start in fullscreen mode\n"
" -L,--login-shell start shell as a login shell\n" " -L,--login-shell start shell as a login shell\n"
" --wait-for-mapped defer client spawn until window is mapped\n"
" --pty=PATH display an existing PTY instead of creating one\n" " --pty=PATH display an existing PTY instead of creating one\n"
" -D,--working-directory=DIR directory to start in (CWD)\n" " -D,--working-directory=DIR directory to start in (CWD)\n"
" -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n" " -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n"
@ -187,6 +188,7 @@ sanitize_signals(void)
enum { enum {
PTY_OPTION = CHAR_MAX + 1, PTY_OPTION = CHAR_MAX + 1,
TOPLEVEL_TAG_OPTION = CHAR_MAX + 2, TOPLEVEL_TAG_OPTION = CHAR_MAX + 2,
WAIT_FOR_MAPPED_OPTION = CHAR_MAX + 3,
}; };
int int
@ -218,6 +220,7 @@ main(int argc, char *const *argv)
{"app-id", required_argument, NULL, 'a'}, {"app-id", required_argument, NULL, 'a'},
{"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION}, {"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION},
{"login-shell", no_argument, NULL, 'L'}, {"login-shell", no_argument, NULL, 'L'},
{"wait-for-mapped", no_argument, NULL, WAIT_FOR_MAPPED_OPTION},
{"working-directory", required_argument, NULL, 'D'}, {"working-directory", required_argument, NULL, 'D'},
{"font", required_argument, NULL, 'f'}, {"font", required_argument, NULL, 'f'},
{"window-size-pixels", required_argument, NULL, 'w'}, {"window-size-pixels", required_argument, NULL, 'w'},
@ -280,6 +283,10 @@ main(int argc, char *const *argv)
tll_push_back(overrides, xstrdup("login-shell=yes")); tll_push_back(overrides, xstrdup("login-shell=yes"));
break; break;
case WAIT_FOR_MAPPED_OPTION:
tll_push_back(overrides, xstrdup("wait-for-mapped=yes"));
break;
case 'T': case 'T':
tll_push_back(overrides, xstrjoin("title=", optarg)); tll_push_back(overrides, xstrjoin("title=", optarg));
break; break;

View file

@ -4317,6 +4317,9 @@ frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_da
wl_callback_destroy(wl_callback); wl_callback_destroy(wl_callback);
term->window->frame_callback = NULL; term->window->frame_callback = NULL;
/* --wait-for-mapped: first frame done, on-screen size is final */
term_spawn_pending(term);
bool grid = term->render.pending.grid; bool grid = term->render.pending.grid;
bool csd = term->render.pending.csd; bool csd = term->render.pending.csd;
bool search = term->is_searching && term->render.pending.search; bool search = term->is_searching && term->render.pending.search;

View file

@ -294,6 +294,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
} }
xassert(term->interactive_resizing.grid == NULL); xassert(term->interactive_resizing.grid == NULL);
vt_from_slave(term, buf, count); vt_from_slave(term, buf, count);
} }
@ -1435,16 +1436,38 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
add_utmp_record(conf, reaper, ptmx); add_utmp_record(conf, reaper, ptmx);
if (!pty_path) { if (!pty_path) {
/* Start the slave/client */ if (conf->wait_for_mapped) {
if ((term->slave = slave_spawn( /* Deep-copy: server.c frees argv/envp after term_init() returns */
term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars, char **argv_copy = xcalloc(argc + 1, sizeof(char *));
conf->term, conf->shell, conf->login_shell, for (int i = 0; i < argc; i++)
&conf->notifications)) == -1) argv_copy[i] = xstrdup(argv[i]);
{
goto err;
}
reaper_add(term->reaper, term->slave, &fdm_client_terminated, term); char **envp_copy = NULL;
if (envp != NULL) {
size_t n = 0;
while (envp[n] != NULL) n++;
envp_copy = xcalloc(n + 1, sizeof(char *));
for (size_t i = 0; i < n; i++)
envp_copy[i] = xstrdup(envp[i]);
}
term->pending_spawn.armed = true;
term->pending_spawn.argc = argc;
term->pending_spawn.argv = argv_copy;
term->pending_spawn.envp = envp_copy;
term->pending_spawn.cwd = cwd != NULL ? xstrdup(cwd) : NULL;
} else {
/* Start the slave/client */
if ((term->slave = slave_spawn(
term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars,
conf->term, conf->shell, conf->login_shell,
&conf->notifications)) == -1)
{
goto err;
}
reaper_add(term->reaper, term->slave, &fdm_client_terminated, term);
}
} }
/* Guess scale; we're not mapped yet, so we don't know on which /* Guess scale; we're not mapped yet, so we don't know on which
@ -1517,6 +1540,54 @@ term_window_configured(struct terminal *term)
} }
} }
/* free deferred spawn args in --wait-for-mapped */
static void
pending_spawn_free(struct terminal *term)
{
for (int i = 0; i < term->pending_spawn.argc; i++)
free(term->pending_spawn.argv[i]);
free(term->pending_spawn.argv);
if (term->pending_spawn.envp != NULL) {
for (char **e = term->pending_spawn.envp; *e != NULL; e++)
free(*e);
free(term->pending_spawn.envp);
}
free(term->pending_spawn.cwd);
term->pending_spawn.argc = 0;
term->pending_spawn.argv = NULL;
term->pending_spawn.envp = NULL;
term->pending_spawn.cwd = NULL;
}
/* --wait-for-mapped: spawn the deferred client. Idempotent. */
void
term_spawn_pending(struct terminal *term)
{
if (!term->pending_spawn.armed || term->shutdown.in_progress)
return;
term->pending_spawn.armed = false;
term->slave = slave_spawn(
term->ptmx, term->pending_spawn.argc, term->pending_spawn.cwd,
term->pending_spawn.argv,
(const char *const *)term->pending_spawn.envp,
&term->conf->env_vars, term->conf->term, term->conf->shell,
term->conf->login_shell, &term->conf->notifications);
pending_spawn_free(term);
if (term->slave == -1) {
LOG_ERR("deferred slave_spawn() failed");
if (!term->conf->hold_at_exit)
term_shutdown(term);
return;
}
reaper_add(term->reaper, term->slave, &fdm_client_terminated, term);
}
/* /*
* Shutdown logic * Shutdown logic
* *
@ -1825,6 +1896,9 @@ term_destroy(struct terminal *term)
del_utmp_record(term->conf, term->reaper, term->ptmx); del_utmp_record(term->conf, term->reaper, term->ptmx);
/* Frees deferred spawn args if --wait-for-mapped never spawned; else no-op */
pending_spawn_free(term);
fdm_del(term->fdm, term->selection.auto_scroll.fd); fdm_del(term->fdm, term->selection.auto_scroll.fd);
fdm_del(term->fdm, term->render.app_sync_updates.timer_fd); fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
fdm_del(term->fdm, term->render.app_id.timer_fd); fdm_del(term->fdm, term->render.app_id.timer_fd);

View file

@ -419,6 +419,15 @@ struct terminal {
pid_t slave; pid_t slave;
int ptmx; int ptmx;
/* --wait-for-mapped: deferred slave_spawn() args (#453) */
struct {
bool armed;
int argc;
char **argv;
char **envp;
char *cwd;
} pending_spawn;
struct vt vt; struct vt vt;
struct grid *grid; struct grid *grid;
struct grid normal; struct grid normal;
@ -868,6 +877,7 @@ int term_pt_or_px_as_pixels(
void term_window_configured(struct terminal *term); void term_window_configured(struct terminal *term);
void term_spawn_pending(struct terminal *term);
void term_damage_rows(struct terminal *term, int start, int end); void term_damage_rows(struct terminal *term, int start, int end);
void term_damage_rows_in_view(struct terminal *term, int start, int end); void term_damage_rows_in_view(struct terminal *term, int start, int end);

View file

@ -488,6 +488,7 @@ test_section_main(void)
test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters); test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters);
test_boolean(&ctx, &parse_section_main, "login-shell", &conf.login_shell); test_boolean(&ctx, &parse_section_main, "login-shell", &conf.login_shell);
test_boolean(&ctx, &parse_section_main, "wait-for-mapped", &conf.wait_for_mapped);
test_boolean(&ctx, &parse_section_main, "box-drawings-uses-font-glyphs", &conf.box_drawings_uses_font_glyphs); test_boolean(&ctx, &parse_section_main, "box-drawings-uses-font-glyphs", &conf.box_drawings_uses_font_glyphs);
test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title); test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title);
test_boolean(&ctx, &parse_section_main, "dpi-aware", &conf.dpi_aware); test_boolean(&ctx, &parse_section_main, "dpi-aware", &conf.dpi_aware);