Merge branch 'toplevel-icon'

This commit is contained in:
Daniel Eklöf 2024-09-13 09:20:50 +02:00
commit 33f3818520
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
16 changed files with 248 additions and 25 deletions

View file

@ -61,6 +61,8 @@
(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.
* Implemented `CSI 21 t`: report window title.
[1807]: https://codeberg.org/dnkl/foot/issues/1807

9
csi.c
View file

@ -1250,7 +1250,6 @@ csi_dispatch(struct terminal *term, uint8_t final)
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 +1353,14 @@ csi_dispatch(struct terminal *term, uint8_t final)
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);

View file

@ -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_

View file

@ -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
}

3
main.c
View file

@ -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;
}

View file

@ -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(),

7
misc.c
View file

@ -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
View file

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

13
osc.c
View file

@ -1145,9 +1145,16 @@ 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);
break;
case 1: /* icon */
break;
case 2: /* title */
term_set_window_title(term, string);
break;
case 4: {
/* Set color<idx> */

View file

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

View file

@ -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>
@ -4944,10 +4948,54 @@ 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);
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

View file

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

View file

@ -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);
@ -2007,6 +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);
term_set_app_id(term, NULL);
term_set_user_mouse_cursor(term, NULL);
@ -3528,7 +3568,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 +3588,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 +3603,24 @@ term_set_app_id(struct terminal *term, const char *app_id)
term->app_id = NULL;
}
render_refresh_app_id(term);
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
#if 0
term->window_icon != NULL
? term->window_icon
:
#endif
streq(app_id, "footclient")
? "foot"
: app_id;
}
void

View file

@ -552,6 +552,8 @@ struct terminal {
bool window_title_has_been_set;
char *window_title;
tll(char *) window_title_stack;
//char *window_icon; /* No escape sequence available to set the 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,7 @@ 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);
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);

View file

@ -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;
@ -1581,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) {
@ -1679,6 +1696,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)
@ -1796,6 +1817,21 @@ 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) {
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, 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);
}
#endif
if (conf->csd.preferred == CONF_CSD_PREFER_NONE) {
/* User specifically do *not* want decorations */
win->csd_mode = CSD_NO;

View file

@ -20,6 +20,10 @@
#include <xdg-output-unstable-v1.h>
#include <xdg-shell.h>
#if defined(HAVE_XDG_TOPLEVEL_ICON)
#include <xdg-toplevel-icon-v1.h>
#endif
#include <fcft/fcft.h>
#include <tllist.h>
@ -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;