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);