mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-16 22:05:21 -05:00
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
This commit is contained in:
parent
3f8a1fc85b
commit
97ec375c67
10 changed files with 220 additions and 32 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
31
csi.c
31
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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_
|
||||
|
|
|
|||
7
misc.c
7
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;
|
||||
}
|
||||
|
|
|
|||
2
misc.h
2
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);
|
||||
|
|
|
|||
15
osc.c
15
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<idx> */
|
||||
|
|
|
|||
69
render.c
69
render.c
|
|
@ -12,15 +12,19 @@
|
|||
|
||||
#include "macros.h"
|
||||
#if HAS_INCLUDE(<pthread_np.h>)
|
||||
#include <pthread_np.h>
|
||||
#define pthread_setname_np(thread, name) (pthread_set_name_np(thread, name), 0)
|
||||
#include <pthread_np.h>
|
||||
#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 <presentation-time.h>
|
||||
#include <wayland-cursor.h>
|
||||
#include <xdg-shell.h>
|
||||
#include <presentation-time.h>
|
||||
|
||||
#if defined(HAVE_XDG_TOPLEVEL_ICON)
|
||||
#include <xdg-toplevel-icon-v1.h>
|
||||
#endif
|
||||
|
||||
#include <fcft/fcft.h>
|
||||
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
1
render.h
1
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);
|
||||
|
|
|
|||
91
terminal.c
91
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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue