]> git.mdlowis.com Git - proto/labwc.git/commitdiff
Support xdg-toplevel-icon protocol
authortokyo4j <hrak1529@gmail.com>
Fri, 30 May 2025 11:58:48 +0000 (20:58 +0900)
committerHiroaki Yamamoto <hrak1529@gmail.com>
Fri, 6 Jun 2025 17:12:56 +0000 (02:12 +0900)
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.

12 files changed:
include/common/scaled-icon-buffer.h
include/labwc.h
include/ssd.h
include/view.h
src/common/scaled-icon-buffer.c
src/menu/menu.c
src/osd.c
src/server.c
src/ssd/ssd-part.c
src/ssd/ssd-titlebar.c
src/view.c
src/xdg.c

index 54f9f17699cdfe6b5ffbc168624ccd6df6662824..b41cfcf9eaaaf834bd1a22297bcf5dc55bfe31d8 100644 (file)
@@ -3,6 +3,7 @@
 #define LABWC_SCALED_ICON_BUFFER_H
 
 #include <stdbool.h>
+#include <wayland-server-core.h>
 
 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);
index f910870044d30a374aef4e7fa01c28996dbeb09c..2fd9327ab10656f2c831f9405235b6ba3459bc76 100644 (file)
@@ -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;
 
index 5cbac60a8d35e24e73af40c705cbc7d5050fdc5d..eda0370ed71d4573991bd009b69bbb847f34ec28 100644 (file)
@@ -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);
index 50ed620db071e2f9b1f2635ba77ed520317e5550..613f9160a5abcd5bbb21bd61fa1f80b66054d641 100644 (file)
@@ -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);
 
index a96df609ef60297b40afbc6dd6fe484aa79c6cc5..5674c6ddc332420aff885a1c4bd0c8922b947072 100644 (file)
@@ -2,6 +2,8 @@
 #define _POSIX_C_SOURCE 200809L
 #include <assert.h>
 #include <string.h>
+#include <wlr/util/log.h>
+#include "buffer.h"
 #include "common/macros.h"
 #include "common/mem.h"
 #include "common/scaled-icon-buffer.h"
 #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);
 }
 
index 9ce98dc94ff561787563693e6cb8ae7c5d688cf0..0c2d0c3d3a23f67bf6320743225bb2266adacffa 100644 (file)
@@ -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);
index c78d5929003b70864a6a0575160adad24ad0161a..45b4b2de54139c5ba00182bb26fad49b89cebefb 100644 (file)
--- 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 {
index e9d5213636f99054a72c21bf30d8130884b24c83..61244792877d50eea60d87570c167020a2d46065 100644 (file)
@@ -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",
index dbfa58f0b9faf550e6183b823470d6cf70a1a097..010d494af1487b3d67854a9bea2aa56b8ef2b0a5 100644 (file)
@@ -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);
index 47cca7a243c548da947d0a61b691f89d2dc3832d..84d28f751c2b837e1d15a096b0db4e19163c74c0 100644 (file)
@@ -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
index 3a6e692ca1efc3ad9c0ddf9e95b622cfcbbf2b3a..b9879436b27f6f56394a2d7ec8b32f7bda085d84 100644 (file)
@@ -4,6 +4,7 @@
 #include <strings.h>
 #include <wlr/types/wlr_output_layout.h>
 #include <wlr/types/wlr_security_context_v1.h>
+#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
index 6430d7b66daa6f3c857f862d87e96ac1b1ae3160..55a7af5e3d110d90af53ee91d7cf8528d79cc973 100644 (file)
--- a/src/xdg.c
+++ b/src/xdg.c
@@ -2,7 +2,9 @@
 
 #include <assert.h>
 #include <wlr/types/wlr_fractional_scale_v1.h>
-
+#include <wlr/types/wlr_xdg_toplevel_icon_v1.h>
+#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);
 }