From 6c56b04b3f312be8dcee307e29515f3c83b3ae87 Mon Sep 17 00:00:00 2001 From: delthas Date: Mon, 4 Sep 2023 14:02:05 +0200 Subject: [PATCH] osc: add support for osc 176 (app ID) This adds support for a new OSC escape sequence: OSC 176, that lets terminal programs tell the terminal the name of the app that is running. foot then sets the app ID of the toplevel to that ID, which lets the compositor know which app is running, and typically sets the appropriate icon, window grouping, ... See: https://gist.github.com/delthas/d451e2cc1573bb2364839849c7117239 --- README.md | 1 + doc/foot-ctlseqs.7.scd | 5 ++++ notify.c | 2 +- osc.c | 4 +++ render.c | 22 +++++++++++++++ render.h | 1 + terminal.c | 61 +++++++++++++++++++++++++++++++++++++++++- terminal.h | 7 +++++ 8 files changed, 101 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c8d3878..75e10889 100644 --- a/README.md +++ b/README.md @@ -536,6 +536,7 @@ with the terminal emulator itself. Foot implements the following OSCs: * `OSC 117` - reset highlight background color * `OSC 119` - reset highlight foreground color * `OSC 133` - [shell integration](#shell-integration) +* `OSC 176` - set app ID * `OSC 555` - flash screen (**foot specific**) * `OSC 777` - desktop notification (only the `;notify` sub-command of OSC 777 is supported.) diff --git a/doc/foot-ctlseqs.7.scd b/doc/foot-ctlseqs.7.scd index 68a54beb..64c56d5d 100644 --- a/doc/foot-ctlseqs.7.scd +++ b/doc/foot-ctlseqs.7.scd @@ -693,6 +693,11 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_. | \\E] 133 ; D \\E\\ : FinalTerm : Mark end of command output +| \\E] 176 ; _app-id_ \\E\\ +: foot +: Set app ID. _app-id_ is optional; if assigned, + the terminal window App ID will be set to the value. + An empty App ID resets the value to the default. | \\E] 555 \\E\\ : foot : Flash the entire terminal (foot extension) diff --git a/notify.c b/notify.c index 04427477..7a208479 100644 --- a/notify.c +++ b/notify.c @@ -36,7 +36,7 @@ notify_notify(const struct terminal *term, const char *title, const char *body) if (!spawn_expand_template( &term->conf->notify, 4, (const char *[]){"app-id", "window-title", "title", "body"}, - (const char *[]){term->conf->app_id, term->window_title, title, body}, + (const char *[]){term->app_id ? term->app_id : term->conf->app_id, term->window_title, title, body}, &argc, &argv)) { return; diff --git a/osc.c b/osc.c index 1ea61a3e..28e93e51 100644 --- a/osc.c +++ b/osc.c @@ -916,6 +916,10 @@ osc_dispatch(struct terminal *term) } break; + case 176: + term_set_app_id(term, string); + break; + case 555: osc_flash(term); break; diff --git a/render.c b/render.c index 13d348f3..f82e54f0 100644 --- a/render.c +++ b/render.c @@ -4529,6 +4529,28 @@ render_refresh_title(struct terminal *term) render_refresh_csd(term); } +void +render_refresh_app_id(struct terminal *term) +{ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) + return; + + struct timespec diff; + timespec_sub(&now, &term->render.app_id.last_update, &diff); + + if (diff.tv_sec == 0 && diff.tv_nsec < 8333 * 1000) { + const struct itimerspec timeout = { + .it_value = {.tv_nsec = 8333 * 1000 - diff.tv_nsec}, + }; + + timerfd_settime(term->render.app_id.timer_fd, 0, &timeout, NULL); + } else { + term->render.app_id.last_update = now; + xdg_toplevel_set_app_id(term->window->xdg_toplevel, term->app_id ? term->app_id : term->conf->app_id); + } +} + void render_refresh(struct terminal *term) { diff --git a/render.h b/render.h index 78ebae40..cfedf311 100644 --- a/render.h +++ b/render.h @@ -21,6 +21,7 @@ bool render_resize( struct terminal *term, int width, int height, uint8_t resize_options); void render_refresh(struct terminal *term); +void render_refresh_app_id(struct terminal *term); void render_refresh_csd(struct terminal *term); void render_refresh_search(struct terminal *term); void render_refresh_title(struct terminal *term); diff --git a/terminal.c b/terminal.c index 7ace10ef..bc7dc428 100644 --- a/terminal.c +++ b/terminal.c @@ -627,6 +627,30 @@ fdm_title_update_timeout(struct fdm *fdm, int fd, int events, void *data) return true; } +static bool +fdm_app_id_update_timeout(struct fdm *fdm, int fd, int events, void *data) +{ + if (events & EPOLLHUP) + return false; + + struct terminal *term = data; + uint64_t unused; + ssize_t ret = read(term->render.app_id.timer_fd, &unused, sizeof(unused)); + + if (ret < 0) { + if (errno == EAGAIN) + return true; + LOG_ERRNO("failed to read app ID update throttle timer"); + return false; + } + + struct itimerspec reset = {{0}}; + timerfd_settime(term->render.app_id.timer_fd, 0, &reset, NULL); + + render_refresh_app_id(term); + return true; +} + static bool initialize_render_workers(struct terminal *term) { @@ -1050,6 +1074,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, int delay_upper_fd = -1; int app_sync_updates_fd = -1; int title_update_fd = -1; + int app_id_update_fd = -1; struct terminal *term = malloc(sizeof(*term)); if (unlikely(term == NULL)) { @@ -1084,6 +1109,12 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, goto close_fds; } + if ((app_id_update_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0) + { + LOG_ERRNO("failed to create app ID update throttle timer FD"); + goto close_fds; + } + if (ioctl(ptmx, (unsigned int)TIOCSWINSZ, &(struct winsize){.ws_row = 24, .ws_col = 80}) < 0) { @@ -1114,7 +1145,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, !fdm_add(fdm, delay_lower_fd, EPOLLIN, &fdm_delayed_render, term) || !fdm_add(fdm, delay_upper_fd, EPOLLIN, &fdm_delayed_render, term) || !fdm_add(fdm, app_sync_updates_fd, EPOLLIN, &fdm_app_sync_updates_timeout, term) || - !fdm_add(fdm, title_update_fd, EPOLLIN, &fdm_title_update_timeout, term)) + !fdm_add(fdm, title_update_fd, EPOLLIN, &fdm_title_update_timeout, term) || + !fdm_add(fdm, app_id_update_fd, EPOLLIN, &fdm_app_id_update_timeout, term)) { goto err; } @@ -1210,6 +1242,9 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .title = { .timer_fd = title_update_fd, }, + .app_id = { + .timer_fd = app_id_update_fd, + }, .workers = { .count = conf->render_worker_count, .queue = tll_init(), @@ -1318,6 +1353,7 @@ close_fds: fdm_del(fdm, delay_upper_fd); fdm_del(fdm, app_sync_updates_fd); fdm_del(fdm, title_update_fd); + fdm_del(fdm, app_id_update_fd); free(term); return NULL; @@ -1510,6 +1546,7 @@ term_shutdown(struct terminal *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); fdm_del(term->fdm, term->render.title.timer_fd); fdm_del(term->fdm, term->delayed_render_timer.lower_fd); fdm_del(term->fdm, term->delayed_render_timer.upper_fd); @@ -1548,6 +1585,7 @@ term_shutdown(struct terminal *term) term->selection.auto_scroll.fd = -1; term->render.app_sync_updates.timer_fd = -1; + term->render.app_id.timer_fd = -1; term->render.title.timer_fd = -1; term->delayed_render_timer.lower_fd = -1; term->delayed_render_timer.upper_fd = -1; @@ -1601,6 +1639,7 @@ term_destroy(struct terminal *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); fdm_del(term->fdm, term->render.title.timer_fd); fdm_del(term->fdm, term->delayed_render_timer.lower_fd); fdm_del(term->fdm, term->delayed_render_timer.upper_fd); @@ -1644,6 +1683,7 @@ term_destroy(struct terminal *term) composed_free(term->composed); + free(term->app_id); free(term->window_title); tll_free_and_free(term->window_title_stack, free); @@ -3260,6 +3300,25 @@ term_set_window_title(struct terminal *term, const char *title) term->window_title_has_been_set = true; } +void +term_set_app_id(struct terminal *term, const char *app_id) +{ + if (app_id != NULL && *app_id == '\0') + app_id = NULL; + if (term->app_id == NULL && app_id == NULL) + return; + if (term->app_id != NULL && app_id != NULL && strcmp(term->app_id, app_id) == 0) + return; + + free(term->app_id); + if (app_id != NULL) { + term->app_id = xstrdup(app_id); + } else { + term->app_id = NULL; + } + render_refresh_app_id(term); +} + void term_flash(struct terminal *term, unsigned duration_ms) { diff --git a/terminal.h b/terminal.h index 7b743c09..49c88926 100644 --- a/terminal.h +++ b/terminal.h @@ -483,6 +483,7 @@ struct terminal { bool window_title_has_been_set; char *window_title; tll(char *) window_title_stack; + char *app_id; struct { bool active; @@ -606,6 +607,11 @@ struct terminal { int timer_fd; } title; + struct { + struct timespec last_update; + int timer_fd; + } app_id; + uint32_t scrollback_lines; /* Number of scrollback lines, from conf (TODO: move out from render struct?) */ struct { @@ -832,6 +838,7 @@ void term_xcursor_update_for_seat(struct terminal *term, struct seat *seat); void term_set_user_mouse_cursor(struct terminal *term, const char *cursor); void term_set_window_title(struct terminal *term, const char *title); +void term_set_app_id(struct terminal *term, const char *app_id); void term_flash(struct terminal *term, unsigned duration_ms); void term_bell(struct terminal *term); bool term_spawn_new(const struct terminal *term);