From 5ef69fc591e2fbad2b399e765d697150c97a95a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Sep 2024 10:42:24 +0200 Subject: [PATCH 01/11] meson: detect wayland-protocols >= 1.37, and conditionally enable xdg-toplevel-icon-v1 --- meson.build | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/meson.build b/meson.build index 1b3b01aa..91e745bf 100644 --- a/meson.build +++ b/meson.build @@ -171,6 +171,14 @@ wl_proto_xml = [ wayland_protocols_datadir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', ] +if wayland_protocols.version().version_compare('>=1.37') + add_project_arguments('-DHAVE_XDG_TOPLEVEL_ICON', language: 'c') + wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml'] + xdg_toplevel_icon = true +else + xdg_toplevel_icon = false +endif + foreach prot : wl_proto_xml wl_proto_headers += custom_target( prot.underscorify() + '-client-header', @@ -401,6 +409,7 @@ summary( 'Themes': get_option('themes'), 'IME': get_option('ime'), 'Grapheme clustering': utf8proc.found(), + 'Wayland: xdg-toplevel-icon-v1': xdg_toplevel_icon, 'utmp backend': utmp_backend, 'utmp helper default path': utmp_default_helper_path, 'Build terminfo': tic.found(), From 28a1c67dd5ecc917ebd69c0552a1ed99165c310f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Sep 2024 11:18:30 +0200 Subject: [PATCH 02/11] wayland: bind the xdg-toplevel-icon manager global --- wayland.c | 15 +++++++++++++++ wayland.h | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/wayland.c b/wayland.c index 3f65901b..1e98560a 100644 --- a/wayland.c +++ b/wayland.c @@ -1363,6 +1363,17 @@ handle_global(void *data, struct wl_registry *registry, &wp_single_pixel_buffer_manager_v1_interface, required); } +#if defined(HAVE_XDG_TOPLEVEL_ICON) + else if (streq(interface, xdg_toplevel_icon_v1_interface.name)) { + const uint32_t required = 1; + if (!verify_iface_version(interface, version, required)) + return; + + wayl->toplevel_icon_manager = wl_registry_bind( + wayl->registry, name, &xdg_toplevel_icon_v1_interface, required); + } +#endif + #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED else if (streq(interface, zwp_text_input_manager_v3_interface.name)) { const uint32_t required = 1; @@ -1679,6 +1690,10 @@ wayl_destroy(struct wayland *wayl) zwp_text_input_manager_v3_destroy(wayl->text_input_manager); #endif +#if defined(HAVE_XDG_TOPLEVEL_ICON) + if (wayl->toplevel_icon_manager != NULL) + xdg_toplevel_icon_manager_v1_destroy(wayl->toplevel_icon_manager); +#endif if (wayl->single_pixel_manager != NULL) wp_single_pixel_buffer_manager_v1_destroy(wayl->single_pixel_manager); if (wayl->fractional_scale_manager != NULL) diff --git a/wayland.h b/wayland.h index ca9c05fa..227e2a68 100644 --- a/wayland.h +++ b/wayland.h @@ -20,6 +20,10 @@ #include #include +#if defined(HAVE_XDG_TOPLEVEL_ICON) + #include +#endif + #include #include @@ -443,6 +447,10 @@ struct wayland { struct wp_single_pixel_buffer_manager_v1 *single_pixel_manager; +#if defined(HAVE_XDG_TOPLEVEL_ICON) + struct xdg_toplevel_icon_manager_v1 *toplevel_icon_manager; +#endif + bool presentation_timings; struct wp_presentation *presentation; uint32_t presentation_clock_id; From 0cb07027f2f1e5035cc009140084128d35995c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Sep 2024 13:15:21 +0200 Subject: [PATCH 03/11] wayland: set toplevel icon If the xdg-toplevel-icon-v1 protocol is available, and we have the corresponding manager global, set the toplevel icon to "foot". Note: we do *not* provide any pixel data. This is by design; we want to keep things simple. To be able to provide pixel data, we would have to either: * embed the raw pixel data in the foot binary * link against either libpng or/and e.g. nanosvg, locate, at run-time, the paths to our own icons, and load them at run-time. * link against either libpng or/and e.g. nanosvg, and, at run-time, do a full icon lookup. This would also require us to add a config option for which icon theme to use. Of the two, I would prefer the first option. But, let's skip this completely for now. By providing the icon as a name, the compositor will have to lookup the icon itself. Compositors supporting icons is likely to already support this. So what do we gain by implementing this protocol? Compositors no longer has to parse .desktop files and map our app-id to find the icon to use. There's one question remaining. With this patch, the icon name is hardcoded to "foot", just like our .desktop files. But, perhaps we should use the app-id instead? And if so, should we also change the icon when the app-id changes? My gut feeling is, yes, we should use the app-id instead, and yes, we should update the icon when the app-id is changed at run-time. --- wayland.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/wayland.c b/wayland.c index 1e98560a..30ee86c6 100644 --- a/wayland.c +++ b/wayland.c @@ -1811,6 +1811,17 @@ wayl_win_init(struct terminal *term, const char *token) xdg_toplevel_set_app_id(win->xdg_toplevel, conf->app_id); +#if defined(HAVE_XDG_TOPLEVEL_ICON) + if (wayl->toplevel_icon_manager != NULL) { + struct xdg_toplevel_icon_v1 *icon = + xdg_toplevel_icon_manager_v1_create_icon(wayl->toplevel_icon_manager); + xdg_toplevel_icon_v1_set_name(icon, "foot"); + xdg_toplevel_icon_manager_v1_set_icon( + wayl->toplevel_icon_manager, win->xdg_toplevel, icon); + xdg_toplevel_icon_v1_destroy(icon); + } +#endif + if (conf->csd.preferred == CONF_CSD_PREFER_NONE) { /* User specifically do *not* want decorations */ win->csd_mode = CSD_NO; From b34137dde3bd40b5a116b4d8f86196027fc11d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Sep 2024 18:25:07 +0200 Subject: [PATCH 04/11] toplevel-icon: set to app-id, instead of hardcoding to "foot" And, special case "footclient", and map it to "foot". --- render.c | 27 ++++++++++++++++++++++++--- wayland.c | 6 +++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/render.c b/render.c index 00ea3445..93bad7fe 100644 --- a/render.c +++ b/render.c @@ -4944,10 +4944,31 @@ render_refresh_app_id(struct terminal *term) }; 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); + return; } + + const char *app_id = + term->app_id != NULL ? term->app_id : term->conf->app_id; + + xdg_toplevel_set_app_id(term->window->xdg_toplevel, app_id); + +#if defined(HAVE_XDG_TOPLEVEL_ICON) + if (term->wl->toplevel_icon_manager != NULL) { + struct xdg_toplevel_icon_v1 *icon = + xdg_toplevel_icon_manager_v1_create_icon( + term->wl->toplevel_icon_manager); + + xdg_toplevel_icon_v1_set_name( + icon, streq(app_id, "footclient") ? "foot" : app_id); + + xdg_toplevel_icon_manager_v1_set_icon( + term->wl->toplevel_icon_manager, term->window->xdg_toplevel, icon); + + xdg_toplevel_icon_v1_destroy(icon); + } +#endif + + term->render.app_id.last_update = now; } void diff --git a/wayland.c b/wayland.c index 30ee86c6..fd228312 100644 --- a/wayland.c +++ b/wayland.c @@ -1813,9 +1813,13 @@ wayl_win_init(struct terminal *term, const char *token) #if defined(HAVE_XDG_TOPLEVEL_ICON) if (wayl->toplevel_icon_manager != NULL) { + const char *app_id = + term->app_id != NULL ? term->app_id : term->conf->app_id; + struct xdg_toplevel_icon_v1 *icon = xdg_toplevel_icon_manager_v1_create_icon(wayl->toplevel_icon_manager); - xdg_toplevel_icon_v1_set_name(icon, "foot"); + xdg_toplevel_icon_v1_set_name(icon, streq( + app_id, "footclient") ? "foot" : app_id); xdg_toplevel_icon_manager_v1_set_icon( wayl->toplevel_icon_manager, win->xdg_toplevel, icon); xdg_toplevel_icon_v1_destroy(icon); From 3f8a1fc85b933678be59bf05d52652335529545b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 8 Sep 2024 18:26:28 +0200 Subject: [PATCH 05/11] changelog: xdg-toplevel-icon-v1 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cadf79..564b62b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ (and the grid reflowed) or not when e.g. zooming in/out ([#1807][1807]). * `strikeout-thickness` option. +* Implemented the new `xdg-toplevel-icon-v1` protocol. [1807]: https://codeberg.org/dnkl/foot/issues/1807 From 97ec375c67cc7b071b0478b6c887057008dc469e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 10 Sep 2024 18:53:38 +0200 Subject: [PATCH 06/11] toplevel-icon: implement OSC-1, CSI 20/21/22/23 t * The toplevel icon is now set to the app-id, unless "overridden" by OSC-1 or OSC-0. * Implemented OSC-1 * OSC-0 extended to also set the icon * Implemented CSI 20 t - report window icon * Implemented CSI 21 t - report window title * Implemented CSI 22 ; 1 t - push window icon * Implemented CS 23 ; 1 t - pop window icon * Extended CSI 22/23 ; 0 t to also push/pop the icon * Verify app-id set by OSC-176 is valid UTF-8 * Verify icon set by OSC-0/1 is valid UTF-8 --- CHANGELOG.md | 5 +++ csi.c | 31 +++++++++++++- doc/foot-ctlseqs.7.scd | 22 ++++++++-- misc.c | 7 ++++ misc.h | 2 + osc.c | 15 +++++-- render.c | 69 ++++++++++++++++++++++---------- render.h | 1 + terminal.c | 91 +++++++++++++++++++++++++++++++++++++++++- terminal.h | 9 +++++ 10 files changed, 220 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 564b62b2..1d0f5454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,11 @@ ([#1807][1807]). * `strikeout-thickness` option. * Implemented the new `xdg-toplevel-icon-v1` protocol. +* Implemented `CSI 20 t`: report window icon. +* Implemented `CSI 21 t`: report window title. +* Implemented `CSI 22 ; 1 t`: push window icon. +* Implemented `CSI 23 ; 1 t`: pop window icon. +* Implemented `OSC 1`: set window icon. [1807]: https://codeberg.org/dnkl/foot/issues/1807 diff --git a/csi.c b/csi.c index 7c6ea7ca..39821d8d 100644 --- a/csi.c +++ b/csi.c @@ -1249,8 +1249,6 @@ csi_dispatch(struct terminal *term, uint8_t final) case 8: LOG_WARN("unimplemented: resize window in chars"); break; case 9: LOG_WARN("unimplemented: maximize/unmaximize window"); break; case 10: LOG_WARN("unimplemented: to/from full screen"); break; - case 20: LOG_WARN("unimplemented: report icon label"); break; - case 21: LOG_WARN("unimplemented: report window title"); break; case 24: LOG_WARN("unimplemented: resize window (DECSLPP)"); break; case 11: /* report if window is iconified */ @@ -1354,6 +1352,24 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } + case 20: { + const char *icon = term_icon(term); + + char reply[3 + strlen(icon) + 2 + 1]; + int chars = xsnprintf( + reply, sizeof(reply), "\033]L%s\033\\", icon); + term_to_slave(term, reply, chars); + break; + } + + case 21: { + char reply[3 + strlen(term->window_title) + 2 + 1]; + int chars = xsnprintf( + reply, sizeof(reply), "\033]l%s\033\\", term->window_title); + term_to_slave(term, reply, chars); + break; + } + case 22: { /* push window title */ /* 0 - icon + title, 1 - icon, 2 - title */ unsigned what = vt_param_get(term, 1, 0); @@ -1361,6 +1377,10 @@ csi_dispatch(struct terminal *term, uint8_t final) tll_push_back( term->window_title_stack, xstrdup(term->window_title)); } + if (what == 0 || what == 1) { + tll_push_back( + term->window_icon_stack, xstrdup(term->window_icon)); + } break; } @@ -1374,6 +1394,13 @@ csi_dispatch(struct terminal *term, uint8_t final) free(title); } } + if (what == 0 || what == 1) { + if (tll_length(term->window_icon_stack) > 0) { + char *icon = tll_pop_back(term->window_icon_stack); + term_set_icon(term, icon); + free(icon); + } + } break; } diff --git a/doc/foot-ctlseqs.7.scd b/doc/foot-ctlseqs.7.scd index 998b6843..60f78d83 100644 --- a/doc/foot-ctlseqs.7.scd +++ b/doc/foot-ctlseqs.7.scd @@ -388,15 +388,27 @@ manipulation sequences. The generic format is: | 19 : - : Report screen size, in characters. +| 20 +: - +: Report icon label. +| 21 +: - +: Report window title. | 22 : - -: Push window title+icon. Foot does not support pushing the icon. +: Push window title+icon. +| 22 +: 1 +: Push window icon. | 22 : 2 : Push window title. | 23 : - -: Pop window title+icon. Foot does not support popping the icon. +: Pop window title+icon. +| 23 +: 1 +: Pop window icon. | 23 : 2 : Pop window title. @@ -659,8 +671,10 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_. :< *Description* | \\E] 0 ; _Pt_ \\E\\ : xterm -: Set window icon and title to _Pt_ (foot does not support setting the - icon) +: Set window icon and title to _Pt_. +| \\E] 1 ; _Pt_ \\E\\ +: xterm +: Set window icon to _Pt_. | \\E] 2 ; _Pt_ \\E\\ : xterm : Set window title to _Pt_ diff --git a/misc.c b/misc.c index a81aa9e4..1e5b9328 100644 --- a/misc.c +++ b/misc.c @@ -42,3 +42,10 @@ timespec_sub(const struct timespec *a, const struct timespec *b, res->tv_nsec += one_sec_in_ns; } } + +bool +is_valid_utf8(const char *value) +{ + return value != NULL && + mbsntoc32(NULL, value, strlen(value), 0) != (size_t)-1; +} diff --git a/misc.h b/misc.h index 648bb65f..cce8d2c1 100644 --- a/misc.h +++ b/misc.h @@ -8,3 +8,5 @@ bool isword(char32_t wc, bool spaces_only, const char32_t *delimiters); void timespec_add(const struct timespec *a, const struct timespec *b, struct timespec *res); void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res); + +bool is_valid_utf8(const char *value); diff --git a/osc.c b/osc.c index 0db11811..541a13f4 100644 --- a/osc.c +++ b/osc.c @@ -1145,9 +1145,18 @@ osc_dispatch(struct terminal *term) char *string = (char *)&term->vt.osc.data[data_ofs]; switch (param) { - case 0: term_set_window_title(term, string); break; /* icon + title */ - case 1: break; /* icon */ - case 2: term_set_window_title(term, string); break; /* title */ + case 0: /* icon + title */ + term_set_window_title(term, string); + term_set_icon(term, string); + break; + + case 1: /* icon */ + term_set_icon(term, string); + break; + + case 2: /* title */ + term_set_window_title(term, string); + break; case 4: { /* Set color */ diff --git a/render.c b/render.c index 93bad7fe..355aa40e 100644 --- a/render.c +++ b/render.c @@ -12,15 +12,19 @@ #include "macros.h" #if HAS_INCLUDE() -#include -#define pthread_setname_np(thread, name) (pthread_set_name_np(thread, name), 0) + #include + #define pthread_setname_np(thread, name) (pthread_set_name_np(thread, name), 0) #elif defined(__NetBSD__) -#define pthread_setname_np(thread, name) pthread_setname_np(thread, "%s", (void *)name) + #define pthread_setname_np(thread, name) pthread_setname_np(thread, "%s", (void *)name) #endif +#include #include #include -#include + +#if defined(HAVE_XDG_TOPLEVEL_ICON) +#include +#endif #include @@ -4951,26 +4955,49 @@ render_refresh_app_id(struct terminal *term) term->app_id != NULL ? term->app_id : term->conf->app_id; xdg_toplevel_set_app_id(term->window->xdg_toplevel, app_id); - -#if defined(HAVE_XDG_TOPLEVEL_ICON) - if (term->wl->toplevel_icon_manager != NULL) { - struct xdg_toplevel_icon_v1 *icon = - xdg_toplevel_icon_manager_v1_create_icon( - term->wl->toplevel_icon_manager); - - xdg_toplevel_icon_v1_set_name( - icon, streq(app_id, "footclient") ? "foot" : app_id); - - xdg_toplevel_icon_manager_v1_set_icon( - term->wl->toplevel_icon_manager, term->window->xdg_toplevel, icon); - - xdg_toplevel_icon_v1_destroy(icon); - } -#endif - term->render.app_id.last_update = now; } +void +render_refresh_icon(struct terminal *term) +{ +#if defined(HAVE_XDG_TOPLEVEL_ICON) + if (term->wl->toplevel_icon_manager == NULL) { + LOG_DBG("compositor does not implement xdg-toplevel-icon: " + "ignoring request to refresh window icon"); + return; + } + + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) + return; + + struct timespec diff; + timespec_sub(&now, &term->render.icon.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.icon.timer_fd, 0, &timeout, NULL); + return; + } + + const char *icon_name = term_icon(term); + LOG_DBG("setting toplevel icon: %s", icon_name); + + struct xdg_toplevel_icon_v1 *icon = + xdg_toplevel_icon_manager_v1_create_icon(term->wl->toplevel_icon_manager); + xdg_toplevel_icon_v1_set_name(icon, icon_name); + xdg_toplevel_icon_manager_v1_set_icon( + term->wl->toplevel_icon_manager, term->window->xdg_toplevel, icon); + xdg_toplevel_icon_v1_destroy(icon); + + term->render.icon.last_update = now; +#endif +} + void render_refresh(struct terminal *term) { diff --git a/render.h b/render.h index cfedf311..1898351c 100644 --- a/render.h +++ b/render.h @@ -22,6 +22,7 @@ bool render_resize( void render_refresh(struct terminal *term); void render_refresh_app_id(struct terminal *term); +void render_refresh_icon(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 453798e8..5e9a60a5 100644 --- a/terminal.c +++ b/terminal.c @@ -639,6 +639,30 @@ fdm_title_update_timeout(struct fdm *fdm, int fd, int events, void *data) return true; } +static bool +fdm_icon_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.icon.timer_fd, &unused, sizeof(unused)); + + if (ret < 0) { + if (errno == EAGAIN) + return true; + LOG_ERRNO("failed to read icon update throttle timer"); + return false; + } + + struct itimerspec reset = {{0}}; + timerfd_settime(term->render.icon.timer_fd, 0, &reset, NULL); + + render_refresh_icon(term); + return true; +} + static bool fdm_app_id_update_timeout(struct fdm *fdm, int fd, int events, void *data) { @@ -1114,6 +1138,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 icon_update_fd = -1; int app_id_update_fd = -1; struct terminal *term = malloc(sizeof(*term)); @@ -1150,6 +1175,12 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, goto close_fds; } + if ((icon_update_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0) + { + LOG_ERRNO("failed to create icon update throttle timer FD"); + 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"); @@ -1187,6 +1218,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, !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, icon_update_fd, EPOLLIN, &fdm_icon_update_timeout, term) || !fdm_add(fdm, app_id_update_fd, EPOLLIN, &fdm_app_id_update_timeout, term)) { goto err; @@ -1288,6 +1320,9 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .title = { .timer_fd = title_update_fd, }, + .icon = { + .timer_fd = icon_update_fd, + }, .app_id = { .timer_fd = app_id_update_fd, }, @@ -1406,6 +1441,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, icon_update_fd); fdm_del(fdm, app_id_update_fd); free(term); @@ -1626,6 +1662,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.icon.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); @@ -1677,6 +1714,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.icon.timer_fd = -1; term->render.title.timer_fd = -1; term->delayed_render_timer.lower_fd = -1; term->delayed_render_timer.upper_fd = -1; @@ -1731,6 +1769,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.icon.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); @@ -1775,7 +1814,9 @@ term_destroy(struct terminal *term) composed_free(term->composed); free(term->app_id); + free(term->window_icon); free(term->window_title); + tll_free_and_free(term->window_icon_stack, free); tll_free_and_free(term->window_title_stack, free); for (size_t i = 0; i < sizeof(term->fonts) / sizeof(term->fonts[0]); i++) @@ -2007,6 +2048,9 @@ term_reset(struct terminal *term, bool hard) term->saved_charsets = term->charsets; tll_free_and_free(term->window_title_stack, free); term_set_window_title(term, term->conf->title); + tll_free_and_free(term->window_icon_stack, free); + term_set_app_id(term, NULL); + term_set_icon(term, NULL); term_set_user_mouse_cursor(term, NULL); @@ -3528,7 +3572,7 @@ term_set_window_title(struct terminal *term, const char *title) if (term->window_title != NULL && streq(term->window_title, title)) return; - if (mbsntoc32(NULL, title, strlen(title), 0) == (size_t)-1) { + if (!is_valid_utf8(title)) { /* It's an xdg_toplevel::set_title() protocol violation to set a title with an invalid UTF-8 sequence */ LOG_WARN("%s: title is not valid UTF-8, ignoring", title); @@ -3548,9 +3592,14 @@ term_set_app_id(struct terminal *term, const char *app_id) 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) + if (term->app_id != NULL && app_id != NULL && streq(term->app_id, app_id)) return; + if (app_id != NULL && !is_valid_utf8(app_id)) { + LOG_WARN("%s: app-id is not valid UTF-8, ignoring", app_id); + return; + } + free(term->app_id); if (app_id != NULL) { term->app_id = xstrdup(app_id); @@ -3558,6 +3607,44 @@ term_set_app_id(struct terminal *term, const char *app_id) term->app_id = NULL; } render_refresh_app_id(term); + render_refresh_icon(term); +} + +void +term_set_icon(struct terminal *term, const char *icon) +{ + if (icon != NULL && *icon == '\0') + icon = NULL; + if (term->window_icon == NULL && icon == NULL) + return; + if (term->window_icon != NULL && icon != NULL && streq(term->window_icon, icon)) + return; + + if (icon != NULL && !is_valid_utf8(icon)) { + LOG_WARN("%s: icon label is not valid UTF-8, ignoring", icon); + return; + } + + free(term->window_icon); + if (icon != NULL) { + term->window_icon = xstrdup(icon); + } else { + term->window_icon = NULL; + } + render_refresh_icon(term); +} + +const char * +term_icon(const struct terminal *term) +{ + const char *app_id = + term->app_id != NULL ? term->app_id : term->conf->app_id; + + return term->window_icon != NULL + ? term->window_icon + : streq(app_id, "footclient") + ? "foot" + : app_id; } void diff --git a/terminal.h b/terminal.h index a87a125b..28576f23 100644 --- a/terminal.h +++ b/terminal.h @@ -552,6 +552,8 @@ struct terminal { bool window_title_has_been_set; char *window_title; tll(char *) window_title_stack; + char *window_icon; + tll(char *)window_icon_stack; char *app_id; struct { @@ -670,6 +672,11 @@ struct terminal { int timer_fd; } title; + struct { + struct timespec last_update; + int timer_fd; + } icon; + struct { struct timespec last_update; int timer_fd; @@ -925,6 +932,8 @@ 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_set_icon(struct terminal *term, const char *icon); +const char *term_icon(const struct terminal *term); void term_flash(struct terminal *term, unsigned duration_ms); void term_bell(struct terminal *term); bool term_spawn_new(const struct terminal *term); From f5caa2d265f347183864b20929be7f74fcef4da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 10 Sep 2024 19:13:00 +0200 Subject: [PATCH 07/11] pgo: add missing stub for render_refresh_icon() --- pgo/pgo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pgo/pgo.c b/pgo/pgo.c index f87863c0..aab18847 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -70,6 +70,7 @@ void render_refresh(struct terminal *term) {} void render_refresh_csd(struct terminal *term) {} void render_refresh_title(struct terminal *term) {} void render_refresh_app_id(struct terminal *term) {} +void render_refresh_icon(struct terminal *term) {} bool render_xcursor_is_valid(const struct seat *seat, const char *cursor) From c6208a98c80f4b945608b4ce1bd6f35fab5b56d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 13 Sep 2024 08:45:54 +0200 Subject: [PATCH 08/11] main: include toplevel-icon support in --version output --- foot-features.h | 9 +++++++++ main.c | 3 ++- wayland.c | 20 +++++++++++++------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/foot-features.h b/foot-features.h index ad447767..674c1056 100644 --- a/foot-features.h +++ b/foot-features.h @@ -37,3 +37,12 @@ static inline bool feature_graphemes(void) return false; #endif } + +static inline bool feature_xdg_toplevel_icon(void) +{ +#if defined(HAVE_XDG_TOPLEVEL_ICON) + return true; +#else + return false; +#endif +} diff --git a/main.c b/main.c index 15741012..973cbae4 100644 --- a/main.c +++ b/main.c @@ -51,11 +51,12 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %cassertions", + "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', + feature_xdg_toplevel_icon() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } diff --git a/wayland.c b/wayland.c index fd228312..9c184adc 100644 --- a/wayland.c +++ b/wayland.c @@ -1592,27 +1592,33 @@ wayl_init(struct fdm *fdm, struct key_binding_manager *key_binding_manager, goto out; } + if (presentation_timings && wayl->presentation == NULL) { + LOG_ERR("compositor does not implement the presentation time interface"); + goto out; + } + if (wayl->primary_selection_device_manager == NULL) - LOG_WARN("no primary selection available"); + LOG_WARN("compositor does not implement the primary selection interface"); if (wayl->xdg_activation == NULL) { LOG_WARN( - "no XDG activation support; " + "compositor does not implement XDG activation, " "bell.urgent will fall back to coloring the window margins red"); } if (wayl->fractional_scale_manager == NULL || wayl->viewporter == NULL) - LOG_WARN("fractional scaling not available"); + LOG_WARN("compositor does not implement fractional scaling"); if (wayl->cursor_shape_manager == NULL) { - LOG_WARN("no server-side cursors available, " + LOG_WARN("compositor does not implement server-side cursors, " "falling back to client-side cursors"); } - if (presentation_timings && wayl->presentation == NULL) { - LOG_ERR("presentation time interface not implemented by compositor"); - goto out; +#if defined(HAVE_XDG_TOPLEVEL_ICON) + if (wayl->toplevel_icon_manager == NULL) { + LOG_WARN("compositor does not implement the XDG toplevel icon protocol"); } +#endif #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED if (wayl->text_input_manager == NULL) { From 7984f08925e18e24e6b54ac4498d65fa26b2a4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 13 Sep 2024 08:51:12 +0200 Subject: [PATCH 09/11] osc: OSC-1 does not set the icon, it sets the icon _label_ In fact, there appears there *is* no escape sequence to set the icon. Keep most of the logic in place, but in practice, we'll always set the icon to the app-id. That is, at startup, we set it to the configured app-id (either from config, or the command line). OSC-176, which sets the app-id, also updates the icon (to the app-id). --- csi.c | 11 ----------- osc.c | 2 -- terminal.c | 36 ++++++------------------------------ terminal.h | 5 ++--- 4 files changed, 8 insertions(+), 46 deletions(-) diff --git a/csi.c b/csi.c index 39821d8d..61e0e44b 100644 --- a/csi.c +++ b/csi.c @@ -1377,10 +1377,6 @@ csi_dispatch(struct terminal *term, uint8_t final) tll_push_back( term->window_title_stack, xstrdup(term->window_title)); } - if (what == 0 || what == 1) { - tll_push_back( - term->window_icon_stack, xstrdup(term->window_icon)); - } break; } @@ -1394,13 +1390,6 @@ csi_dispatch(struct terminal *term, uint8_t final) free(title); } } - if (what == 0 || what == 1) { - if (tll_length(term->window_icon_stack) > 0) { - char *icon = tll_pop_back(term->window_icon_stack); - term_set_icon(term, icon); - free(icon); - } - } break; } diff --git a/osc.c b/osc.c index 541a13f4..5efc7588 100644 --- a/osc.c +++ b/osc.c @@ -1147,11 +1147,9 @@ osc_dispatch(struct terminal *term) switch (param) { case 0: /* icon + title */ term_set_window_title(term, string); - term_set_icon(term, string); break; case 1: /* icon */ - term_set_icon(term, string); break; case 2: /* title */ diff --git a/terminal.c b/terminal.c index 5e9a60a5..6d62a3ed 100644 --- a/terminal.c +++ b/terminal.c @@ -1814,9 +1814,7 @@ term_destroy(struct terminal *term) composed_free(term->composed); free(term->app_id); - free(term->window_icon); free(term->window_title); - tll_free_and_free(term->window_icon_stack, free); tll_free_and_free(term->window_title_stack, free); for (size_t i = 0; i < sizeof(term->fonts) / sizeof(term->fonts[0]); i++) @@ -2048,9 +2046,7 @@ term_reset(struct terminal *term, bool hard) term->saved_charsets = term->charsets; tll_free_and_free(term->window_title_stack, free); term_set_window_title(term, term->conf->title); - tll_free_and_free(term->window_icon_stack, free); term_set_app_id(term, NULL); - term_set_icon(term, NULL); term_set_user_mouse_cursor(term, NULL); @@ -3610,39 +3606,19 @@ term_set_app_id(struct terminal *term, const char *app_id) render_refresh_icon(term); } -void -term_set_icon(struct terminal *term, const char *icon) -{ - if (icon != NULL && *icon == '\0') - icon = NULL; - if (term->window_icon == NULL && icon == NULL) - return; - if (term->window_icon != NULL && icon != NULL && streq(term->window_icon, icon)) - return; - - if (icon != NULL && !is_valid_utf8(icon)) { - LOG_WARN("%s: icon label is not valid UTF-8, ignoring", icon); - return; - } - - free(term->window_icon); - if (icon != NULL) { - term->window_icon = xstrdup(icon); - } else { - term->window_icon = NULL; - } - render_refresh_icon(term); -} - const char * term_icon(const struct terminal *term) { const char *app_id = term->app_id != NULL ? term->app_id : term->conf->app_id; - return term->window_icon != NULL + return +#if 0 +term->window_icon != NULL ? term->window_icon - : streq(app_id, "footclient") + : + #endif + streq(app_id, "footclient") ? "foot" : app_id; } diff --git a/terminal.h b/terminal.h index 28576f23..e87df54c 100644 --- a/terminal.h +++ b/terminal.h @@ -552,8 +552,8 @@ struct terminal { bool window_title_has_been_set; char *window_title; tll(char *) window_title_stack; - char *window_icon; - tll(char *)window_icon_stack; + //char *window_icon; /* No escape sequence available to set the icon */ + //tll(char *)window_icon_stack; char *app_id; struct { @@ -932,7 +932,6 @@ 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_set_icon(struct terminal *term, const char *icon); const char *term_icon(const struct terminal *term); void term_flash(struct terminal *term, unsigned duration_ms); void term_bell(struct terminal *term); From 9151685d04ef3b572f0c574e559143a71f97a854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 13 Sep 2024 08:57:07 +0200 Subject: [PATCH 10/11] csi: revert implementation of CSI 20 t --- csi.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/csi.c b/csi.c index 61e0e44b..35a39f82 100644 --- a/csi.c +++ b/csi.c @@ -1249,6 +1249,7 @@ csi_dispatch(struct terminal *term, uint8_t final) case 8: LOG_WARN("unimplemented: resize window in chars"); break; case 9: LOG_WARN("unimplemented: maximize/unmaximize window"); break; case 10: LOG_WARN("unimplemented: to/from full screen"); break; + case 20: LOG_WARN("unimplemented: report icon label"); break; case 24: LOG_WARN("unimplemented: resize window (DECSLPP)"); break; case 11: /* report if window is iconified */ @@ -1352,16 +1353,6 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } - case 20: { - const char *icon = term_icon(term); - - char reply[3 + strlen(icon) + 2 + 1]; - int chars = xsnprintf( - reply, sizeof(reply), "\033]L%s\033\\", icon); - term_to_slave(term, reply, chars); - break; - } - case 21: { char reply[3 + strlen(term->window_title) + 2 + 1]; int chars = xsnprintf( From 76b58b566386598765b824458aa3670e064b6f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 13 Sep 2024 08:57:20 +0200 Subject: [PATCH 11/11] changelog: remove escape sequences we've reverted --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d0f5454..622617b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,11 +62,7 @@ ([#1807][1807]). * `strikeout-thickness` option. * Implemented the new `xdg-toplevel-icon-v1` protocol. -* Implemented `CSI 20 t`: report window icon. * Implemented `CSI 21 t`: report window title. -* Implemented `CSI 22 ; 1 t`: push window icon. -* Implemented `CSI 23 ; 1 t`: pop window icon. -* Implemented `OSC 1`: set window icon. [1807]: https://codeberg.org/dnkl/foot/issues/1807