Bladeren bron

wayland: Add support for setting window icons via the xdg-toplevel-icon-v1 protocol

Frank Praznik 6 maanden geleden
bovenliggende
commit
a86f8dedb9

+ 9 - 0
src/video/wayland/SDL_waylandvideo.c

@@ -59,6 +59,7 @@
 #include "primary-selection-unstable-v1-client-protocol.h"
 #include "fractional-scale-v1-client-protocol.h"
 #include "cursor-shape-v1-client-protocol.h"
+#include "xdg-toplevel-icon-v1-client-protocol.h"
 
 #ifdef HAVE_LIBDECOR_H
 #include <libdecor.h>
@@ -276,6 +277,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
     device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize;
     device->SetWindowModalFor = Wayland_SetWindowModalFor;
     device->SetWindowTitle = Wayland_SetWindowTitle;
+    device->SetWindowIcon = Wayland_SetWindowIcon;
     device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels;
     device->DestroyWindow = Wayland_DestroyWindow;
     device->SetWindowHitTest = Wayland_SetWindowHitTest;
@@ -880,6 +882,8 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
         if (d->input) {
             Wayland_CreateCursorShapeDevice(d->input);
         }
+    } else if (SDL_strcmp(interface, "xdg_toplevel_icon_manager_v1") == 0) {
+            d->xdg_toplevel_icon_manager_v1 = wl_registry_bind(d->registry, id, &xdg_toplevel_icon_manager_v1_interface, 1);
 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
     } else if (SDL_strcmp(interface, "qt_touch_extension") == 0) {
         Wayland_touch_create(d, id);
@@ -1128,6 +1132,11 @@ static void Wayland_VideoCleanup(_THIS)
         data->cursor_shape_manager = NULL;
     }
 
+    if (data->xdg_toplevel_icon_manager_v1) {
+        xdg_toplevel_icon_manager_v1_destroy(data->xdg_toplevel_icon_manager_v1);
+        data->xdg_toplevel_icon_manager_v1 = NULL;
+    }
+
     if (data->compositor) {
         wl_compositor_destroy(data->compositor);
         data->compositor = NULL;

+ 1 - 0
src/video/wayland/SDL_waylandvideo.h

@@ -78,6 +78,7 @@ typedef struct
     struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager;
     struct xdg_activation_v1 *activation_manager;
     struct zwp_text_input_manager_v3 *text_input_manager;
+    struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1;
     struct zxdg_output_manager_v1 *xdg_output_manager;
     struct wp_viewporter *viewporter;
     struct wp_fractional_scale_manager_v1 *fractional_scale_manager;

+ 60 - 0
src/video/wayland/SDL_waylandwindow.c

@@ -31,6 +31,7 @@
 #include "SDL_waylandwindow.h"
 #include "SDL_waylandvideo.h"
 #include "SDL_waylandtouch.h"
+#include "SDL_waylandshmbuffer.h"
 #include "SDL_hints.h"
 #include "../../SDL_hints_c.h"
 #include "SDL_events.h"
@@ -41,6 +42,7 @@
 #include "xdg-activation-v1-client-protocol.h"
 #include "viewporter-client-protocol.h"
 #include "fractional-scale-v1-client-protocol.h"
+#include "xdg-toplevel-icon-v1-client-protocol.h"
 
 #ifdef HAVE_LIBDECOR_H
 #include <libdecor.h>
@@ -1273,6 +1275,12 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
         } else {
             libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, c->classname);
             libdecor_frame_map(data->shell_surface.libdecor.frame);
+
+            if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) {
+                xdg_toplevel_icon_manager_v1_set_icon(c->xdg_toplevel_icon_manager_v1,
+                                                      libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame),
+                                                      data->xdg_toplevel_icon_v1);
+            }
         }
     } else
 #endif
@@ -1317,6 +1325,12 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
             xdg_toplevel_set_app_id(data->shell_surface.xdg.roleobj.toplevel, c->classname);
             xdg_toplevel_add_listener(data->shell_surface.xdg.roleobj.toplevel, &toplevel_listener_xdg, data);
 
+            if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) {
+                xdg_toplevel_icon_manager_v1_set_icon(c->xdg_toplevel_icon_manager_v1,
+                                                      data->shell_surface.xdg.roleobj.toplevel,
+                                                      data->xdg_toplevel_icon_v1);
+            }
+
             SetMinMaxDimensions(window, SDL_FALSE);
         }
     }
@@ -2172,6 +2186,46 @@ void Wayland_SetWindowTitle(_THIS, SDL_Window *window)
     WAYLAND_wl_display_flush(viddata->display);
 }
 
+void Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
+{
+    SDL_WindowData *wind = window->driverdata;
+    SDL_VideoData *viddata = _this->driverdata;
+    struct xdg_toplevel *toplevel = NULL;
+
+    if (!viddata->xdg_toplevel_icon_manager_v1) {
+        SDL_SetError("wayland: cannot set icon; xdg_toplevel_icon_v1 protocol not supported");
+        return;
+    }
+    if (icon->w != icon->h) {
+        SDL_SetError("wayland: icon width and height must be equal, got %ix%i", icon->w, icon->h);
+        return;
+    }
+    if (wind->xdg_toplevel_icon_v1) {
+        xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1);
+        wind->xdg_toplevel_icon_v1 = NULL;
+    }
+
+    Wayland_ReleaseSHMBuffer(&wind->icon);
+    if (Wayland_AllocSHMBuffer(icon->w, icon->h, &wind->icon) != 0) {
+        SDL_SetError("wayland: failed to allocate SHM buffer for the icon");
+        return;
+    }
+    SDL_PremultiplyAlpha(icon->w, icon->h, icon->format->format, icon->pixels, icon->pitch, SDL_PIXELFORMAT_ARGB8888, wind->icon.shm_data, icon->w * 4);
+    wind->xdg_toplevel_icon_v1 = xdg_toplevel_icon_manager_v1_create_icon(viddata->xdg_toplevel_icon_manager_v1);
+    xdg_toplevel_icon_v1_add_buffer(wind->xdg_toplevel_icon_v1, wind->icon.wl_buffer, 1);
+#ifdef HAVE_LIBDECOR_H
+    if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR && wind->shell_surface.libdecor.frame) {
+        toplevel = libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame);
+    } else
+#endif
+        if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && wind->shell_surface.xdg.roleobj.toplevel) {
+            toplevel = wind->shell_surface.xdg.roleobj.toplevel;
+        }
+    if (toplevel) {
+        xdg_toplevel_icon_manager_v1_set_icon(viddata->xdg_toplevel_icon_manager_v1, toplevel, wind->xdg_toplevel_icon_v1);
+    }
+}
+
 void Wayland_SuspendScreenSaver(_THIS)
 {
     SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
@@ -2240,6 +2294,12 @@ void Wayland_DestroyWindow(_THIS, SDL_Window *window)
             wp_fractional_scale_v1_destroy(wind->fractional_scale);
         }
 
+        if (wind->xdg_toplevel_icon_v1) {
+            xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1);
+        }
+
+        Wayland_ReleaseSHMBuffer(&wind->icon);
+
         SDL_free(wind->outputs);
 
         if (wind->gles_swap_frame_callback) {

+ 5 - 0
src/video/wayland/SDL_waylandwindow.h

@@ -29,6 +29,7 @@
 #include "../../events/SDL_touch_c.h"
 
 #include "SDL_waylandvideo.h"
+#include "SDL_waylandshmbuffer.h"
 
 struct SDL_WaylandInput;
 
@@ -89,6 +90,9 @@ typedef struct
     struct xdg_activation_token_v1 *activation_token;
     struct wp_viewport *draw_viewport;
     struct wp_fractional_scale_v1 *fractional_scale;
+    struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1;
+
+    struct Wayland_SHMBuffer icon;
 
     /* floating dimensions for restoring from maximized and fullscreen */
     int floating_width, floating_height;
@@ -142,6 +146,7 @@ extern int Wayland_SetWindowModalFor(_THIS, SDL_Window *modal_window, SDL_Window
 extern void Wayland_SetWindowTitle(_THIS, SDL_Window *window);
 extern void Wayland_DestroyWindow(_THIS, SDL_Window *window);
 extern void Wayland_SuspendScreenSaver(_THIS);
+extern void Wayland_SetWindowIcon(_THIS, SDL_Window *window, SDL_Surface *icon);
 
 extern SDL_bool
 Wayland_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info);

+ 205 - 0
wayland-protocols/xdg-toplevel-icon-v1.xml

@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="xdg_toplevel_icon_v1">
+
+  <copyright>
+    Copyright © 2023-2024 Matthias Klumpp
+    Copyright ©      2024 David Edmundson
+
+    Permission is hereby granted, free of charge, to any person obtaining a
+    copy of this software and associated documentation files (the "Software"),
+    to deal in the Software without restriction, including without limitation
+    the rights to use, copy, modify, merge, publish, distribute, sublicense,
+    and/or sell copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice (including the next
+    paragraph) shall be included in all copies or substantial portions of the
+    Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+  </copyright>
+
+  <description summary="protocol to assign icons to toplevels">
+    This protocol allows clients to set icons for their toplevel surfaces
+    either via the XDG icon stock (using an icon name), or from pixel data.
+
+    A toplevel icon represents the individual toplevel (unlike the application
+    or launcher icon, which represents the application as a whole), and may be
+    shown in window switchers, window overviews and taskbars that list
+    individual windows.
+
+    This document adheres to RFC 2119 when using words like "must",
+    "should", "may", etc.
+
+    Warning! The protocol described in this file is currently in the testing
+    phase. Backward compatible changes may be added together with the
+    corresponding interface version bump. Backward incompatible changes can
+    only be done by creating a new major version of the extension.
+  </description>
+
+  <interface name="xdg_toplevel_icon_manager_v1" version="1">
+    <description summary="interface to manage toplevel icons">
+      This interface allows clients to create toplevel window icons and set
+      them on toplevel windows to be displayed to the user.
+    </description>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the toplevel icon manager">
+        Destroy the toplevel icon manager.
+        This does not destroy objects created with the manager.
+      </description>
+    </request>
+
+    <request name="create_icon">
+      <description summary="create a new icon instance">
+        Creates a new icon object. This icon can then be attached to a
+        xdg_toplevel via the 'set_icon' request.
+      </description>
+      <arg name="id" type="new_id" interface="xdg_toplevel_icon_v1"/>
+    </request>
+
+    <request name="set_icon">
+      <description summary="set an icon on a toplevel window">
+        This request assigns the icon 'icon' to 'toplevel', or clears the
+        toplevel icon if 'icon' was null.
+        This state is double-buffered and is applied on the next
+        wl_surface.commit of the toplevel.
+
+        After making this call, the xdg_toplevel_icon_v1 provided as 'icon'
+        can be destroyed by the client without 'toplevel' losing its icon.
+        The xdg_toplevel_icon_v1 is immutable from this point, and any
+        future attempts to change it must raise the
+        'xdg_toplevel_icon_v1.immutable' protocol error.
+
+        The compositor must set the toplevel icon from either the pixel data
+        the icon provides, or by loading a stock icon using the icon name.
+        See the description of 'xdg_toplevel_icon_v1' for details.
+
+        If 'icon' is set to null, the icon of the respective toplevel is reset
+        to its default icon (usually the icon of the application, derived from
+        its desktop-entry file, or a placeholder icon).
+        If this request is passed an icon with no pixel buffers or icon name
+        assigned, the icon must be reset just like if 'icon' was null.
+      </description>
+      <arg name="toplevel" type="object" interface="xdg_toplevel" summary="the toplevel to act on"/>
+      <arg name="icon" type="object" interface="xdg_toplevel_icon_v1" allow-null="true"/>
+    </request>
+
+    <event name="icon_size">
+      <description summary="describes a supported &amp; preferred icon size">
+        This event indicates an icon size the compositor prefers to be
+        available if the client has scalable icons and can render to any size.
+
+        When the 'xdg_toplevel_icon_manager_v1' object is created, the
+        compositor may send one or more 'icon_size' events to describe the list
+        of preferred icon sizes. If the compositor has no size preference, it
+        may not send any 'icon_size' event, and it is up to the client to
+        decide a suitable icon size.
+
+        A sequence of 'icon_size' events must be finished with a 'done' event.
+        If the compositor has no size preferences, it must still send the
+        'done' event, without any preceding 'icon_size' events.
+      </description>
+      <arg name="size" type="int"
+	   summary="the edge size of the square icon in surface-local coordinates, e.g. 64"/>
+    </event>
+
+    <event name="done">
+      <description summary="all information has been sent">
+        This event is sent after all 'icon_size' events have been sent.
+      </description>
+    </event>
+  </interface>
+
+  <interface name="xdg_toplevel_icon_v1" version="1">
+    <description summary="a toplevel window icon">
+      This interface defines a toplevel icon.
+      An icon can have a name, and multiple buffers.
+      In order to be applied, the icon must have either a name, or at least
+      one buffer assigned. Applying an empty icon (with no buffer or name) to
+      a toplevel should reset its icon to the default icon.
+
+      It is up to compositor policy whether to prefer using a buffer or loading
+      an icon via its name. See 'set_name' and 'add_buffer' for details.
+    </description>
+
+    <enum name="error">
+      <entry name="invalid_buffer"
+             summary="the provided buffer does not satisfy requirements"
+	     value="1"/>
+      <entry name="immutable"
+             summary="the icon has already been assigned to a toplevel and must not be changed"
+	     value="2"/>
+      <entry name="no_buffer"
+             summary="the provided buffer has been destroyed before the toplevel icon"
+             value="3"/>
+    </enum>
+
+    <request name="destroy" type="destructor">
+      <description summary="destroy the icon object">
+        Destroys the 'xdg_toplevel_icon_v1' object.
+        The icon must still remain set on every toplevel it was assigned to,
+        until the toplevel icon is reset explicitly.
+      </description>
+    </request>
+
+    <request name="set_name">
+      <description summary="set an icon name">
+        This request assigns an icon name to this icon.
+        Any previously set name is overridden.
+
+        The compositor must resolve 'icon_name' according to the lookup rules
+        described in the XDG icon theme specification[1] using the
+        environment's current icon theme.
+
+        If the compositor does not support icon names or cannot resolve
+        'icon_name' according to the XDG icon theme specification it must
+        fall back to using pixel buffer data instead.
+
+        If this request is made after the icon has been assigned to a toplevel
+        via 'set_icon', a 'immutable' error must be raised.
+
+        [1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
+      </description>
+      <arg name="icon_name" type="string"/>
+    </request>
+
+    <request name="add_buffer">
+      <description summary="add icon data from a pixel buffer">
+        This request adds pixel data supplied as wl_buffer to the icon.
+
+        The client should add pixel data for all icon sizes and scales that
+        it can provide, or which are explicitly requested by the compositor
+        via 'icon_size' events on xdg_toplevel_icon_manager_v1.
+
+        The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm
+        and must be a square (width and height being equal).
+        If any of these buffer requirements are not fulfilled, a 'invalid_buffer'
+        error must be raised.
+
+        If this icon instance already has a buffer of the same size and scale
+        from a previous 'add_buffer' request, data from the last request
+        overrides the preexisting pixel data.
+
+        The wl_buffer must be kept alive for as long as the xdg_toplevel_icon
+        it is associated with is not destroyed, otherwise a 'no_buffer' error
+        is raised. The buffer contents must not be modified after it was
+        assigned to the icon. As a result, the region of the wl_shm_pool's
+        backing storage used for the wl_buffer must not be modified after this
+        request is sent. The wl_buffer.release event is unused.
+
+        If this request is made after the icon has been assigned to a toplevel
+        via 'set_icon', a 'immutable' error must be raised.
+      </description>
+      <arg name="buffer" type="object" interface="wl_buffer"/>
+      <arg name="scale" type="int"
+	   summary="the scaling factor of the icon, e.g. 1"/>
+    </request>
+  </interface>
+</protocol>