From: tokyo4j Date: Fri, 30 May 2025 11:58:48 +0000 (+0900) Subject: Support xdg-toplevel-icon protocol X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=fb077c00950cc23997fe88aa5b04502cb5ec855e;p=proto%2Flabwc.git Support xdg-toplevel-icon protocol This patch also changes the semantics of scaled_icon_buffer: rather than calling scaled_icon_buffer_set_app_id() every time an app_id is set, we can now call scaled_icon_buffer_set_view() just once so that multiple scaled_icon_buffers bound to a window are automatically updated when an app_id is set or new icon is set via xdg-toplevel-icon-v1. --- diff --git a/include/common/scaled-icon-buffer.h b/include/common/scaled-icon-buffer.h index 54f9f176..b41cfcf9 100644 --- a/include/common/scaled-icon-buffer.h +++ b/include/common/scaled-icon-buffer.h @@ -3,6 +3,7 @@ #define LABWC_SCALED_ICON_BUFFER_H #include +#include struct wlr_scene_tree; struct wlr_scene_node; @@ -12,8 +13,18 @@ struct scaled_icon_buffer { struct scaled_scene_buffer *scaled_buffer; struct wlr_scene_buffer *scene_buffer; struct server *server; - char *app_id; + /* for window icon */ + struct view *view; + char *view_app_id; + char *view_icon_name; + struct wl_array view_icon_buffers; + struct { + struct wl_listener set_icon; + struct wl_listener destroy; + } on_view; + /* for general icon (e.g. in menus) */ char *icon_name; + int width; int height; }; @@ -28,8 +39,8 @@ struct scaled_icon_buffer *scaled_icon_buffer_create( struct wlr_scene_tree *parent, struct server *server, int width, int height); -void scaled_icon_buffer_set_app_id(struct scaled_icon_buffer *self, - const char *app_id); +void scaled_icon_buffer_set_view(struct scaled_icon_buffer *self, + struct view *view); void scaled_icon_buffer_set_icon_name(struct scaled_icon_buffer *self, const char *icon_name); diff --git a/include/labwc.h b/include/labwc.h index f9108700..2fd9327a 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -264,6 +264,9 @@ struct server { struct wl_listener xdg_activation_request; struct wl_listener xdg_activation_new_token; + struct wlr_xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager; + struct wl_listener xdg_toplevel_icon_set_icon; + struct wl_list views; struct wl_list unmanaged_surfaces; diff --git a/include/ssd.h b/include/ssd.h index 5cbac60a..eda0370e 100644 --- a/include/ssd.h +++ b/include/ssd.h @@ -94,7 +94,6 @@ void ssd_update_title(struct ssd *ssd); void ssd_update_geometry(struct ssd *ssd); void ssd_destroy(struct ssd *ssd); void ssd_set_titlebar(struct ssd *ssd, bool enabled); -void ssd_update_window_icon(struct ssd *ssd); void ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable); void ssd_enable_shade(struct ssd *ssd, bool enable); diff --git a/include/view.h b/include/view.h index 50ed620d..613f9160 100644 --- a/include/view.h +++ b/include/view.h @@ -281,6 +281,12 @@ struct view { struct foreign_toplevel *foreign_toplevel; + /* used by scaled_icon_buffer */ + struct { + char *name; + struct wl_array buffers; /* struct lab_data_buffer * */ + } icon; + struct { struct wl_signal new_app_id; struct wl_signal new_title; @@ -289,6 +295,11 @@ struct view { struct wl_signal minimized; struct wl_signal fullscreened; struct wl_signal activated; /* bool *activated */ + /* + * This is emitted when app_id, or icon set via xdg_toplevel_icon + * is updated. This is listened by scaled_icon_buffer. + */ + struct wl_signal set_icon; struct wl_signal destroy; } events; }; @@ -614,6 +625,10 @@ int view_get_min_width(void); void view_set_shade(struct view *view, bool shaded); +/* Icon buffers set with this function are dropped later */ +void view_set_icon(struct view *view, const char *icon_name, + struct wl_array *buffers); + struct view_size_hints view_get_size_hints(struct view *view); void view_adjust_size(struct view *view, int *w, int *h); diff --git a/src/common/scaled-icon-buffer.c b/src/common/scaled-icon-buffer.c index a96df609..5674c6dd 100644 --- a/src/common/scaled-icon-buffer.c +++ b/src/common/scaled-icon-buffer.c @@ -2,6 +2,8 @@ #define _POSIX_C_SOURCE 200809L #include #include +#include +#include "buffer.h" #include "common/macros.h" #include "common/mem.h" #include "common/scaled-icon-buffer.h" @@ -12,6 +14,35 @@ #include "desktop-entry.h" #include "img/img.h" #include "node.h" +#include "view.h" + +#if HAVE_LIBSFDO + +static struct lab_data_buffer * +choose_best_icon_buffer(struct scaled_icon_buffer *self, int icon_size, double scale) +{ + int best_dist = INT_MIN; + struct lab_data_buffer *best_buffer = NULL; + + struct lab_data_buffer **buffer; + wl_array_for_each(buffer, &self->view_icon_buffers) { + int curr_dist = (*buffer)->base.width - (int)(icon_size * scale); + bool curr_is_better; + if ((curr_dist < 0 && best_dist > 0) + || (curr_dist > 0 && best_dist < 0)) { + /* prefer too big icon over too small icon */ + curr_is_better = curr_dist > 0; + } else { + curr_is_better = abs(curr_dist) < abs(best_dist); + } + if (curr_is_better) { + best_dist = curr_dist; + best_buffer = *buffer; + } + } + return best_buffer; +} +#endif /* HAVE_LIBSFDO */ static struct lab_data_buffer * _create_buffer(struct scaled_scene_buffer *scaled_buffer, double scale) @@ -24,10 +55,29 @@ _create_buffer(struct scaled_scene_buffer *scaled_buffer, double scale) if (self->icon_name) { img = desktop_entry_load_icon(self->server, self->icon_name, icon_size, scale); - } else if (self->app_id) { - img = desktop_entry_load_icon_from_app_id(self->server, - self->app_id, icon_size, scale); + } else if (self->view) { + if (self->view_icon_name) { + wlr_log(WLR_DEBUG, "loading icon by name: %s", + self->view_icon_name); + img = desktop_entry_load_icon(self->server, + self->view_icon_name, icon_size, scale); + } if (!img) { + struct lab_data_buffer *buffer = + choose_best_icon_buffer(self, icon_size, scale); + if (buffer) { + wlr_log(WLR_DEBUG, "loading icon by buffer"); + return buffer_resize(buffer, + self->width, self->height, scale); + } + } + if (!img) { + wlr_log(WLR_DEBUG, "loading icon by app_id"); + img = desktop_entry_load_icon_from_app_id(self->server, + self->view_app_id, icon_size, scale); + } + if (!img) { + wlr_log(WLR_DEBUG, "loading fallback icon"); img = desktop_entry_load_icon(self->server, rc.fallback_app_icon_name, icon_size, scale); } @@ -44,18 +94,53 @@ _create_buffer(struct scaled_scene_buffer *scaled_buffer, double scale) return buffer; #else return NULL; -#endif +#endif /* HAVE_LIBSFDO */ +} + +static void +set_icon_buffers(struct scaled_icon_buffer *self, struct wl_array *buffers) +{ + struct lab_data_buffer **icon_buffer; + wl_array_for_each(icon_buffer, &self->view_icon_buffers) { + wlr_buffer_unlock(&(*icon_buffer)->base); + } + wl_array_release(&self->view_icon_buffers); + wl_array_init(&self->view_icon_buffers); + + if (!buffers) { + return; + } + + wl_array_for_each(icon_buffer, buffers) { + wlr_buffer_lock(&(*icon_buffer)->base); + } + wl_array_copy(&self->view_icon_buffers, buffers); } static void _destroy(struct scaled_scene_buffer *scaled_buffer) { struct scaled_icon_buffer *self = scaled_buffer->data; - free(self->app_id); + if (self->view) { + wl_list_remove(&self->on_view.set_icon.link); + wl_list_remove(&self->on_view.destroy.link); + } + free(self->view_app_id); + free(self->view_icon_name); + set_icon_buffers(self, NULL); free(self->icon_name); free(self); } +static bool +icon_buffers_equal(struct wl_array *a, struct wl_array *b) +{ + if (a->size != b->size) { + return false; + } + return a->size == 0 || !memcmp(a->data, b->data, a->size); +} + static bool _equal(struct scaled_scene_buffer *scaled_buffer_a, struct scaled_scene_buffer *scaled_buffer_b) @@ -63,7 +148,9 @@ _equal(struct scaled_scene_buffer *scaled_buffer_a, struct scaled_icon_buffer *a = scaled_buffer_a->data; struct scaled_icon_buffer *b = scaled_buffer_b->data; - return str_equal(a->app_id, b->app_id) + return str_equal(a->view_app_id, b->view_app_id) + && str_equal(a->view_icon_name, b->view_icon_name) + && icon_buffers_equal(&a->view_icon_buffers, &b->view_icon_buffers) && str_equal(a->icon_name, b->icon_name) && a->width == b->width && a->height == b->height; @@ -96,15 +183,54 @@ scaled_icon_buffer_create(struct wlr_scene_tree *parent, struct server *server, return self; } +static void +handle_view_set_icon(struct wl_listener *listener, void *data) +{ + struct scaled_icon_buffer *self = + wl_container_of(listener, self, on_view.set_icon); + + /* view_get_string_prop() never returns NULL */ + xstrdup_replace(self->view_app_id, view_get_string_prop(self->view, "app_id")); + zfree(self->view_icon_name); + if (self->view->icon.name) { + self->view_icon_name = xstrdup(self->view->icon.name); + } + set_icon_buffers(self, &self->view->icon.buffers); + + scaled_scene_buffer_request_update(self->scaled_buffer, + self->width, self->height); +} + +static void +handle_view_destroy(struct wl_listener *listener, void *data) +{ + struct scaled_icon_buffer *self = + wl_container_of(listener, self, on_view.destroy); + wl_list_remove(&self->on_view.destroy.link); + wl_list_remove(&self->on_view.set_icon.link); + self->view = NULL; +} + void -scaled_icon_buffer_set_app_id(struct scaled_icon_buffer *self, - const char *app_id) +scaled_icon_buffer_set_view(struct scaled_icon_buffer *self, struct view *view) { - assert(app_id); - if (str_equal(self->app_id, app_id)) { + assert(view); + if (self->view == view) { return; } - xstrdup_replace(self->app_id, app_id); + + if (self->view) { + wl_list_remove(&self->on_view.set_icon.link); + wl_list_remove(&self->on_view.destroy.link); + } + self->view = view; + if (view) { + self->on_view.set_icon.notify = handle_view_set_icon; + wl_signal_add(&view->events.set_icon, &self->on_view.set_icon); + self->on_view.destroy.notify = handle_view_destroy; + wl_signal_add(&view->events.destroy, &self->on_view.destroy); + } + scaled_scene_buffer_request_update(self->scaled_buffer, self->width, self->height); } diff --git a/src/menu/menu.c b/src/menu/menu.c index 9ce98dc9..0c2d0c3d 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -199,9 +199,8 @@ item_create_scene_for_state(struct menuitem *item, float *text_color, scaled_icon_buffer_set_icon_name(icon_buffer, item->icon_name); } else if (show_app_icon) { /* app icon in client-list-combined-menu */ - const char *app_id = view_get_string_prop( - item->client_list_view, "app_id"); - scaled_icon_buffer_set_app_id(icon_buffer, app_id); + scaled_icon_buffer_set_view(icon_buffer, + item->client_list_view); } wlr_scene_node_set_position(&icon_buffer->scene_buffer->node, theme->menu_items_padding_x, theme->menu_items_padding_y); diff --git a/src/osd.c b/src/osd.c index c78d5929..45b4b2de 100644 --- a/src/osd.c +++ b/src/osd.c @@ -371,8 +371,7 @@ create_osd_scene(struct output *output, struct wl_array *views) struct scaled_icon_buffer *icon_buffer = scaled_icon_buffer_create(item_root, server, icon_size, icon_size); - scaled_icon_buffer_set_app_id(icon_buffer, - view_get_string_prop(*view, "app_id")); + scaled_icon_buffer_set_view(icon_buffer, *view); node = &icon_buffer->scene_buffer->node; height = icon_size; } else { diff --git a/src/server.c b/src/server.c index e9d52136..61244792 100644 --- a/src/server.c +++ b/src/server.c @@ -274,6 +274,7 @@ allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, "zwp_tablet_manager_v2", "zxdg_importer_v1", "zxdg_importer_v2", + "xdg_toplevel_icon_manager_v1", /* plus */ "zxdg_exporter_v1", "zxdg_exporter_v2", diff --git a/src/ssd/ssd-part.c b/src/ssd/ssd-part.c index dbfa58f0..010d494a 100644 --- a/src/ssd/ssd-part.c +++ b/src/ssd/ssd-part.c @@ -113,6 +113,7 @@ add_scene_button(struct wl_list *part_list, enum ssd_part_type type, struct scaled_icon_buffer *icon_buffer = scaled_icon_buffer_create(parent, view->server, button_width - 2 * icon_padding, button_height); + scaled_icon_buffer_set_view(icon_buffer, view); assert(icon_buffer); icon_part->node = &icon_buffer->scene_buffer->node; wlr_scene_node_set_position(icon_part->node, icon_padding, 0); diff --git a/src/ssd/ssd-titlebar.c b/src/ssd/ssd-titlebar.c index 47cca7a2..84d28f75 100644 --- a/src/ssd/ssd-titlebar.c +++ b/src/ssd/ssd-titlebar.c @@ -94,7 +94,6 @@ ssd_titlebar_create(struct ssd *ssd) update_visible_buttons(ssd); ssd_update_title(ssd); - ssd_update_window_icon(ssd); bool maximized = view->maximized == VIEW_AXIS_BOTH; bool squared = ssd_should_be_squared(ssd); @@ -324,7 +323,6 @@ ssd_titlebar_update(struct ssd *ssd) } FOR_EACH_END ssd_update_title(ssd); - ssd_update_window_icon(ssd); } void @@ -565,34 +563,4 @@ ssd_should_be_squared(struct ssd *ssd) && view->maximized != VIEW_AXIS_BOTH; } -void -ssd_update_window_icon(struct ssd *ssd) -{ -#if HAVE_LIBSFDO - if (!ssd) { - return; - } - - /* - * When app id is not set, an empty string is stored here and the - * fallback icon is always rendered. - */ - const char *app_id = view_get_string_prop(ssd->view, "app_id"); - assert(app_id); - - struct ssd_sub_tree *subtree; - FOR_EACH_STATE(ssd, subtree) { - struct ssd_part *part = ssd_get_part( - &subtree->parts, LAB_SSD_BUTTON_WINDOW_ICON); - if (!part) { - break; - } - - struct ssd_button *button = node_ssd_button_from_node(part->node); - assert(button->window_icon); - scaled_icon_buffer_set_app_id(button->window_icon, app_id); - } FOR_EACH_END -#endif -} - #undef FOR_EACH_STATE diff --git a/src/view.c b/src/view.c index 3a6e692c..b9879436 100644 --- a/src/view.c +++ b/src/view.c @@ -4,6 +4,7 @@ #include #include #include +#include "buffer.h" #include "common/box.h" #include "common/list.h" #include "common/macros.h" @@ -2371,10 +2372,8 @@ void view_update_app_id(struct view *view) { assert(view); - if (view->ssd_enabled) { - ssd_update_window_icon(view->ssd); - } wl_signal_emit_mutable(&view->events.new_app_id, NULL); + wl_signal_emit_mutable(&view->events.set_icon, NULL); } void @@ -2499,6 +2498,29 @@ view_set_shade(struct view *view, bool shaded) wlr_scene_node_set_enabled(&view->content_tree->node, !view->shaded); } +void +view_set_icon(struct view *view, const char *icon_name, struct wl_array *buffers) +{ + /* Update icon name */ + zfree(view->icon.name); + if (icon_name) { + view->icon.name = xstrdup(icon_name); + } + + /* Update icon images */ + struct lab_data_buffer **buffer; + wl_array_for_each(buffer, &view->icon.buffers) { + wlr_buffer_drop(&(*buffer)->base); + } + wl_array_release(&view->icon.buffers); + wl_array_init(&view->icon.buffers); + if (buffers) { + wl_array_copy(&view->icon.buffers, buffers); + } + + wl_signal_emit_mutable(&view->events.set_icon, NULL); +} + void view_init(struct view *view) { @@ -2511,6 +2533,7 @@ view_init(struct view *view) wl_signal_init(&view->events.minimized); wl_signal_init(&view->events.fullscreened); wl_signal_init(&view->events.activated); + wl_signal_init(&view->events.set_icon); wl_signal_init(&view->events.destroy); } @@ -2569,6 +2592,10 @@ view_destroy(struct view *view) osd_on_view_destroy(view); undecorate(view); + if (view->icon.buffers.data) { + view_set_icon(view, NULL, NULL); + } + /* * The layer-shell top-layer is disabled when an application is running * in fullscreen mode, so if that's the case, we may have to re-enable diff --git a/src/xdg.c b/src/xdg.c index 6430d7b6..55a7af5e 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -2,7 +2,9 @@ #include #include - +#include +#include "buffer.h" +#include "common/array.h" #include "common/macros.h" #include "common/mem.h" #include "decorations.h" @@ -999,6 +1001,38 @@ xdg_toplevel_new(struct wl_listener *listener, void *data) wl_list_insert(&server->views, &view->link); } +static void +handle_xdg_toplevel_icon_set_icon(struct wl_listener *listener, void *data) +{ + struct wlr_xdg_toplevel_icon_manager_v1_set_icon_event *event = data; + + struct server *server = + wl_container_of(listener, server, xdg_toplevel_icon_set_icon); + struct wlr_xdg_surface *xdg_surface = event->toplevel->base; + struct view *view = xdg_surface->data; + assert(view); + + char *icon_name = NULL; + struct wl_array buffers; + wl_array_init(&buffers); + + if (event->icon) { + icon_name = event->icon->name; + + struct wlr_xdg_toplevel_icon_v1_buffer *icon_buffer; + wl_list_for_each(icon_buffer, &event->icon->buffers, link) { + struct lab_data_buffer *buffer = + buffer_create_from_wlr_buffer(icon_buffer->buffer); + if (buffer) { + array_add(&buffers, buffer); + } + } + } + + view_set_icon(view, icon_name, &buffers); + wl_array_release(&buffers); +} + void xdg_shell_init(struct server *server) { @@ -1025,6 +1059,12 @@ xdg_shell_init(struct server *server) server->xdg_activation_new_token.notify = handle_xdg_activation_new_token; wl_signal_add(&server->xdg_activation->events.new_token, &server->xdg_activation_new_token); + + server->xdg_toplevel_icon_manager = wlr_xdg_toplevel_icon_manager_v1_create( + server->wl_display, 1); + server->xdg_toplevel_icon_set_icon.notify = handle_xdg_toplevel_icon_set_icon; + wl_signal_add(&server->xdg_toplevel_icon_manager->events.set_icon, + &server->xdg_toplevel_icon_set_icon); } void @@ -1033,4 +1073,5 @@ xdg_shell_finish(struct server *server) wl_list_remove(&server->new_xdg_toplevel.link); wl_list_remove(&server->xdg_activation_request.link); wl_list_remove(&server->xdg_activation_new_token.link); + wl_list_remove(&server->xdg_toplevel_icon_set_icon.link); }