From 6e94da182c285a91c9cd9e9b4978aa12fc014604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Feb 2021 10:44:42 +0100 Subject: [PATCH 01/19] changelog: add section for 1.6.4 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ab193c8..1376197b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [1.6.4](#1-6-4) * [1.6.3](#1-6-3) * [1.6.2](#1-6-2) * [1.6.1](#1-6-1) @@ -21,6 +22,16 @@ * [1.2.0](#1-2-0) +## 1.6.4 +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.6.3 ### Added From 8a32c5c5c1c1fb5bef33c2e194b3967740a8052f Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Fri, 29 Jan 2021 12:57:03 -0700 Subject: [PATCH 02/19] ime: Reset terminal's IME state on text_input.leave To reproduce issue: - Launch an IME from inside foot - Type in some preedit text - Use Ctrl-C to close the IME - IME text stays in terminal until focus is changed --- CHANGELOG.md | 7 +++++++ ime.c | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1376197b..339a9835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,12 +25,19 @@ ## 1.6.4 ### Added ### Changed + +* The IME state no longer stays stuck in the terminal if the IME goes + away during preedit. + + ### Deprecated ### Removed ### Fixed ### Security ### Contributors +* [tdeo](https://codeberg.org/tdeo) + ## 1.6.3 diff --git a/ime.c b/ime.c index d420fc9f..d851c26f 100644 --- a/ime.c +++ b/ime.c @@ -39,6 +39,11 @@ leave(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, { struct seat *seat = data; LOG_DBG("leave: seat=%s", seat->name); + + struct terminal *term = seat->kbd_focus; + if (term != NULL) + term_ime_reset(term); + ime_disable(seat); } From c49eac2147de25dedd91ad624d4bc45a203f4f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Feb 2021 19:42:55 +0100 Subject: [PATCH 03/19] meson: convert -Dterminfo from a boolean to a feature option Patch from Jan Beich --- .builds/freebsd-x64.yml | 4 ++-- doc/foot.ini.5.scd | 2 +- foot.ini | 2 +- meson_options.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.builds/freebsd-x64.yml b/.builds/freebsd-x64.yml index 8481c67f..8615ecef 100644 --- a/.builds/freebsd-x64.yml +++ b/.builds/freebsd-x64.yml @@ -34,11 +34,11 @@ tasks: ln -s ../../fcft foot/subprojects/fcft - debug: | mkdir -p bld/debug - meson --buildtype=debug -Dterminfo=false -Dfcft:text-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug + meson --buildtype=debug -Dterminfo=disabled -Dfcft:text-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug ninja -C bld/debug -k0 meson test -C bld/debug --print-errorlogs - release: | mkdir -p bld/release - meson --buildtype=minsize -Dterminfo=false -Dfcft:text-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release + meson --buildtype=minsize -Dterminfo=disabled -Dfcft:text-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release ninja -C bld/release -k0 meson test -C bld/release --print-errorlogs diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 94596fdf..8d9960e0 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -112,7 +112,7 @@ in this order: *term* Value to set the environment variable *TERM* to. Default: _foot_ - or _xterm-256color_ if built with _-Dterminfo=false_ + or _xterm-256color_ if built with _-Dterminfo=disabled_ *title* Initial window title. Default: _foot_. diff --git a/foot.ini b/foot.ini index 98e25b93..baf0df84 100644 --- a/foot.ini +++ b/foot.ini @@ -10,7 +10,7 @@ # initial-window-mode=windowed # pad=2x2 # shell=$SHELL (if set, otherwise user's default shell from /etc/passwd) -# term=foot (or xterm-256color if built with -Dterminfo=false) +# term=foot (or xterm-256color if built with -Dterminfo=disabled) # login-shell=no # workers= # bold-text-in-bright=no diff --git a/meson_options.txt b/meson_options.txt index 0a5b0a3d..b21bc7a4 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,2 +1,2 @@ option('ime', type: 'boolean', value: true, description: 'IME (Input Method Editor) support') -option('terminfo', type: 'boolean', value: true, description: 'Install terminfo') +option('terminfo', type: 'feature', description: 'Install terminfo') From a60b7babd3cf047f4be264a02ce84c1ac3d1d526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Feb 2021 19:44:57 +0100 Subject: [PATCH 04/19] changelog: -Dterminfo changed from boolean to feature option --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 339a9835..5400e2b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ * The IME state no longer stays stuck in the terminal if the IME goes away during preedit. +* `-Dterminfo` changed from a `boolean` to a `feature` option. ### Deprecated @@ -37,6 +38,7 @@ ### Contributors * [tdeo](https://codeberg.org/tdeo) +* jbeich ## 1.6.3 From ac46e5844805c6df0ec20d98e1cc678065717dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Feb 2021 19:46:06 +0100 Subject: [PATCH 05/19] install: add -Dterminfo to list of compile-time options --- INSTALL.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index d553544f..9d193fd8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -135,9 +135,10 @@ mkdir -p bld/release && cd bld/release Available compile-time options: -| Option | Type | Default | Description | Extra dependencies | -|----------|------|---------|---------------------|--------------------| -| `-Dime` | bool | `true` | Enables IME support | None | +| Option | Type | Default | Description | Extra dependencies | +|-------------|---------|---------|---------------------|--------------------| +| `-Dime` | bool | `true` | Enables IME support | None | +| `-Dterminfo | feature | `auto` | Build terminfo | `tic` (ncurses) | ### Release build From cf1335f2580ae303ace3bac6fa0d1a25c7d5b60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 10 Feb 2021 16:17:36 +0100 Subject: [PATCH 06/19] fdm: add support for managing signals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add fdm_signal_add() and fdm_signal_del(). Signals added to the fdm will be monitored, and the provided callback called as “soon as possible” from the main context (i.e not from the signal handler context). Monitored signals are *blocked* by default. We use epoll_pwait() to unblock them while we’re polling. This allows us to do race-free signal detection. We use a single handler for all monitored signals; the handler simply updates the signal’s slot in a global array (sized to fit SIGRTMAX signals). When epoll_pwait() returns EINTR, we loop the global array. The callback associated with each signal that fired is called. --- fdm.c | 163 ++++++++++++++++++++++++++++++++++++++++++++++-------- fdm.h | 8 ++- pgo/pgo.c | 2 +- 3 files changed, 147 insertions(+), 26 deletions(-) diff --git a/fdm.c b/fdm.c index 39a72f5b..76e71f74 100644 --- a/fdm.c +++ b/fdm.c @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -16,14 +17,20 @@ #define LOG_ENABLE_DBG 0 #include "log.h" -struct handler { +struct fd_handler { int fd; int events; - fdm_handler_t callback; + fdm_fd_handler_t callback; void *callback_data; bool deleted; }; +struct sig_handler { + int signo; + fdm_signal_handler_t callback; + void *callback_data; +}; + struct hook { fdm_hook_t callback; void *callback_data; @@ -34,33 +41,58 @@ typedef tll(struct hook) hooks_t; struct fdm { int epoll_fd; bool is_polling; - tll(struct handler *) fds; - tll(struct handler *) deferred_delete; + tll(struct fd_handler *) fds; + tll(struct fd_handler *) deferred_delete; + + sigset_t sigmask; + struct sig_handler *signal_handlers; + hooks_t hooks_low; hooks_t hooks_normal; hooks_t hooks_high; }; +static volatile sig_atomic_t *received_signals = NULL; + struct fdm * fdm_init(void) { + sigset_t sigmask; + if (sigprocmask(0, NULL, &sigmask) < 0) { + LOG_ERRNO("failed to get process signal mask"); + return NULL; + } + int epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (epoll_fd == -1) { LOG_ERRNO("failed to create epoll FD"); return NULL; } + assert(received_signals == NULL); /* Only one FDM instance supported */ + received_signals = calloc(SIGRTMAX, sizeof(received_signals[0])); + struct fdm *fdm = malloc(sizeof(*fdm)); if (unlikely(fdm == NULL)) { LOG_ERRNO("malloc() failed"); return NULL; } + struct sig_handler *sig_handlers = calloc(SIGRTMAX, sizeof(sig_handlers[0])); + + if (sig_handlers == NULL) { + LOG_ERRNO("failed to allocate signal handler array"); + free(fdm); + return NULL; + } + *fdm = (struct fdm){ .epoll_fd = epoll_fd, .is_polling = false, .fds = tll_init(), .deferred_delete = tll_init(), + .sigmask = sigmask, + .signal_handlers = sig_handlers, .hooks_low = tll_init(), .hooks_normal = tll_init(), .hooks_high = tll_init(), @@ -77,6 +109,11 @@ fdm_destroy(struct fdm *fdm) if (tll_length(fdm->fds) > 0) LOG_WARN("FD list not empty"); + for (int i = 0; i < SIGRTMAX; i++) { + if (fdm->signal_handlers[i].callback != NULL) + LOG_WARN("handler for signal %d not removed", i); + } + if (tll_length(fdm->hooks_low) > 0 || tll_length(fdm->hooks_normal) > 0 || tll_length(fdm->hooks_high) > 0) @@ -90,6 +127,9 @@ fdm_destroy(struct fdm *fdm) assert(tll_length(fdm->hooks_normal) == 0); assert(tll_length(fdm->hooks_high) == 0); + sigprocmask(SIG_SETMASK, &fdm->sigmask, NULL); + free(fdm->signal_handlers); + tll_free(fdm->fds); tll_free(fdm->deferred_delete); tll_free(fdm->hooks_low); @@ -97,19 +137,15 @@ fdm_destroy(struct fdm *fdm) tll_free(fdm->hooks_high); close(fdm->epoll_fd); free(fdm); + + free((void *)received_signals); + received_signals = NULL; } bool -fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data) +fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t cb, void *data) { #if defined(_DEBUG) - int flags = fcntl(fd, F_GETFL); - if (!(flags & O_NONBLOCK)) { - LOG_ERR("FD=%d is in blocking mode", fd); - assert(false); - return false; - } - tll_foreach(fdm->fds, it) { if (it->item->fd == fd) { LOG_ERR("FD=%d already registered", fd); @@ -119,30 +155,30 @@ fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data) } #endif - struct handler *fd_data = malloc(sizeof(*fd_data)); - if (unlikely(fd_data == NULL)) { + struct fd_handler *handler = malloc(sizeof(*handler)); + if (unlikely(handler == NULL)) { LOG_ERRNO("malloc() failed"); return false; } - *fd_data = (struct handler) { + *handler = (struct fd_handler) { .fd = fd, .events = events, - .callback = handler, + .callback = cb, .callback_data = data, .deleted = false, }; - tll_push_back(fdm->fds, fd_data); + tll_push_back(fdm->fds, handler); struct epoll_event ev = { .events = events, - .data = {.ptr = fd_data}, + .data = {.ptr = handler}, }; if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { LOG_ERRNO("failed to register FD=%d with epoll", fd); - free(fd_data); + free(handler); tll_pop_back(fdm->fds); return false; } @@ -194,7 +230,7 @@ fdm_del_no_close(struct fdm *fdm, int fd) } static bool -event_modify(struct fdm *fdm, struct handler *fd, int new_events) +event_modify(struct fdm *fdm, struct fd_handler *fd, int new_events) { if (new_events == fd->events) return true; @@ -291,6 +327,69 @@ fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority) return false; } +static void +signal_handler(int signo) +{ + received_signals[signo] = true; +} + +bool +fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data) +{ + if (fdm->signal_handlers[signo].callback != NULL) { + LOG_ERR("signal %d already has a handler", signo); + return false; + } + + sigset_t mask, original; + sigemptyset(&mask); + sigaddset(&mask, signo); + + if (sigprocmask(SIG_BLOCK, &mask, &original) < 0) { + LOG_ERRNO("failed to block signal %d", signo); + return false; + } + + struct sigaction action = {.sa_handler = &signal_handler}; + if (sigaction(signo, &action, NULL) < 0) { + LOG_ERRNO("failed to set signal handler for signal %d", signo); + sigprocmask(SIG_SETMASK, &original, NULL); + return false; + } + + received_signals[signo] = false; + fdm->signal_handlers[signo].callback = handler; + fdm->signal_handlers[signo].callback_data = data; + return true; +} + +bool +fdm_signal_del(struct fdm *fdm, int signo) +{ + if (fdm->signal_handlers[signo].callback == NULL) + return false; + + struct sigaction action = {.sa_handler = SIG_DFL}; + if (sigaction(signo, &action, NULL) < 0) { + LOG_ERRNO("failed to restore signal handler for signal %d", signo); + return false; + } + + received_signals[signo] = false; + fdm->signal_handlers[signo].callback = NULL; + fdm->signal_handlers[signo].callback_data = NULL; + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, signo); + if (sigprocmask(SIG_UNBLOCK, &mask, NULL) < 0) { + LOG_ERRNO("failed to unblock signal %d", signo); + return false; + } + + return true; +} + bool fdm_poll(struct fdm *fdm) { @@ -324,10 +423,28 @@ fdm_poll(struct fdm *fdm) struct epoll_event events[tll_length(fdm->fds)]; - int r = epoll_wait(fdm->epoll_fd, events, tll_length(fdm->fds), -1); + int r = epoll_pwait( + fdm->epoll_fd, events, tll_length(fdm->fds), -1, &fdm->sigmask); + if (unlikely(r < 0)) { - if (errno == EINTR) + if (errno == EINTR) { + /* TODO: is it possible to receive a signal without + * getting EINTR here? */ + + for (int i = 0; i < SIGRTMAX; i++) { + if (received_signals[i]) { + + received_signals[i] = false; + struct sig_handler *handler = &fdm->signal_handlers[i]; + + assert(handler->callback != NULL); + if (!handler->callback(fdm, i, handler->callback_data)) + return false; + } + } + return true; + } LOG_ERRNO("failed to epoll"); return false; @@ -337,7 +454,7 @@ fdm_poll(struct fdm *fdm) fdm->is_polling = true; for (int i = 0; i < r; i++) { - struct handler *fd = events[i].data.ptr; + struct fd_handler *fd = events[i].data.ptr; if (fd->deleted) continue; diff --git a/fdm.h b/fdm.h index 8661c580..2ccff958 100644 --- a/fdm.h +++ b/fdm.h @@ -4,7 +4,8 @@ struct fdm; -typedef bool (*fdm_handler_t)(struct fdm *fdm, int fd, int events, void *data); +typedef bool (*fdm_fd_handler_t)(struct fdm *fdm, int fd, int events, void *data); +typedef bool (*fdm_signal_handler_t)(struct fdm *fdm, int signo, void *data); typedef void (*fdm_hook_t)(struct fdm *fdm, void *data); enum fdm_hook_priority { @@ -16,7 +17,7 @@ enum fdm_hook_priority { struct fdm *fdm_init(void); void fdm_destroy(struct fdm *fdm); -bool fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data); +bool fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t handler, void *data); bool fdm_del(struct fdm *fdm, int fd); bool fdm_del_no_close(struct fdm *fdm, int fd); @@ -27,4 +28,7 @@ bool fdm_hook_add(struct fdm *fdm, fdm_hook_t hook, void *data, enum fdm_hook_priority priority); bool fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority); +bool fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data); +bool fdm_signal_del(struct fdm *fdm, int signo); + bool fdm_poll(struct fdm *fdm); diff --git a/pgo/pgo.c b/pgo/pgo.c index fa95a0ec..1b0b4641 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -35,7 +35,7 @@ async_write(int fd, const void *data, size_t len, size_t *idx) } bool -fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data) +fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t handler, void *data) { return true; } From 89f49b5bc78b7f7e77435d8e9b6b2e48751b7c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 10 Feb 2021 16:21:56 +0100 Subject: [PATCH 07/19] main: monitor SIGINT+SIGTERM using the FDM --- main.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/main.c b/main.c index f8da2fb3..46d26b81 100644 --- a/main.c +++ b/main.c @@ -33,12 +33,11 @@ #include "xmalloc.h" #include "xsnprintf.h" -static volatile sig_atomic_t aborted = 0; - -static void -sig_handler(int signo) +static bool +fdm_sigint(struct fdm *fdm, int signo, void *data) { - aborted = 1; + *(volatile sig_atomic_t *)data = true; + return true; } static const char * @@ -476,10 +475,10 @@ main(int argc, char *const *argv) if (as_server && (server = server_init(&conf, fdm, reaper, wayl)) == NULL) goto out; - /* Remember to restore signals in slave */ - const struct sigaction sa = {.sa_handler = &sig_handler}; - if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) { - LOG_ERRNO("failed to register signal handlers"); + 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; } @@ -512,6 +511,8 @@ out: render_destroy(renderer); wayl_destroy(wayl); reaper_destroy(reaper); + fdm_signal_del(fdm, SIGTERM); + fdm_signal_del(fdm, SIGINT); fdm_destroy(fdm); config_free(conf); From 37220fc1899796b56352ee54bb4133cb1a1ee4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 10 Feb 2021 16:22:36 +0100 Subject: [PATCH 08/19] render: block all signals in the rendering threads --- render.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/render.c b/render.c index 311f2b2b..f32bf670 100644 --- a/render.c +++ b/render.c @@ -1,6 +1,7 @@ #include "render.h" #include +#include #include #include @@ -1227,6 +1228,10 @@ render_worker_thread(void *_ctx) const int my_id = ctx->my_id; free(ctx); + sigset_t mask; + sigfillset(&mask); + pthread_sigmask(SIG_SETMASK, &mask, NULL); + char proc_title[16]; snprintf(proc_title, sizeof(proc_title), "foot:render:%d", my_id); From 79e3a4694365b58b482f2f437052773711ec67dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 10 Feb 2021 16:22:51 +0100 Subject: [PATCH 09/19] reaper: monitor SIGCHLD using the FDM instead of via a signalfd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In addition to letting the FDM do the low-level signal watching, this patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a signal, or via a signalfd, can be coalesced, like all signals. This means we need to loop on waitpid() with WNOHANG until there are no more processes to reap. This in turn requires a small change to the way reaper callbacks are implemented. Previously, the callback was allowed to do the wait(). This was signalled back to the reaper through the callback’s return value. Now, since we’ve already wait():ed, the process’ exit status is passed as an argument to the reaper callback. The callback for the client application has been updated accordingly; it sets a flag in the terminal struct, telling term_destroy() that the process has already been wait():ed on, and also stores the exit status. --- reaper.c | 116 ++++++++++++++--------------------------------------- reaper.h | 3 +- terminal.c | 110 +++++++++++++++++++++++++++----------------------- terminal.h | 3 +- 4 files changed, 92 insertions(+), 140 deletions(-) diff --git a/reaper.c b/reaper.c index 9d98854e..4abc785e 100644 --- a/reaper.c +++ b/reaper.c @@ -3,13 +3,13 @@ #include #include #include +#include #include -#include +#include #define LOG_MODULE "reaper" #define LOG_ENABLE_DBG 0 #include "log.h" -#include "tllist.h" struct child { pid_t pid; @@ -19,56 +19,32 @@ struct child { struct reaper { struct fdm *fdm; - int fd; tll(struct child) children; }; -static bool fdm_reap(struct fdm *fdm, int fd, int events, void *data); +static bool fdm_reap(struct fdm *fdm, int signo, void *data); struct reaper * reaper_init(struct fdm *fdm) { - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - - /* Block normal signal handling - we're using a signalfd instead */ - if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { - LOG_ERRNO("failed to block SIGCHLD"); - return NULL; - } - - int fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); - if (fd < 0) { - LOG_ERRNO("failed to create signal FD"); - sigprocmask(SIG_UNBLOCK, &mask, NULL); - return NULL; - } - struct reaper *reaper = malloc(sizeof(*reaper)); if (unlikely(reaper == NULL)) { LOG_ERRNO("malloc() failed"); - close(fd); - sigprocmask(SIG_UNBLOCK, &mask, NULL); return NULL; } *reaper = (struct reaper){ .fdm = fdm, - .fd = fd, .children = tll_init(), }; - if (!fdm_add(fdm, fd, EPOLLIN, &fdm_reap, reaper)){ - LOG_ERR("failed to register with the FDM"); + if (!fdm_signal_add(fdm, SIGCHLD, &fdm_reap, reaper)) goto err; - } return reaper; err: tll_free(reaper->children); - close(fd); free(reaper); return NULL; } @@ -79,14 +55,9 @@ reaper_destroy(struct reaper *reaper) if (reaper == NULL) return; - fdm_del(reaper->fdm, reaper->fd); + fdm_signal_del(reaper->fdm, SIGCHLD); tll_free(reaper->children); free(reaper); - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - sigprocmask(SIG_UNBLOCK, &mask, NULL); } void @@ -110,69 +81,40 @@ reaper_del(struct reaper *reaper, pid_t pid) } static bool -fdm_reap(struct fdm *fdm, int fd, int events, void *data) +fdm_reap(struct fdm *fdm, int signo, void *data) { struct reaper *reaper = data; - bool pollin = events & EPOLLIN; - bool hup = events & EPOLLHUP; + while (true) { + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + break; - if (hup && !pollin) - return false; + if (WIFEXITED(status)) + LOG_DBG("pid=%d: exited with status=%d", pid, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + LOG_DBG("pid=%d: killed by signal=%d", pid, WTERMSIG(status)); + else + LOG_DBG("pid=%d: died of unknown resason", pid); - assert(pollin); + tll_foreach(reaper->children, it) { + struct child *_child = &it->item; - struct signalfd_siginfo info; - ssize_t amount = read(reaper->fd, &info, sizeof(info)); - - if (amount < 0) { - LOG_ERRNO("failed to read"); - return false; - } - - assert((size_t)amount >= sizeof(info)); - - if (info.ssi_signo != SIGCHLD) { - LOG_WARN("got non-SIGCHLD signal: %d", info.ssi_signo); - return true; - } - - tll_foreach(reaper->children, it) { - struct child *_child = &it->item; - - if (_child->pid != (pid_t)info.ssi_pid) - continue; - - /* Make sure we remove it *before* the callback, since it too - * may remove it */ - struct child child = it->item; - tll_remove(reaper->children, it); - - bool reap_ourselves = true; - if (child.cb != NULL) - reap_ourselves = !child.cb(reaper, child.pid, child.cb_data); - - if (reap_ourselves) { - int result; - int res = waitpid(child.pid, &result, WNOHANG); - - if (res <= 0) { - if (res < 0) - LOG_ERRNO("waitpid failed for pid=%d", child.pid); + if (_child->pid != pid) continue; - } - else if (WIFEXITED(result)) - LOG_DBG("pid=%d: exited with status=%d", pid, WEXITSTATUS(result)); - else if (WIFSIGNALED(result)) - LOG_DBG("pid=%d: killed by signal=%d", pid, WTERMSIG(result)); - else - LOG_DBG("pid=%d: died of unknown resason", pid); + /* Make sure we remove it *before* the callback, since it too + * may remove it */ + struct child child = it->item; + tll_remove(reaper->children, it); + + if (child.cb != NULL) + child.cb(reaper, child.pid, status, child.cb_data); + + break; } } - if (hup) - return false; - return true; } diff --git a/reaper.h b/reaper.h index 2cd6dd6e..4416af03 100644 --- a/reaper.h +++ b/reaper.h @@ -10,7 +10,8 @@ struct reaper; struct reaper *reaper_init(struct fdm *fdm); void reaper_destroy(struct reaper *reaper); -typedef bool (*reaper_cb)(struct reaper *reaper, pid_t pid, void *data); +typedef void (*reaper_cb)( + struct reaper *reaper, pid_t pid, int status, void *data); void reaper_add(struct reaper *reaper, pid_t pid, reaper_cb cb, void *cb_data); void reaper_del(struct reaper *reaper, pid_t pid); diff --git a/terminal.c b/terminal.c index c65fe974..f12465c3 100644 --- a/terminal.c +++ b/terminal.c @@ -953,22 +953,25 @@ load_fonts_from_conf(struct terminal *term) return reload_fonts(term); } -static bool -slave_died(struct reaper *reaper, pid_t pid, void *data) +static void +slave_died(struct reaper *reaper, pid_t pid, int status, void *data) { struct terminal *term = data; LOG_DBG("slave (PID=%u) died", pid); + term->slave_has_been_reaped = true; + term->exit_status = status; + if (term->conf->hold_at_exit) { /* The PTMX FDM handler may already have closed our end */ if (term->ptmx >= 0) { fdm_del(term->fdm, term->ptmx); term->ptmx = -1; } - return true; + return; } - return term_shutdown(term); + term_shutdown(term); } struct terminal * @@ -1044,7 +1047,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .fdm = fdm, .reaper = reaper, .conf = conf, - .quit = false, .ptmx = ptmx, .ptmx_buffers = tll_init(), .ptmx_paste_buffers = tll_init(), @@ -1457,70 +1459,76 @@ term_destroy(struct terminal *term) int ret = EXIT_SUCCESS; if (term->slave > 0) { - LOG_DBG("waiting for slave (PID=%u) to die", term->slave); + int exit_status; - /* - * Note: we've closed ptmx, so the slave *should* exit... - * - * But, since it is possible to write clients that ignore - * this, we need to handle it in *some* way. - * - * So, what we do is register a SIGALRM handler, and configure - * a 2 second alarm. If the slave hasn't died after this time, - * we send it a SIGTERM, then wait another 2 seconds (using - * the same alarm mechanism). If it still hasn't died, we send - * it a SIGKILL. - * - * Note that this solution is *not* asynchronous, and any - * other events etc will be ignored during this time. This of - * course only applies to a 'foot --server' instance, where - * there might be other terminals running. - */ - sigaction(SIGALRM, &(const struct sigaction){.sa_handler = &sig_alarm}, NULL); - alarm(2); + if (term->slave_has_been_reaped) + exit_status = term->exit_status; + else { + LOG_DBG("waiting for slave (PID=%u) to die", term->slave); - int status; - int kill_signal = SIGTERM; + /* + * Note: we've closed ptmx, so the slave *should* exit... + * + * But, since it is possible to write clients that ignore + * this, we need to handle it in *some* way. + * + * So, what we do is register a SIGALRM handler, and configure + * a 2 second alarm. If the slave hasn't died after this time, + * we send it a SIGTERM, then wait another 2 seconds (using + * the same alarm mechanism). If it still hasn't died, we send + * it a SIGKILL. + * + * Note that this solution is *not* asynchronous, and any + * other events etc will be ignored during this time. This of + * course only applies to a 'foot --server' instance, where + * there might be other terminals running. + */ + sigaction(SIGALRM, &(const struct sigaction){.sa_handler = &sig_alarm}, NULL); + alarm(2); - while (true) { - int r = waitpid(term->slave, &status, 0); + int kill_signal = SIGTERM; - if (r == term->slave) - break; + while (true) { + int r = waitpid(term->slave, &exit_status, 0); - if (r == -1) { - assert(errno == EINTR); + if (r == term->slave) + break; - if (alarm_raised) { - LOG_DBG("slave hasn't died yet, sending: %s (%d)", - kill_signal == SIGTERM ? "SIGTERM" : "SIGKILL", - kill_signal); + if (r == -1) { + assert(errno == EINTR); - kill(term->slave, kill_signal); + if (alarm_raised) { + LOG_DBG("slave hasn't died yet, sending: %s (%d)", + kill_signal == SIGTERM ? "SIGTERM" : "SIGKILL", + kill_signal); - alarm_raised = 0; - if (kill_signal != SIGKILL) - alarm(2); + kill(term->slave, kill_signal); - kill_signal = SIGKILL; + alarm_raised = 0; + if (kill_signal != SIGKILL) + alarm(2); + kill_signal = SIGKILL; + + } } } + + /* Cancel alarm */ + alarm(0); + sigaction(SIGALRM, &(const struct sigaction){.sa_handler = SIG_DFL}, NULL); } - /* Cancel alarm */ - alarm(0); - sigaction(SIGALRM, &(const struct sigaction){.sa_handler = SIG_DFL}, NULL); - ret = EXIT_FAILURE; - if (WIFEXITED(status)) { - ret = WEXITSTATUS(status); + if (WIFEXITED(exit_status)) { + ret = WEXITSTATUS(exit_status); LOG_DBG("slave exited with code %d", ret); - } else if (WIFSIGNALED(status)) { - ret = WTERMSIG(status); + } else if (WIFSIGNALED(exit_status)) { + ret = WTERMSIG(exit_status); LOG_WARN("slave exited with signal %d (%s)", ret, strsignal(ret)); } else { - LOG_WARN("slave exited for unknown reason (status = 0x%08x)", status); + LOG_WARN("slave exited for unknown reason (status = 0x%08x)", + exit_status); } } diff --git a/terminal.h b/terminal.h index b6b150ae..3c341299 100644 --- a/terminal.h +++ b/terminal.h @@ -487,8 +487,9 @@ struct terminal { } ime; #endif - bool quit; bool is_shutting_down; + bool slave_has_been_reaped; + int exit_status; void (*shutdown_cb)(void *data, int exit_code); void *shutdown_data; From a0afae747b60649ea23efc785255b6f2dece67d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 16 Jan 2021 11:26:03 +0100 Subject: [PATCH 10/19] =?UTF-8?q?config:=20add=20new=20option=20=E2=80=98s?= =?UTF-8?q?election-target=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This option controls the clipboard target that selected text should be copied to. --- config.c | 17 +++++++++++++++++ config.h | 5 +++++ foot.ini | 1 + 3 files changed, 23 insertions(+) diff --git a/config.c b/config.c index 54c8e60f..08a46a4a 100644 --- a/config.c +++ b/config.c @@ -602,6 +602,22 @@ parse_section_main(const char *key, const char *value, struct config *conf, conf->notify.argv = argv; } + else if (strcmp(key, "selection-target") == 0) { + if (strcasecmp(value, "primary") == 0) + conf->selection_target = SELECTION_TARGET_PRIMARY; + else if (strcasecmp(value, "clipboard") == 0) + conf->selection_target = SELECTION_TARGET_CLIPBOARD; + else if (strcasecmp(value, "both") == 0) + conf->selection_target = SELECTION_TARGET_BOTH; + else { + LOG_AND_NOTIFY_ERR( + "%s:%d: [default]: %s: invalid 'selection-target'; " + "must be one of 'primary', 'clipboard' or 'both", + path, lineno, value); + return false; + } + } + else { LOG_AND_NOTIFY_ERR("%s:%u: [default]: %s: invalid key", path, lineno, key); return false; @@ -2037,6 +2053,7 @@ config_load(struct config *conf, const char *conf_path, .render_worker_count = sysconf(_SC_NPROCESSORS_ONLN), .server_socket_path = get_server_socket_path(), .presentation_timings = false, + .selection_target = SELECTION_TARGET_PRIMARY, .hold_at_exit = false, .notify = { .raw_cmd = NULL, diff --git a/config.h b/config.h index a1b27c04..db9a5b39 100644 --- a/config.h +++ b/config.h @@ -166,6 +166,11 @@ struct config { char *server_socket_path; bool presentation_timings; bool hold_at_exit; + enum { + SELECTION_TARGET_PRIMARY, + SELECTION_TARGET_CLIPBOARD, + SELECTION_TARGET_BOTH + } selection_target; struct { char *raw_cmd; diff --git a/foot.ini b/foot.ini index baf0df84..0443b85f 100644 --- a/foot.ini +++ b/foot.ini @@ -17,6 +17,7 @@ # bell=none # word-delimiters=,│`|:"'()[]{}<> # notify=notify-send -a foot -i foot ${title} ${body} +# selection-target=primary [scrollback] # lines=1000 From 76e8d3f483802934e1f87b7c9ef11577b703c590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 16 Jan 2021 11:26:45 +0100 Subject: [PATCH 11/19] =?UTF-8?q?selection:=20copy=20selected=20text=20to?= =?UTF-8?q?=20the=20target=20configured=20by=20=E2=80=98selection-target?= =?UTF-8?q?=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #288 --- selection.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/selection.c b/selection.c index b83331a6..fd1b1661 100644 --- a/selection.c +++ b/selection.c @@ -655,7 +655,20 @@ selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial) } assert(term->selection.start.row <= term->selection.end.row); - selection_to_primary(seat, term, serial); + + switch (term->conf->selection_target) { + case SELECTION_TARGET_PRIMARY: + selection_to_primary(seat, term, serial); + break; + case SELECTION_TARGET_CLIPBOARD: + selection_to_clipboard(seat, term, serial); + break; + + case SELECTION_TARGET_BOTH: + selection_to_primary(seat, term, serial); + selection_to_clipboard(seat, term, serial); + break; + } } void From 9db9a0d22515f90a75ec09deb900308e7604ad5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 16 Jan 2021 15:39:44 +0100 Subject: [PATCH 12/19] =?UTF-8?q?config:=20add=20=E2=80=98none=E2=80=99=20?= =?UTF-8?q?as=20a=20possible=20value=20for=20=E2=80=98selection-target?= =?UTF-8?q?=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ‘selection-target’ is set to ‘none’, selecting text does not copy the text to _any_ clipboard. This patch also refactors the value parsing to be data driven. --- config.c | 30 ++++++++++++++++++------------ config.h | 1 + selection.c | 3 +++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/config.c b/config.c index 08a46a4a..63d62ea3 100644 --- a/config.c +++ b/config.c @@ -603,19 +603,25 @@ parse_section_main(const char *key, const char *value, struct config *conf, } else if (strcmp(key, "selection-target") == 0) { - if (strcasecmp(value, "primary") == 0) - conf->selection_target = SELECTION_TARGET_PRIMARY; - else if (strcasecmp(value, "clipboard") == 0) - conf->selection_target = SELECTION_TARGET_CLIPBOARD; - else if (strcasecmp(value, "both") == 0) - conf->selection_target = SELECTION_TARGET_BOTH; - else { - LOG_AND_NOTIFY_ERR( - "%s:%d: [default]: %s: invalid 'selection-target'; " - "must be one of 'primary', 'clipboard' or 'both", - path, lineno, value); - return false; + static const char *const values[] = { + [SELECTION_TARGET_NONE] = "none", + [SELECTION_TARGET_PRIMARY] = "primary", + [SELECTION_TARGET_CLIPBOARD] = "clipboard", + [SELECTION_TARGET_BOTH] = "both", + }; + + for (size_t i = 0; i < ALEN(values); i++) { + if (strcasecmp(value, values[i]) == 0) { + conf->selection_target = i; + return true; + } } + + LOG_AND_NOTIFY_ERR( + "%s:%d: [default]: %s: invalid 'selection-target'; " + "must be one of 'none', 'primary', 'clipboard' or 'both", + path, lineno, value); + return false; } else { diff --git a/config.h b/config.h index db9a5b39..753a3386 100644 --- a/config.h +++ b/config.h @@ -167,6 +167,7 @@ struct config { bool presentation_timings; bool hold_at_exit; enum { + SELECTION_TARGET_NONE, SELECTION_TARGET_PRIMARY, SELECTION_TARGET_CLIPBOARD, SELECTION_TARGET_BOTH diff --git a/selection.c b/selection.c index fd1b1661..2b209ae2 100644 --- a/selection.c +++ b/selection.c @@ -657,6 +657,9 @@ selection_finalize(struct seat *seat, struct terminal *term, uint32_t serial) assert(term->selection.start.row <= term->selection.end.row); switch (term->conf->selection_target) { + case SELECTION_TARGET_NONE: + break; + case SELECTION_TARGET_PRIMARY: selection_to_primary(seat, term, serial); break; From c0bff58d8dbf7a37bf0f757ae1f91e8ea6290cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 16 Jan 2021 15:44:51 +0100 Subject: [PATCH 13/19] changelog: selection-target=none|primary|clipboard|both --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5400e2b8..3de3ad21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,14 @@ ## 1.6.4 ### Added + +* `selection-target=none|primary|clipboard|both` to `foot.ini`. It can + be used to configure which clipboard(s) selected text should be + copied to. The default is `primary`, which corresponds to the + behavior in older foot releases + (https://codeberg.org/dnkl/foot/issues/288). + + ### Changed * The IME state no longer stays stuck in the terminal if the IME goes From 416e8e7e5828c8eddbf5a94f15f10aa5863562c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 20 Jan 2021 17:56:14 +0100 Subject: [PATCH 14/19] doc: foot.ini: selection-target=none|primary|clipboard|both --- doc/foot.ini.5.scd | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 8d9960e0..633bc0b6 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -122,12 +122,6 @@ in this order: compositor can use this value to e.g. group multiple windows, or apply window management rules. Default: _foot_. -*workers* - Number of threads to use for rendering. Set to 0 to disable - multithreading. Default: the number of available logical CPUs - (including SMT). Note that this is not always the best value. In - some cases, the number of physical _cores_ is better. - *bold-text-in-bright* Boolean. When enabled, bold text is rendered in a brighter color (in addition to using a bold font). Default: _no_. @@ -173,6 +167,17 @@ in this order: Default: _notify-send -a foot -i foot ${title} ${body}_. +*selection-target* + Clipboard target to automatically copy selected text to. One of + *none*, *primary*, *clipboard* or *both*. Default: _primary_. + + +*workers* + Number of threads to use for rendering. Set to 0 to disable + multithreading. Default: the number of available logical CPUs + (including SMT). Note that this is not always the best value. In + some cases, the number of physical _cores_ is better. + # SECTION: scrollback From 036a77194b96691156b23a93cc5a5f618a36f584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 20 Jan 2021 17:57:44 +0100 Subject: [PATCH 15/19] =?UTF-8?q?config:=20selection-target:=20space-optim?= =?UTF-8?q?ize=20the=20static=20=E2=80=98value=E2=80=99=20array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.c b/config.c index 63d62ea3..eb72d99c 100644 --- a/config.c +++ b/config.c @@ -603,7 +603,7 @@ parse_section_main(const char *key, const char *value, struct config *conf, } else if (strcmp(key, "selection-target") == 0) { - static const char *const values[] = { + static const char values[][12] = { [SELECTION_TARGET_NONE] = "none", [SELECTION_TARGET_PRIMARY] = "primary", [SELECTION_TARGET_CLIPBOARD] = "clipboard", From caede905447164acb2018b2f411b6aa749da9736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 11 Feb 2021 19:54:55 +0100 Subject: [PATCH 16/19] changelog: use standard signals for SIGCHLD --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3de3ad21..0be06d79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,9 @@ * The IME state no longer stays stuck in the terminal if the IME goes away during preedit. * `-Dterminfo` changed from a `boolean` to a `feature` option. +* Use standard signals instead of a signalfd to handle + `SIGCHLD`. Fixes an issue on FreeBSD where foot did not detect when + the client application had terminated. ### Deprecated From 299904e987b280416877d353563e4db39341b60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Feb 2021 10:03:01 +0100 Subject: [PATCH 17/19] =?UTF-8?q?selection:=20don=E2=80=99t=20strip=20form?= =?UTF-8?q?atting=20C0=20control=20characters=20in=20bracketed=20paste=20m?= =?UTF-8?q?ode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s ok to let the receiving end handle formatting C0 control characters in bracketed paste mode. In fact, we *must* let them through. Otherwise it is impossible to paste e.g. tabs into editors and similar applications. --- CHANGELOG.md | 4 ++++ selection.c | 43 +++++++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0be06d79..c7d79e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,10 @@ ### Deprecated ### Removed ### Fixed + +* `BS`, `HT` and `DEL` from being stripped in bracketed paste mode. + + ### Security ### Contributors diff --git a/selection.c b/selection.c index 2b209ae2..9a0ce496 100644 --- a/selection.c +++ b/selection.c @@ -1337,6 +1337,15 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data) */ char *p = text; size_t left = count; + +#define skip_one() \ + do { \ + ctx->decoder(ctx, p, i); \ + assert(i + 1 <= left); \ + p += i + 1; \ + left -= i + 1; \ + } while (0) + again: for (size_t i = 0; i < left; i++) { switch (p[i]) { @@ -1351,33 +1360,29 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data) case '\r': /* Convert \r\n -> \r */ if (!ctx->bracketed && i + 1 < left && p[i + 1] == '\n') { - ctx->decoder(ctx, p, i + 1); - - assert(i + 2 <= left); - p += i + 2; - left -= i + 2; + i++; + skip_one(); goto again; } break; /* C0 non-formatting control characters (\b \t \n \r excluded) */ case '\x01': case '\x02': case '\x03': case '\x04': case '\x05': - case '\x06': case '\x07': case '\x0b': case '\x0c': case '\x0e': - case '\x0f': case '\x10': case '\x11': case '\x12': case '\x13': - case '\x14': case '\x15': case '\x16': case '\x17': case '\x18': - case '\x19': case '\x1a': case '\x1b': case '\x1c': case '\x1d': - case '\x1e': case '\x1f': - /* FALLTHROUGH */ + case '\x06': case '\x07': case '\x0e': case '\x0f': case '\x10': + case '\x11': case '\x12': case '\x13': case '\x14': case '\x15': + case '\x16': case '\x17': case '\x18': case '\x19': case '\x1a': + case '\x1b': case '\x1c': case '\x1d': case '\x1e': case '\x1f': + skip_one(); + goto again; /* Additional control characters stripped by default (but * configurable) in XTerm: BS, HT, DEL */ - case '\b': case '\t': case '\x7f': - ctx->decoder(ctx, p, i); - - assert(i + 1 <= left); - p += i + 1; - left -= i + 1; - goto again; + case '\b': case '\t': case '\v': case '\f': case '\x7f': + if (!ctx->bracketed) { + skip_one(); + goto again; + } + break; } } @@ -1385,6 +1390,8 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data) left = 0; } +#undef skip_one + done: ctx->finish(ctx); clipboard_receive_done(fdm, ctx); From c18e459757827f262cf81cd1155f90bb8a978568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Feb 2021 21:27:15 +0100 Subject: [PATCH 18/19] changelog: prepare for 1.6.4 --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7d79e0f..3134ad65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ ## 1.6.4 + ### Added * `selection-target=none|primary|clipboard|both` to `foot.ini`. It can @@ -42,14 +43,11 @@ the client application had terminated. -### Deprecated -### Removed ### Fixed * `BS`, `HT` and `DEL` from being stripped in bracketed paste mode. -### Security ### Contributors * [tdeo](https://codeberg.org/tdeo) From dcd4f1ca798f3f86e211d91395874534d2acb0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Feb 2021 21:27:47 +0100 Subject: [PATCH 19/19] meson/pkgbuild: bump version to 1.6.4 --- PKGBUILD | 2 +- meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PKGBUILD b/PKGBUILD index 86c3377e..1be08306 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,5 +1,5 @@ pkgname=('foot-git' 'foot-terminfo-git') -pkgver=1.6.3 +pkgver=1.6.4 pkgrel=1 arch=('x86_64' 'aarch64') url=https://codeberg.org/dnkl/foot diff --git a/meson.build b/meson.build index 659fed65..a75f4132 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.6.3', + version: '1.6.4', license: 'MIT', meson_version: '>=0.53.0', default_options: [