diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 1846eb9c5b..0aa2695625 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/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 @@ -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; diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index f4ec5922ad..ebdb6fc131 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/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; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index a09181bf84..98644e6bb4 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/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 @@ -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) { diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 1087f3bc8d..b09d991b27 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/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); diff --git a/wayland-protocols/xdg-toplevel-icon-v1.xml b/wayland-protocols/xdg-toplevel-icon-v1.xml new file mode 100644 index 0000000000..fc409fef7c --- /dev/null +++ b/wayland-protocols/xdg-toplevel-icon-v1.xml @@ -0,0 +1,205 @@ + + + + + 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. + + + + 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. + + + + + This interface allows clients to create toplevel window icons and set + them on toplevel windows to be displayed to the user. + + + + + Destroy the toplevel icon manager. + This does not destroy objects created with the manager. + + + + + + Creates a new icon object. This icon can then be attached to a + xdg_toplevel via the 'set_icon' request. + + + + + + + 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. + + + + + + + + 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. + + + + + + + This event is sent after all 'icon_size' events have been sent. + + + + + + + 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. + + + + + + + + + + + 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. + + + + + + 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 + + + + + + + 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. + + + + + +