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"))
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"))
return value_to_str(ctx, &conf->title);
@ -3592,6 +3595,7 @@ config_load(struct config *conf, const char *conf_path,
.presentation_timings = false,
.selection_target = SELECTION_TARGET_PRIMARY,
.hold_at_exit = false,
.wait_for_mapped = false,
.desktop_notifications = {
.command = {
.argv = {.args = NULL},

View file

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

View file

@ -86,6 +86,10 @@ the foot command line
*-L*,*--login-shell*
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*
Display an existing pty instead of creating one. This is useful
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,
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*
Value to set the environment variable *TERM* to. Default:
_@default_terminfo@_

7
main.c
View file

@ -88,6 +88,7 @@ print_usage(const char *prog_name)
" -m,--maximized start in maximized mode\n"
" -F,--fullscreen start in fullscreen mode\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"
" -D,--working-directory=DIR directory to start in (CWD)\n"
" -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n"
@ -187,6 +188,7 @@ sanitize_signals(void)
enum {
PTY_OPTION = CHAR_MAX + 1,
TOPLEVEL_TAG_OPTION = CHAR_MAX + 2,
WAIT_FOR_MAPPED_OPTION = CHAR_MAX + 3,
};
int
@ -218,6 +220,7 @@ main(int argc, char *const *argv)
{"app-id", required_argument, NULL, 'a'},
{"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION},
{"login-shell", no_argument, NULL, 'L'},
{"wait-for-mapped", no_argument, NULL, WAIT_FOR_MAPPED_OPTION},
{"working-directory", required_argument, NULL, 'D'},
{"font", required_argument, NULL, 'f'},
{"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"));
break;
case WAIT_FOR_MAPPED_OPTION:
tll_push_back(overrides, xstrdup("wait-for-mapped=yes"));
break;
case 'T':
tll_push_back(overrides, xstrjoin("title=", optarg));
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);
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 csd = term->render.pending.csd;
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);
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);
if (!pty_path) {
/* 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;
}
if (conf->wait_for_mapped) {
/* Deep-copy: server.c frees argv/envp after term_init() returns */
char **argv_copy = xcalloc(argc + 1, sizeof(char *));
for (int i = 0; i < argc; i++)
argv_copy[i] = xstrdup(argv[i]);
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
@ -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
*
@ -1825,6 +1896,9 @@ term_destroy(struct terminal *term)
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->render.app_sync_updates.timer_fd);
fdm_del(term->fdm, term->render.app_id.timer_fd);

View file

@ -419,6 +419,15 @@ struct terminal {
pid_t slave;
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 grid *grid;
struct grid normal;
@ -868,6 +877,7 @@ int term_pt_or_px_as_pixels(
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_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_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, "locked-title", &conf.locked_title);
test_boolean(&ctx, &parse_section_main, "dpi-aware", &conf.dpi_aware);