Add support for creating utmp records

This patch adds support for creating utmp records using the ‘utempter’
helper binary from the ‘libutempter’ package.

* New config option ‘main.utempter’
* New meson command line option, -Ddefault-utempter-path. Defaults to
  auto-detecting the path.

The default value of the new ‘main.utempter’ config option depends on
the meson command line option ‘-Ddefault-utempter-path’.

If ‘main.utempter’ is *not* set to ‘none’, foot will try to execute
the utempter helper binary to create utmp records when a new terminal
is instantiated. The record is removed when the terminal instance is
destroyed.
This commit is contained in:
Daniel Eklöf 2022-09-23 20:24:04 +02:00
parent 77b74734a4
commit aa10b1d2da
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
11 changed files with 105 additions and 11 deletions

View file

@ -46,6 +46,8 @@
### Added ### Added
* Support for adjusting the thickness of regular underlines ([#1136][1136]). * Support for adjusting the thickness of regular underlines ([#1136][1136]).
* Support (optional) for utmp logging with libutempter.
[1136]: https://codeberg.org/dnkl/foot/issues/1136 [1136]: https://codeberg.org/dnkl/foot/issues/1136

View file

@ -45,6 +45,7 @@ subprojects.
* wayland (_client_ and _cursor_ libraries) * wayland (_client_ and _cursor_ libraries)
* xkbcommon * xkbcommon
* utf8proc (_optional_, needed for grapheme clustering) * utf8proc (_optional_, needed for grapheme clustering)
* libutempter (_optional_, needed for utmp logging)
* [fcft](https://codeberg.org/dnkl/fcft) [^1] * [fcft](https://codeberg.org/dnkl/fcft) [^1]
[^1]: can also be built as subprojects, in which case they are [^1]: can also be built as subprojects, in which case they are
@ -141,16 +142,17 @@ mkdir -p bld/release && cd bld/release
Available compile-time options: Available compile-time options:
| Option | Type | Default | Description | Extra dependencies | | Option | Type | Default | Description | Extra dependencies |
|--------------------------------------|---------|-------------------------|-------------------------------------------------------|--------------------| |--------------------------------------|---------|-------------------------|-----------------------------------------------------------|--------------------|
| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc | | `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc |
| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none | | `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none |
| `-Dime` | bool | `true` | Enables IME support | None | | `-Dime` | bool | `true` | Enables IME support | None |
| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc | | `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc |
| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) | | `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) |
| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none | | `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none |
| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None | | `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None |
| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None | | `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None |
| `-Ddefault-utempter-path` | feature | `auto` | Default path to utempter binary (none disables default) | libutempter |
Documentation includes the man pages, readme, changelog and license Documentation includes the man pages, readme, changelog and license
files. files.

View file

@ -944,6 +944,18 @@ parse_section_main(struct context *ctx)
else if (strcmp(key, "box-drawings-uses-font-glyphs") == 0) else if (strcmp(key, "box-drawings-uses-font-glyphs") == 0)
return value_to_bool(ctx, &conf->box_drawings_uses_font_glyphs); return value_to_bool(ctx, &conf->box_drawings_uses_font_glyphs);
else if (strcmp(key, "utempter") == 0) {
if (!value_to_str(ctx, &conf->utempter_path))
return false;
if (strcmp(conf->utempter_path, "none") == 0) {
free(conf->utempter_path);
conf->utempter_path = NULL;
}
return true;
}
else { else {
LOG_CONTEXTUAL_ERR("not a valid option: %s", key); LOG_CONTEXTUAL_ERR("not a valid option: %s", key);
return false; return false;
@ -2937,6 +2949,9 @@ config_load(struct config *conf, const char *conf_path,
}, },
.env_vars = tll_init(), .env_vars = tll_init(),
.utempter_path = (strlen(FOOT_DEFAULT_UTEMPTER_PATH) > 0
? xstrdup(FOOT_DEFAULT_UTEMPTER_PATH)
: NULL),
.notifications = tll_init(), .notifications = tll_init(),
}; };
@ -3225,6 +3240,9 @@ config_clone(const struct config *old)
tll_push_back(conf->env_vars, copy); tll_push_back(conf->env_vars, copy);
} }
conf->utempter_path =
old->utempter_path != NULL ? xstrdup(old->utempter_path) : NULL;
conf->notifications.length = 0; conf->notifications.length = 0;
conf->notifications.head = conf->notifications.tail = 0; conf->notifications.head = conf->notifications.tail = 0;
tll_foreach(old->notifications, it) { tll_foreach(old->notifications, it) {
@ -3291,6 +3309,7 @@ config_free(struct config *conf)
tll_remove(conf->env_vars, it); tll_remove(conf->env_vars, it);
} }
free(conf->utempter_path);
user_notifications_free(&conf->notifications); user_notifications_free(&conf->notifications);
} }

View file

@ -319,6 +319,8 @@ struct config {
env_var_list_t env_vars; env_var_list_t env_vars;
char *utempter_path;
struct { struct {
enum fcft_scaling_filter fcft_filter; enum fcft_scaling_filter fcft_filter;
bool overflowing_glyphs; bool overflowing_glyphs;

View file

@ -317,6 +317,10 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*.
(including SMT). Note that this is not always the best value. In (including SMT). Note that this is not always the best value. In
some cases, the number of physical _cores_ is better. some cases, the number of physical _cores_ is better.
*utempter*
Path to utempter helper binary. Set to *none* to disable utmp
records. Default: _@utempter@_.
# SECTION: environment # SECTION: environment
This section is used to define environment variables that will be set This section is used to define environment variables that will be set

View file

@ -2,9 +2,16 @@ sh = find_program('sh', native: true)
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true) scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
if utempter_path == ''
default_utempter_value = 'not set'
else
default_utempter_value = utempter_path
endif
conf_data = configuration_data( conf_data = configuration_data(
{ {
'default_terminfo': get_option('default-terminfo'), 'default_terminfo': get_option('default-terminfo'),
'utempter': default_utempter_value,
} }
) )

View file

@ -33,6 +33,7 @@
# word-delimiters=,│`|:"'()[]{}<> # word-delimiters=,│`|:"'()[]{}<>
# selection-target=primary # selection-target=primary
# workers=<number of logical CPUs> # workers=<number of logical CPUs>
# utempter=/usr/lib/utempter/utempter
[environment] [environment]
# name=value # name=value

View file

@ -16,9 +16,30 @@ if cc.has_function('memfd_create')
add_project_arguments('-DMEMFD_CREATE', language: 'c') add_project_arguments('-DMEMFD_CREATE', language: 'c')
endif endif
utempter_path = get_option('default-utempter-path')
if utempter_path == ''
utempter = find_program(
'utempter',
required: false,
dirs: [join_paths(get_option('prefix'), get_option('libdir'), 'utempter'),
join_paths(get_option('prefix'), get_option('libexecdir'), 'utempter'),
'/usr/lib/utempter',
'/usr/libexec/utempter',
'/lib/utempter']
)
if utempter.found()
utempter_path = utempter.full_path()
else
utempter_path = ''
endif
elif utempter_path == 'none'
utempter_path = ''
endif
add_project_arguments( add_project_arguments(
['-D_GNU_SOURCE=200809L', ['-D_GNU_SOURCE=200809L',
'-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo'))] + '-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo')),
'-DFOOT_DEFAULT_UTEMPTER_PATH="@0@"'.format(utempter_path)] +
(is_debug_build (is_debug_build
? ['-D_DEBUG'] ? ['-D_DEBUG']
: [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) + : [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) +
@ -321,6 +342,7 @@ summary(
'Themes': get_option('themes'), 'Themes': get_option('themes'),
'IME': get_option('ime'), 'IME': get_option('ime'),
'Grapheme clustering': utf8proc.found(), 'Grapheme clustering': utf8proc.found(),
'Utempter path': utempter_path,
'Build terminfo': tic.found(), 'Build terminfo': tic.found(),
'Terminfo install location': terminfo_install_location, 'Terminfo install location': terminfo_install_location,
'Default TERM': get_option('default-terminfo'), 'Default TERM': get_option('default-terminfo'),

View file

@ -21,3 +21,6 @@ option('custom-terminfo-install-location', type: 'string', value: '',
option('systemd-units-dir', type: 'string', value: '', option('systemd-units-dir', type: 'string', value: '',
description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}') description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}')
option('default-utempter-path', type: 'string', value: '',
description: 'Default path to utempter helper binary. Default: auto-detect')

View file

@ -203,6 +203,30 @@ fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data)
return true; return true;
} }
static bool
add_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx)
{
if (ptmx < 0)
return true;
if (conf->utempter_path == NULL)
return true;
char *const argv[] = {conf->utempter_path, "add", NULL};
return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL);
}
static bool
del_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx)
{
if (ptmx < 0)
return true;
if (conf->utempter_path == NULL)
return true;
char *const argv[] = {conf->utempter_path, "del", NULL};
return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL);
}
#if PTMX_TIMING #if PTMX_TIMING
static struct timespec last = {0}; static struct timespec last = {0};
#endif #endif
@ -326,6 +350,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
} }
if (hup) { if (hup) {
del_utmp_record(term->conf, term->reaper, term->ptmx);
fdm_del(fdm, fd); fdm_del(fdm, fd);
term->ptmx = -1; term->ptmx = -1;
} }
@ -1251,6 +1276,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
} }
term->font_line_height = conf->line_height; term->font_line_height = conf->line_height;
add_utmp_record(conf, reaper, ptmx);
/* Start the slave/client */ /* Start the slave/client */
if ((term->slave = slave_spawn( if ((term->slave = slave_spawn(
term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars, term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars,
@ -1514,6 +1541,8 @@ term_shutdown(struct terminal *term)
fdm_del(term->fdm, term->blink.fd); fdm_del(term->fdm, term->blink.fd);
fdm_del(term->fdm, term->flash.fd); fdm_del(term->fdm, term->flash.fd);
del_utmp_record(term->conf, term->reaper, term->ptmx);
if (term->window != NULL && term->window->is_configured) if (term->window != NULL && term->window->is_configured)
fdm_del(term->fdm, term->ptmx); fdm_del(term->fdm, term->ptmx);
else else
@ -1595,6 +1624,8 @@ term_destroy(struct terminal *term)
} }
} }
del_utmp_record(term->conf, term->reaper, term->ptmx);
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.title.timer_fd); fdm_del(term->fdm, term->render.title.timer_fd);

View file

@ -458,6 +458,7 @@ test_section_main(void)
test_string(&ctx, &parse_section_main, "shell", &conf.shell); test_string(&ctx, &parse_section_main, "shell", &conf.shell);
test_string(&ctx, &parse_section_main, "term", &conf.term); test_string(&ctx, &parse_section_main, "term", &conf.term);
test_string(&ctx, &parse_section_main, "app-id", &conf.app_id); test_string(&ctx, &parse_section_main, "app-id", &conf.app_id);
test_string(&ctx, &parse_section_main, "utempter", &conf.utempter_path);
test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters); test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters);