diff --git a/src/video/wayland/SDL_waylandvideo.c b/src/video/wayland/SDL_waylandvideo.c index 7cfb5a25e48cdc..8ad870d72f8a2a 100644 --- a/src/video/wayland/SDL_waylandvideo.c +++ b/src/video/wayland/SDL_waylandvideo.c @@ -60,6 +60,7 @@ #include "xdg-foreign-unstable-v2-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" +#include "xdg-toplevel-icon-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -485,6 +486,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void) device->SetWindowMinimumSize = Wayland_SetWindowMinimumSize; device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize; device->SetWindowModalFor = Wayland_SetWindowModalFor; + device->SetWindowIcon = Wayland_SetWindowIcon; device->SetWindowTitle = Wayland_SetWindowTitle; device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels; device->DestroyWindow = Wayland_DestroyWindow; @@ -1091,6 +1093,8 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint } else if (SDL_strcmp(interface, "kde_output_order_v1") == 0) { d->kde_output_order = wl_registry_bind(d->registry, id, &kde_output_order_v1_interface, 1); kde_output_order_v1_add_listener(d->kde_output_order, &kde_output_order_listener, d); + } else if (SDL_strcmp(interface, "xdg_toplevel_icon_v1") == 0) { + d->xdg_toplevel_icon = wl_registry_bind(d->registry, id, &xdg_toplevel_icon_v1_interface, 1); } } diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index c6ee45788f7f6d..5efc8f1948bb8b 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -81,6 +81,7 @@ struct SDL_VideoData struct zwp_input_timestamps_manager_v1 *input_timestamps_manager; struct zxdg_exporter_v2 *zxdg_exporter_v2; struct kde_output_order_v1 *kde_output_order; + struct xdg_toplevel_icon_v1 *xdg_toplevel_icon; struct xkb_context *xkb_context; struct SDL_WaylandInput *input; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 28d7b2cbbc5b46..0b87cc4e3865ce 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -30,6 +30,8 @@ #include "SDL_waylandevents_c.h" #include "SDL_waylandwindow.h" #include "SDL_waylandvideo.h" +#include "SDL_waylandshmbuffer.h" + #include "../../SDL_hints_c.h" #include "xdg-shell-client-protocol.h" @@ -39,6 +41,7 @@ #include "viewporter-client-protocol.h" #include "fractional-scale-v1-client-protocol.h" #include "xdg-foreign-unstable-v2-client-protocol.h" +#include "xdg-toplevel-icon-v1-client-protocol.h" #ifdef HAVE_LIBDECOR_H #include @@ -1654,6 +1657,11 @@ void Wayland_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) /* Restore state that was set prior to this call */ Wayland_SetWindowTitle(_this, window); + if (data->pending_icon) { + Wayland_SetWindowIcon(_this, window, data->pending_icon); + SDL_DestroySurface(data->pending_icon); + data->pending_icon = NULL; + } /* We have to wait until the surface gets a "configure" event, or use of * this surface will fail. This is a new rule for xdg_shell. @@ -2476,6 +2484,44 @@ void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, i *h = data->current.drawable_height; } +int Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) +{ + SDL_WindowData *wind = window->driverdata; + SDL_VideoData *viddata = _this->driverdata; + + if (!viddata->xdg_toplevel_icon) { + return SDL_SetError("Unable to set the window's icon. Missing compositor support"); + } + + if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && wind->shell_surface.xdg.roleobj.toplevel) { // or libdecor path + if (icon) { + struct Wayland_SHMBuffer shmBuffer; + if (Wayland_AllocSHMBuffer(icon->w, icon->h, &shmBuffer) != 0) { + return SDL_SetError("Unable to set the window's icon. Failed to allocate icon memory"); + } + + SDL_PremultiplyAlpha(icon->w, icon->h, + icon->format->format, icon->pixels, icon->pitch, + SDL_PIXELFORMAT_ARGB8888, shmBuffer.shm_data, icon->w * 4); + + xdg_toplevel_icon_v1_set_icon_buffer(viddata->xdg_toplevel_icon, wind->shell_surface.xdg.roleobj.toplevel, shmBuffer.wl_buffer); + Wayland_ReleaseSHMBuffer(&shmBuffer); + } else { + xdg_toplevel_icon_v1_set_icon_buffer(viddata->xdg_toplevel_icon, wind->shell_surface.xdg.roleobj.toplevel, NULL); + } + } else { // top level is not yet shown, cache the icon for sending later + if (wind->pending_icon) { + SDL_DestroySurface(wind->pending_icon); + wind->pending_icon = NULL; + } + if (icon) { + wind->pending_icon = SDL_DuplicateSurface(icon); + } + } + + return 0; +} + void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) { SDL_WindowData *wind = window->driverdata; diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 23f88066889188..b2af33224a5f7c 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -175,6 +175,9 @@ struct SDL_WindowData SDL_HitTestResult hit_test_result; + /** Icon set before a window is created*/ + SDL_Surface* pending_icon; + struct wl_list external_window_list_link; }; @@ -199,6 +202,7 @@ extern void Wayland_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *win extern void Wayland_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h); extern int Wayland_SetWindowModalFor(SDL_VideoDevice *_this, SDL_Window *modal_window, SDL_Window *parent_window); +extern int Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); extern void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); extern void Wayland_ShowWindowSystemMenu(SDL_Window *window, int x, int y); extern void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/wayland-protocols/xdg-toplevel-icon-v1.xml b/wayland-protocols/xdg-toplevel-icon-v1.xml new file mode 100644 index 00000000000000..d02493d3f361c9 --- /dev/null +++ b/wayland-protocols/xdg-toplevel-icon-v1.xml @@ -0,0 +1,140 @@ + + + + + Copyright © 2023-2024 Matthias Klumpp + + 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 an 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 provides a way for clients to set a dedicated icon for + their toplevels to represent them in overviews. + + Icons are supposed to be static, and while it is allowed to change an + icon for an already existing window, icons should not be used to convey + status information or try to be animated. + Compositors may rate-limit toplevel icon changes. + + An icon can either be set from an XDG stock icon via 'set_icon_name' or + from pixel data via 'set_icon_buffer'. + + In case both pixel data and an icon name are set by the client, the named + icon must always be preferred as long as it is valid and found in the + current theme. + + The compositor may alter the icon as it sees fit (for example, scale it, + desaturate it or tint it) in order to integrate it with the environment. + + + + + + + + + This request assigns an icon to 'toplevel' using its XDG icon-theme + name, or clears the toplevel icon. + This state is double-buffered and is applied on the next + wl_surface.commit of the toplevel. + + The request must specify an icon name for a stock icon in the icon + theme. + If 'icon_name' 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). + + 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 icon name can not be resolved to a valid icon, the compositor + must emit an 'icon_failed' event. If an icon was successfully set, + an 'icon_assigned' event must be emitted. + + If the icon name is null and the icon is reset to its default, + an 'icon_assigned' event must always be emitted in response. + + This request must be honored if sent as part of the xdg_toplevel's + configure sequence. The client can send it at a later time to request + an update of the icon. + + If a compositor does not support setting an icon by its name, it must + reply with 'icon_failed' to all 'set_icon_name' requests. + + [1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + + + + + + + + + This request assigns a window icon to 'toplevel' using pixel data + supplied as wl_buffer. + This state is double-buffered and is applied on the next + wl_surface.commit of the toplevel. + + This request provides the compositor with pixel data for the window + icon of 'toplevel' for the scaling factor 'scale'. + + The client should submit pixel data for all icon sizes it can provide. + + The wl_buffer supplying pixel data as 'icon' must be backed by wl_shm + and must be a square (width and height being equal). + If any of the buffer requirements are not fulfilled, a 'wrong_format' + error must be raised. + + The client may invoke this request multiple times to provide an icon in + multiple sizes. + If the compositor alreaey has a buffer of the same size and scale from + a previous 'set_icon_buffer' request, data from the latest request + overrides the preexisting pixel data. + To reset to icon to the compositor's default, 'set_icon_name' with a + 'icon_name' of null should be called. + + + + + + + + +