foot/main.c

696 lines
21 KiB
C
Raw Normal View History

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <limits.h>
#include <locale.h>
#include <getopt.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
2020-06-09 20:35:21 +02:00
#include <sys/utsname.h>
#include <fcntl.h>
#include <fcft/fcft.h>
#define LOG_MODULE "main"
2019-10-28 18:35:16 +01:00
#define LOG_ENABLE_DBG 0
#include "log.h"
2019-06-13 15:19:10 +02:00
#include "config.h"
2019-10-27 11:46:18 +01:00
#include "fdm.h"
key-binding: new API, for handling sets of key bindings Up until now, our Wayland seats have been tracking key bindings. This makes sense, since the seat’s keymap determines how the key bindings are resolved. However, tying bindings to the seat/keymap alone isn’t enough, since we also depend on the current configuration (i.e. user settings) when resolving a key binding. This means configurations that doesn’t match the wayland object’s configuration, currently don’t resolve key bindings correctly. This applies to footclients where the user has overridden key bindings on the command line (e.g. --override key-bindings.foo=bar). Thus, to correctly resolve key bindings, each set of key bindings must be tied *both* to a seat/keymap, *and* a configuration. This patch introduces a key-binding manager, with an API to add/remove/lookup, and load/unload keymaps from sets of key bindings. In the API, sets are tied to a seat and terminal instance, since this makes the most sense (we need to instantiate, or incref a set whenever a new terminal instance is created). Internally, the set is tied to a seat and the terminal’s configuration. Sets are *added* when a new seat is added, and when a new terminal instance is created. Since there can only be one instance of each seat, sets are always removed when a seat is removed. Terminals on the other hand can re-use the same configuration (and typically do). Thus, sets ref-count the configuration. In other words, when instantiating a new terminal, we may not have to instantiate a new set of key bindings, but can often be incref:ed instead. Whenever the keymap changes on a seat, all key bindings sets associated with that seat reloads (re-resolves) their key bindings. Closes #931
2022-04-17 15:39:51 +02:00
#include "foot-features.h"
#include "key-binding.h"
#include "macros.h"
#include "reaper.h"
#include "render.h"
#include "server.h"
#include "shm.h"
#include "terminal.h"
#include "util.h"
#include "version.h"
#include "xmalloc.h"
#include "xsnprintf.h"
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
#error "char32_t does not use UTF-32"
#endif
static bool
fdm_sigint(struct fdm *fdm, int signo, void *data)
{
*(volatile sig_atomic_t *)data = true;
return true;
}
static const char *
version_and_features(void)
{
static char buf[256];
snprintf(buf, sizeof(buf),
"version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %cassertions",
FOOT_VERSION,
feature_pgo() ? '+' : '-',
feature_ime() ? '+' : '-',
feature_graphemes() ? '+' : '-',
feature_xdg_toplevel_icon() ? '+' : '-',
feature_assertions() ? '+' : '-');
return buf;
}
static void
print_usage(const char *prog_name)
{
static const char options[] =
"\nOptions:\n"
" -c,--config=PATH load configuration from PATH ($XDG_CONFIG_HOME/foot/foot.ini)\n"
" -C,--check-config verify configuration, exit with 0 if ok, otherwise exit with 1\n"
" -o,--override=[section.]key=value override configuration option\n"
" -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n"
" -t,--term=TERM value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n"
" -T,--title=TITLE initial window title (foot)\n"
" -a,--app-id=ID window application ID (foot)\n"
" -m,--maximized start in maximized mode\n"
" -F,--fullscreen start in fullscreen mode\n"
" -L,--login-shell start shell as a login shell\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"
" -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n"
" -s,--server[=PATH] run as a server (use 'footclient' to start terminals).\n"
" Without PATH, $XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock will be used.\n"
" -H,--hold remain open after child process exits\n"
" -p,--print-pid=FILE|FD print PID to file or FD (only applicable in server mode)\n"
change default log level to WARNING The default foot output looks like this, in Debian testing "bookworm" at the time of writing: anarcat@angela:pubpaste$ foot true info: main.c:421: version: 1.13.1 +pgo +ime +graphemes -assertions info: main.c:428: arch: Linux x86_64/64-bit info: main.c:440: locale: fr_CA.UTF-8 info: config.c:3003: loading configuration from /home/anarcat/.config/foot/foot.ini info: fcft.c:338: fcft: 3.1.5 +graphemes -runs +svg(nanosvg) -assertions info: fcft.c:377: fontconfig: 2.13.1, freetype: 2.12.1, harfbuzz: 5.2.0 info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Regular.otf: size=8.00pt/8px, dpi=75.00 info: wayland.c:1353: eDP-1: 2256x1504+0x0@60Hz 0x095F 13.32" scale=2 PPI=205x214 (physical) PPI=136x143 (logical), DPI=271.31 info: wayland.c:1509: requesting SSD decorations info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Bold.otf: size=24.00pt/32px, dpi=96.00 info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Regular.otf: size=24.00pt/32px, dpi=96.00 info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Bold.otf: size=24.00pt/32px, dpi=96.00 info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Regular.otf: size=24.00pt/32px, dpi=96.00 info: terminal.c:700: cell width=19, height=39 info: terminal.c:588: using 16 rendering threads info: wayland.c:859: using SSD decorations info: main.c:680: goodbye anarcat@angela:pubpaste$ That's 17 lines of output that are *mostly* useless for most use cases. I might understand having this output during the project's startup, when it's helpful for diagnostics, but now Foot just mostly works everywhere, and I've never had a use for any of that stuff in the (arguably short) time I've been using Foot so far. And if I do, there's the `--log-level` commandline option to tweak this. At first, I looked at tweaking the log level through the config file. But as explained in this issue: https://codeberg.org/dnkl/foot/issues/1142 ... there's a chicken and egg problem there that makes it hard to implement and possibly confusing for users as well. There's also the possibility for users to change the shortcut with which they start foot, for example a `.desktop` file so that menu systems that support those start foot properly. But that only works in that environment, and not through the so many things that will just call `foot` and hope it will do the right thing. In my case, I have `foot` hardcoded in a lot of places now, between sway and waybar, and this is only going to grow. Others have suggested adding the flag to a $TERMINAL global variable, but that won't help .desktop users. So, instead of playing whack-a-mole with the log levels, just make it so that, by default, foot is silent. This is actually one of the [basics of UNIX philosophy][1]: > Rule of Silence: When a program has nothing surprising to say, it > should say nothing. And yes, I am aware I am severely violating that principle by writing a way too long commit log for a one-line patch, but there you go, I figured it was good to document the why of this properly. [1]: https://web.archive.org/web/20031102053334/http://www.faqs.org/docs/artu/ch01s06.html
2022-11-18 11:07:16 -05:00
" -d,--log-level={info|warning|error|none} log level (warning)\n"
" -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n"
" -S,--log-no-syslog disable syslog logging (only applicable in server mode)\n"
" -v,--version show the version number and quit\n"
" -e ignored (for compatibility with xterm -e)\n";
printf("Usage: %s [OPTIONS...]\n", prog_name);
printf("Usage: %s [OPTIONS...] command [ARGS...]\n", prog_name);
puts(options);
}
2019-12-19 07:25:05 +01:00
bool
locale_is_utf8(void)
{
static const char u8[] = u8"ö";
xassert(strlen(u8) == 2);
2019-12-19 07:25:05 +01:00
char32_t w;
if (mbrtoc32(&w, u8, 2, &(mbstate_t){0}) != 2)
2019-12-19 07:25:05 +01:00
return false;
return w == U'ö';
2019-12-19 07:25:05 +01:00
}
struct shutdown_context {
struct terminal **term;
int exit_code;
};
static void
term_shutdown_cb(void *data, int exit_code)
{
struct shutdown_context *ctx = data;
*ctx->term = NULL;
ctx->exit_code = exit_code;
}
static bool
print_pid(const char *pid_file, bool *unlink_at_exit)
{
LOG_DBG("printing PID to %s", pid_file);
errno = 0;
char *end;
int pid_fd = strtoul(pid_file, &end, 10);
if (errno != 0 || *end != '\0') {
if ((pid_fd = open(pid_file,
O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {
LOG_ERRNO("%s: failed to open", pid_file);
return false;
} else
*unlink_at_exit = true;
}
if (pid_fd >= 0) {
char pid[32];
size_t n = xsnprintf(pid, sizeof(pid), "%u\n", getpid());
ssize_t bytes = write(pid_fd, pid, n);
close(pid_fd);
if (bytes < 0) {
LOG_ERRNO("failed to write PID to FD=%u", pid_fd);
return false;
}
LOG_DBG("wrote %zd bytes to FD=%d", bytes, pid_fd);
return true;
} else
return false;
}
static void
sanitize_signals(void)
{
sigset_t mask;
sigemptyset(&mask);
sigprocmask(SIG_SETMASK, &mask, NULL);
struct sigaction dfl = {.sa_handler = SIG_DFL};
sigemptyset(&dfl.sa_mask);
for (int i = 1; i < SIGRTMAX; i++)
sigaction(i, &dfl, NULL);
}
enum {
PTY_OPTION = CHAR_MAX + 1,
};
int
main(int argc, char *const *argv)
{
/* Custom exit code, to enable users to differentiate between foot
2023-10-03 14:11:55 +02:00
* itself failing, and the client application failing */
static const int foot_exit_failure = -26;
int ret = foot_exit_failure;
sanitize_signals();
/* XDG startup notifications */
const char *token = getenv("XDG_ACTIVATION_TOKEN");
unsetenv("XDG_ACTIVATION_TOKEN");
/* Startup notifications; we don't support it, but must ensure we
* don't pass this on to programs launched by us */
unsetenv("DESKTOP_STARTUP_ID");
const char *const prog_name = argc > 0 ? argv[0] : "<nullptr>";
static const struct option longopts[] = {
{"config", required_argument, NULL, 'c'},
{"check-config", no_argument, NULL, 'C'},
{"override", required_argument, NULL, 'o'},
{"term", required_argument, NULL, 't'},
{"title", required_argument, NULL, 'T'},
{"app-id", required_argument, NULL, 'a'},
{"login-shell", no_argument, NULL, 'L'},
{"working-directory", required_argument, NULL, 'D'},
{"font", required_argument, NULL, 'f'},
{"window-size-pixels", required_argument, NULL, 'w'},
{"window-size-chars", required_argument, NULL, 'W'},
{"server", optional_argument, NULL, 's'},
{"hold", no_argument, NULL, 'H'},
{"maximized", no_argument, NULL, 'm'},
{"fullscreen", no_argument, NULL, 'F'},
{"presentation-timings", no_argument, NULL, 'P'}, /* Undocumented */
{"pty", required_argument, NULL, PTY_OPTION},
{"print-pid", required_argument, NULL, 'p'},
{"log-level", required_argument, NULL, 'd'},
{"log-colorize", optional_argument, NULL, 'l'},
{"log-no-syslog", no_argument, NULL, 'S'},
{"version", no_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'},
{NULL, no_argument, NULL, 0},
};
bool check_config = false;
const char *conf_path = NULL;
const char *custom_cwd = NULL;
const char *pty_path = NULL;
bool as_server = false;
const char *conf_server_socket_path = NULL;
bool presentation_timings = false;
bool hold = false;
bool unlink_pid_file = false;
const char *pid_file = NULL;
change default log level to WARNING The default foot output looks like this, in Debian testing "bookworm" at the time of writing: anarcat@angela:pubpaste$ foot true info: main.c:421: version: 1.13.1 +pgo +ime +graphemes -assertions info: main.c:428: arch: Linux x86_64/64-bit info: main.c:440: locale: fr_CA.UTF-8 info: config.c:3003: loading configuration from /home/anarcat/.config/foot/foot.ini info: fcft.c:338: fcft: 3.1.5 +graphemes -runs +svg(nanosvg) -assertions info: fcft.c:377: fontconfig: 2.13.1, freetype: 2.12.1, harfbuzz: 5.2.0 info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Regular.otf: size=8.00pt/8px, dpi=75.00 info: wayland.c:1353: eDP-1: 2256x1504+0x0@60Hz 0x095F 13.32" scale=2 PPI=205x214 (physical) PPI=136x143 (logical), DPI=271.31 info: wayland.c:1509: requesting SSD decorations info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Bold.otf: size=24.00pt/32px, dpi=96.00 info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Regular.otf: size=24.00pt/32px, dpi=96.00 info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Bold.otf: size=24.00pt/32px, dpi=96.00 info: fcft.c:838: /home/anarcat/.local/share/fonts/Fira-4.202/otf/FiraMono-Regular.otf: size=24.00pt/32px, dpi=96.00 info: terminal.c:700: cell width=19, height=39 info: terminal.c:588: using 16 rendering threads info: wayland.c:859: using SSD decorations info: main.c:680: goodbye anarcat@angela:pubpaste$ That's 17 lines of output that are *mostly* useless for most use cases. I might understand having this output during the project's startup, when it's helpful for diagnostics, but now Foot just mostly works everywhere, and I've never had a use for any of that stuff in the (arguably short) time I've been using Foot so far. And if I do, there's the `--log-level` commandline option to tweak this. At first, I looked at tweaking the log level through the config file. But as explained in this issue: https://codeberg.org/dnkl/foot/issues/1142 ... there's a chicken and egg problem there that makes it hard to implement and possibly confusing for users as well. There's also the possibility for users to change the shortcut with which they start foot, for example a `.desktop` file so that menu systems that support those start foot properly. But that only works in that environment, and not through the so many things that will just call `foot` and hope it will do the right thing. In my case, I have `foot` hardcoded in a lot of places now, between sway and waybar, and this is only going to grow. Others have suggested adding the flag to a $TERMINAL global variable, but that won't help .desktop users. So, instead of playing whack-a-mole with the log levels, just make it so that, by default, foot is silent. This is actually one of the [basics of UNIX philosophy][1]: > Rule of Silence: When a program has nothing surprising to say, it > should say nothing. And yes, I am aware I am severely violating that principle by writing a way too long commit log for a one-line patch, but there you go, I figured it was good to document the why of this properly. [1]: https://web.archive.org/web/20031102053334/http://www.faqs.org/docs/artu/ch01s06.html
2022-11-18 11:07:16 -05:00
enum log_class log_level = LOG_CLASS_WARNING;
enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
bool log_syslog = true;
user_notifications_t user_notifications = tll_init();
config_override_t overrides = tll_init();
while (true) {
int c = getopt_long(argc, argv, "+c:Co:t:T:a:LD:f:w:W:s::HmFPp:d:l::Sveh", longopts, NULL);
if (c == -1)
break;
switch (c) {
case 'c':
conf_path = optarg;
break;
case 'C':
check_config = true;
break;
case 'o':
tll_push_back(overrides, xstrdup(optarg));
break;
case 't':
tll_push_back(overrides, xstrjoin("term=", optarg));
break;
case 'L':
tll_push_back(overrides, xstrdup("login-shell=yes"));
break;
case 'T':
tll_push_back(overrides, xstrjoin("title=", optarg));
break;
case 'a':
tll_push_back(overrides, xstrjoin("app-id=", optarg));
break;
case 'D': {
struct stat st;
if (stat(optarg, &st) < 0 || !(st.st_mode & S_IFDIR)) {
fprintf(stderr, "error: %s: not a directory\n", optarg);
return ret;
}
custom_cwd = optarg;
break;
}
case 'f': {
char *font_override = xstrjoin("font=", optarg);
tll_push_back(overrides, font_override);
break;
}
case 'w': {
unsigned width, height;
if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
fprintf(stderr, "error: invalid window-size-pixels: %s\n", optarg);
return ret;
}
tll_push_back(
overrides, xasprintf("initial-window-size-pixels=%ux%u",
width, height));
break;
}
case 'W': {
unsigned width, height;
if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
fprintf(stderr, "error: invalid window-size-chars: %s\n", optarg);
return ret;
}
tll_push_back(
overrides, xasprintf("initial-window-size-chars=%ux%u",
width, height));
break;
}
case 's':
as_server = true;
if (optarg != NULL)
conf_server_socket_path = optarg;
break;
case PTY_OPTION:
pty_path = optarg;
break;
case 'P':
presentation_timings = true;
break;
case 'H':
hold = true;
break;
case 'm':
tll_push_back(overrides, xstrdup("initial-window-mode=maximized"));
break;
case 'F':
tll_push_back(overrides, xstrdup("initial-window-mode=fullscreen"));
break;
case 'p':
pid_file = optarg;
break;
case 'd': {
int lvl = log_level_from_string(optarg);
if (unlikely(lvl < 0)) {
fprintf(
stderr,
"-d,--log-level: %s: argument must be one of %s\n",
optarg,
log_level_string_hint());
return ret;
}
log_level = lvl;
break;
}
case 'l':
if (optarg == NULL || streq(optarg, "auto"))
log_colorize = LOG_COLORIZE_AUTO;
else if (streq(optarg, "never"))
log_colorize = LOG_COLORIZE_NEVER;
else if (streq(optarg, "always"))
log_colorize = LOG_COLORIZE_ALWAYS;
else {
fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
return ret;
}
break;
case 'S':
log_syslog = false;
break;
case 'v':
printf("foot %s\n", version_and_features());
return EXIT_SUCCESS;
case 'h':
print_usage(prog_name);
return EXIT_SUCCESS;
case 'e':
break;
case '?':
return ret;
}
}
if (as_server && pty_path) {
fputs("error: --pty is incompatible with server mode\n", stderr);
return ret;
}
log_init(log_colorize, as_server && log_syslog,
as_server ? LOG_FACILITY_DAEMON : LOG_FACILITY_USER, log_level);
2019-12-17 19:07:28 +01:00
if (argc > 0) {
argc -= optind;
argv += optind;
}
LOG_INFO("%s", version_and_features());
2020-03-15 11:41:24 +01:00
2020-06-09 20:35:21 +02:00
{
struct utsname name;
if (uname(&name) < 0)
LOG_ERRNO("uname() failed");
else
LOG_INFO("arch: %s %s/%zu-bit",
name.sysname, name.machine, sizeof(void *) * 8);
2020-06-09 20:35:21 +02:00
}
srand(time(NULL));
const char *locale = setlocale(LC_CTYPE, "");
if (locale == NULL) {
/*
* If the user has configured an invalid locale, or a name of a locale
* that does not exist on this system, then the above call may return
* NULL. We should just continue with the fallback method below.
*/
LOG_ERR("setlocale() failed. The most common cause is that the "
"configured locale is not available, or has been misspelled");
}
LOG_INFO("locale: %s", locale != NULL ? locale : "<invalid>");
bool bad_locale = locale == NULL || !locale_is_utf8();
if (bad_locale) {
static const char fallback_locales[][12] = {
"C.UTF-8",
"en_US.UTF-8",
};
char *saved_locale = locale != NULL ? xstrdup(locale) : NULL;
/*
* Try to force an UTF-8 locale. If we succeed, launch the
* user's shell as usual, but add a user-notification saying
* the locale has been changed.
*/
for (size_t i = 0; i < ALEN(fallback_locales); i++) {
const char *const fallback_locale = fallback_locales[i];
if (setlocale(LC_CTYPE, fallback_locale) != NULL) {
if (saved_locale != NULL) {
LOG_WARN(
"'%s' is not a UTF-8 locale, falling back to '%s'",
saved_locale, fallback_locale);
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_WARNING,
"'%s' is not a UTF-8 locale, falling back to '%s'",
saved_locale, fallback_locale);
} else {
LOG_WARN(
"invalid locale, falling back to '%s'", fallback_locale);
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_WARNING,
"invalid locale, falling back to '%s'", fallback_locale);
}
bad_locale = false;
break;
}
}
if (bad_locale) {
if (saved_locale != NULL) {
LOG_ERR(
"'%s' is not a UTF-8 locale, and failed to find a fallback",
saved_locale);
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_ERROR,
"'%s' is not a UTF-8 locale, and failed to find a fallback",
saved_locale);
} else {
LOG_ERR("invalid locale, and failed to find a fallback");
user_notification_add_fmt(
&user_notifications, USER_NOTIFICATION_ERROR,
"invalid locale, and failed to find a fallback");
}
}
free(saved_locale);
}
struct config conf = {NULL};
bool conf_successful = config_load(
&conf, conf_path, &user_notifications, &overrides, check_config, as_server);
tll_free_and_free(overrides, free);
if (!conf_successful) {
config_free(&conf);
return ret;
}
if (check_config) {
config_free(&conf);
return EXIT_SUCCESS;
}
_Static_assert((int)LOG_CLASS_ERROR == (int)FCFT_LOG_CLASS_ERROR,
"fcft log level enum offset");
_Static_assert((int)LOG_COLORIZE_ALWAYS == (int)FCFT_LOG_COLORIZE_ALWAYS,
"fcft colorize enum mismatch");
fcft_init(
(enum fcft_log_colorize)log_colorize,
as_server && log_syslog,
(enum fcft_log_class)log_level);
fcft_set_scaling_filter(conf.tweak.fcft_filter);
if (conf_server_socket_path != NULL) {
free(conf.server_socket_path);
conf.server_socket_path = xstrdup(conf_server_socket_path);
}
conf.presentation_timings = presentation_timings;
conf.hold_at_exit = hold;
if (conf.tweak.font_monospace_warn && conf.fonts[0].count > 0) {
check_if_font_is_monospaced(
conf.fonts[0].arr[0].pattern, &conf.notifications);
}
if (bad_locale) {
static char *const bad_locale_fake_argv[] = {"/bin/sh", "-c", "", NULL};
argc = 1;
argv = bad_locale_fake_argv;
conf.hold_at_exit = true;
}
struct fdm *fdm = NULL;
struct reaper *reaper = NULL;
key-binding: new API, for handling sets of key bindings Up until now, our Wayland seats have been tracking key bindings. This makes sense, since the seat’s keymap determines how the key bindings are resolved. However, tying bindings to the seat/keymap alone isn’t enough, since we also depend on the current configuration (i.e. user settings) when resolving a key binding. This means configurations that doesn’t match the wayland object’s configuration, currently don’t resolve key bindings correctly. This applies to footclients where the user has overridden key bindings on the command line (e.g. --override key-bindings.foo=bar). Thus, to correctly resolve key bindings, each set of key bindings must be tied *both* to a seat/keymap, *and* a configuration. This patch introduces a key-binding manager, with an API to add/remove/lookup, and load/unload keymaps from sets of key bindings. In the API, sets are tied to a seat and terminal instance, since this makes the most sense (we need to instantiate, or incref a set whenever a new terminal instance is created). Internally, the set is tied to a seat and the terminal’s configuration. Sets are *added* when a new seat is added, and when a new terminal instance is created. Since there can only be one instance of each seat, sets are always removed when a seat is removed. Terminals on the other hand can re-use the same configuration (and typically do). Thus, sets ref-count the configuration. In other words, when instantiating a new terminal, we may not have to instantiate a new set of key bindings, but can often be incref:ed instead. Whenever the keymap changes on a seat, all key bindings sets associated with that seat reloads (re-resolves) their key bindings. Closes #931
2022-04-17 15:39:51 +02:00
struct key_binding_manager *key_binding_manager = NULL;
struct wayland *wayl = NULL;
struct renderer *renderer = NULL;
struct terminal *term = NULL;
struct server *server = NULL;
struct shutdown_context shutdown_ctx = {.term = &term, .exit_code = foot_exit_failure};
const char *cwd = custom_cwd;
char *_cwd = NULL;
if (cwd == NULL) {
2020-02-20 18:46:45 +01:00
errno = 0;
size_t buf_len = 1024;
do {
_cwd = xrealloc(_cwd, buf_len);
if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) {
2020-02-20 18:46:45 +01:00
LOG_ERRNO("failed to get current working directory");
goto out;
}
buf_len *= 2;
} while (errno == ERANGE);
cwd = _cwd;
}
const char *pwd = getenv("PWD");
if (pwd != NULL) {
char *resolved_path_cwd = realpath(cwd, NULL);
char *resolved_path_pwd = realpath(pwd, NULL);
if (resolved_path_cwd != NULL &&
resolved_path_pwd != NULL &&
streq(resolved_path_cwd, resolved_path_pwd))
{
/*
* The resolved path of $PWD matches the resolved path of
* the *actual* working directory - use $PWD.
*
* This makes a difference when $PWD refers to a symlink.
*/
cwd = pwd;
}
free(resolved_path_cwd);
free(resolved_path_pwd);
}
shm_set_max_pool_size(conf.tweak.max_shm_pool_size);
if ((fdm = fdm_init()) == NULL)
goto out;
if ((reaper = reaper_init(fdm)) == NULL)
goto out;
key-binding: new API, for handling sets of key bindings Up until now, our Wayland seats have been tracking key bindings. This makes sense, since the seat’s keymap determines how the key bindings are resolved. However, tying bindings to the seat/keymap alone isn’t enough, since we also depend on the current configuration (i.e. user settings) when resolving a key binding. This means configurations that doesn’t match the wayland object’s configuration, currently don’t resolve key bindings correctly. This applies to footclients where the user has overridden key bindings on the command line (e.g. --override key-bindings.foo=bar). Thus, to correctly resolve key bindings, each set of key bindings must be tied *both* to a seat/keymap, *and* a configuration. This patch introduces a key-binding manager, with an API to add/remove/lookup, and load/unload keymaps from sets of key bindings. In the API, sets are tied to a seat and terminal instance, since this makes the most sense (we need to instantiate, or incref a set whenever a new terminal instance is created). Internally, the set is tied to a seat and the terminal’s configuration. Sets are *added* when a new seat is added, and when a new terminal instance is created. Since there can only be one instance of each seat, sets are always removed when a seat is removed. Terminals on the other hand can re-use the same configuration (and typically do). Thus, sets ref-count the configuration. In other words, when instantiating a new terminal, we may not have to instantiate a new set of key bindings, but can often be incref:ed instead. Whenever the keymap changes on a seat, all key bindings sets associated with that seat reloads (re-resolves) their key bindings. Closes #931
2022-04-17 15:39:51 +02:00
if ((key_binding_manager = key_binding_manager_new()) == NULL)
goto out;
if ((wayl = wayl_init(
fdm, key_binding_manager, conf.presentation_timings)) == NULL)
{
goto out;
}
if ((renderer = render_init(fdm, wayl)) == NULL)
goto out;
if (!as_server && (term = term_init(
&conf, fdm, reaper, wayl, "foot", cwd, token, pty_path,
argc, argv, NULL,
&term_shutdown_cb, &shutdown_ctx)) == NULL) {
goto out;
}
free(_cwd);
_cwd = NULL;
if (as_server && (server = server_init(&conf, fdm, reaper, wayl)) == NULL)
goto out;
volatile sig_atomic_t aborted = false;
if (!fdm_signal_add(fdm, SIGINT, &fdm_sigint, (void *)&aborted) ||
!fdm_signal_add(fdm, SIGTERM, &fdm_sigint, (void *)&aborted))
{
goto out;
}
struct sigaction sig_ign = {.sa_handler = SIG_IGN};
sigemptyset(&sig_ign.sa_mask);
if (sigaction(SIGHUP, &sig_ign, NULL) < 0 ||
sigaction(SIGPIPE, &sig_ign, NULL) < 0)
{
LOG_ERRNO("failed to ignore SIGHUP+SIGPIPE");
2019-11-03 13:23:49 +01:00
goto out;
}
if (as_server)
LOG_INFO("running as server; launch terminals by running footclient");
if (as_server && pid_file != NULL) {
if (!print_pid(pid_file, &unlink_pid_file))
goto out;
}
ret = EXIT_SUCCESS;
while (likely(!aborted && (as_server || tll_length(wayl->terms) > 0))) {
if (unlikely(!fdm_poll(fdm))) {
ret = foot_exit_failure;
break;
}
2019-10-27 11:46:18 +01:00
}
2019-10-27 11:46:18 +01:00
out:
free(_cwd);
server_destroy(server);
term_destroy(term);
shm_fini();
render_destroy(renderer);
wayl_destroy(wayl);
key-binding: new API, for handling sets of key bindings Up until now, our Wayland seats have been tracking key bindings. This makes sense, since the seat’s keymap determines how the key bindings are resolved. However, tying bindings to the seat/keymap alone isn’t enough, since we also depend on the current configuration (i.e. user settings) when resolving a key binding. This means configurations that doesn’t match the wayland object’s configuration, currently don’t resolve key bindings correctly. This applies to footclients where the user has overridden key bindings on the command line (e.g. --override key-bindings.foo=bar). Thus, to correctly resolve key bindings, each set of key bindings must be tied *both* to a seat/keymap, *and* a configuration. This patch introduces a key-binding manager, with an API to add/remove/lookup, and load/unload keymaps from sets of key bindings. In the API, sets are tied to a seat and terminal instance, since this makes the most sense (we need to instantiate, or incref a set whenever a new terminal instance is created). Internally, the set is tied to a seat and the terminal’s configuration. Sets are *added* when a new seat is added, and when a new terminal instance is created. Since there can only be one instance of each seat, sets are always removed when a seat is removed. Terminals on the other hand can re-use the same configuration (and typically do). Thus, sets ref-count the configuration. In other words, when instantiating a new terminal, we may not have to instantiate a new set of key bindings, but can often be incref:ed instead. Whenever the keymap changes on a seat, all key bindings sets associated with that seat reloads (re-resolves) their key bindings. Closes #931
2022-04-17 15:39:51 +02:00
key_binding_manager_destroy(key_binding_manager);
reaper_destroy(reaper);
fdm_signal_del(fdm, SIGTERM);
fdm_signal_del(fdm, SIGINT);
2019-10-27 11:46:18 +01:00
fdm_destroy(fdm);
config_free(&conf);
2019-12-19 07:25:05 +01:00
if (unlink_pid_file)
unlink(pid_file);
2019-12-19 07:25:05 +01:00
LOG_INFO("goodbye");
fcft_fini();
log_deinit();
return ret == EXIT_SUCCESS && !as_server ? shutdown_ctx.exit_code : ret;
}
UNITTEST
{
char *s = xstrjoin("foo", "bar");
xassert(streq(s, "foobar"));
free(s);
s = xstrjoin3("foo", " ", "bar");
xassert(streq(s, "foo bar"));
free(s);
s = xstrjoin3("foo", ",", "bar");
xassert(streq(s, "foo,bar"));
free(s);
s = xstrjoin3("foo", "bar", "baz");
xassert(streq(s, "foobarbaz"));
free(s);
}