Skip to content

Commit a86f8de

Browse files
committed
wayland: Add support for setting window icons via the xdg-toplevel-icon-v1 protocol
1 parent ea77d1d commit a86f8de

File tree

5 files changed

+280
-0
lines changed

5 files changed

+280
-0
lines changed

src/video/wayland/SDL_waylandvideo.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
#include "primary-selection-unstable-v1-client-protocol.h"
6060
#include "fractional-scale-v1-client-protocol.h"
6161
#include "cursor-shape-v1-client-protocol.h"
62+
#include "xdg-toplevel-icon-v1-client-protocol.h"
6263

6364
#ifdef HAVE_LIBDECOR_H
6465
#include <libdecor.h>
@@ -276,6 +277,7 @@ static SDL_VideoDevice *Wayland_CreateDevice(void)
276277
device->SetWindowMaximumSize = Wayland_SetWindowMaximumSize;
277278
device->SetWindowModalFor = Wayland_SetWindowModalFor;
278279
device->SetWindowTitle = Wayland_SetWindowTitle;
280+
device->SetWindowIcon = Wayland_SetWindowIcon;
279281
device->GetWindowSizeInPixels = Wayland_GetWindowSizeInPixels;
280282
device->DestroyWindow = Wayland_DestroyWindow;
281283
device->SetWindowHitTest = Wayland_SetWindowHitTest;
@@ -880,6 +882,8 @@ static void display_handle_global(void *data, struct wl_registry *registry, uint
880882
if (d->input) {
881883
Wayland_CreateCursorShapeDevice(d->input);
882884
}
885+
} else if (SDL_strcmp(interface, "xdg_toplevel_icon_manager_v1") == 0) {
886+
d->xdg_toplevel_icon_manager_v1 = wl_registry_bind(d->registry, id, &xdg_toplevel_icon_manager_v1_interface, 1);
883887
#ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
884888
} else if (SDL_strcmp(interface, "qt_touch_extension") == 0) {
885889
Wayland_touch_create(d, id);
@@ -1128,6 +1132,11 @@ static void Wayland_VideoCleanup(_THIS)
11281132
data->cursor_shape_manager = NULL;
11291133
}
11301134

1135+
if (data->xdg_toplevel_icon_manager_v1) {
1136+
xdg_toplevel_icon_manager_v1_destroy(data->xdg_toplevel_icon_manager_v1);
1137+
data->xdg_toplevel_icon_manager_v1 = NULL;
1138+
}
1139+
11311140
if (data->compositor) {
11321141
wl_compositor_destroy(data->compositor);
11331142
data->compositor = NULL;

src/video/wayland/SDL_waylandvideo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ typedef struct
7878
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager;
7979
struct xdg_activation_v1 *activation_manager;
8080
struct zwp_text_input_manager_v3 *text_input_manager;
81+
struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1;
8182
struct zxdg_output_manager_v1 *xdg_output_manager;
8283
struct wp_viewporter *viewporter;
8384
struct wp_fractional_scale_manager_v1 *fractional_scale_manager;

src/video/wayland/SDL_waylandwindow.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "SDL_waylandwindow.h"
3232
#include "SDL_waylandvideo.h"
3333
#include "SDL_waylandtouch.h"
34+
#include "SDL_waylandshmbuffer.h"
3435
#include "SDL_hints.h"
3536
#include "../../SDL_hints_c.h"
3637
#include "SDL_events.h"
@@ -41,6 +42,7 @@
4142
#include "xdg-activation-v1-client-protocol.h"
4243
#include "viewporter-client-protocol.h"
4344
#include "fractional-scale-v1-client-protocol.h"
45+
#include "xdg-toplevel-icon-v1-client-protocol.h"
4446

4547
#ifdef HAVE_LIBDECOR_H
4648
#include <libdecor.h>
@@ -1273,6 +1275,12 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
12731275
} else {
12741276
libdecor_frame_set_app_id(data->shell_surface.libdecor.frame, c->classname);
12751277
libdecor_frame_map(data->shell_surface.libdecor.frame);
1278+
1279+
if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) {
1280+
xdg_toplevel_icon_manager_v1_set_icon(c->xdg_toplevel_icon_manager_v1,
1281+
libdecor_frame_get_xdg_toplevel(data->shell_surface.libdecor.frame),
1282+
data->xdg_toplevel_icon_v1);
1283+
}
12761284
}
12771285
} else
12781286
#endif
@@ -1317,6 +1325,12 @@ void Wayland_ShowWindow(_THIS, SDL_Window *window)
13171325
xdg_toplevel_set_app_id(data->shell_surface.xdg.roleobj.toplevel, c->classname);
13181326
xdg_toplevel_add_listener(data->shell_surface.xdg.roleobj.toplevel, &toplevel_listener_xdg, data);
13191327

1328+
if (c->xdg_toplevel_icon_manager_v1 && data->xdg_toplevel_icon_v1) {
1329+
xdg_toplevel_icon_manager_v1_set_icon(c->xdg_toplevel_icon_manager_v1,
1330+
data->shell_surface.xdg.roleobj.toplevel,
1331+
data->xdg_toplevel_icon_v1);
1332+
}
1333+
13201334
SetMinMaxDimensions(window, SDL_FALSE);
13211335
}
13221336
}
@@ -2172,6 +2186,46 @@ void Wayland_SetWindowTitle(_THIS, SDL_Window *window)
21722186
WAYLAND_wl_display_flush(viddata->display);
21732187
}
21742188

2189+
void Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
2190+
{
2191+
SDL_WindowData *wind = window->driverdata;
2192+
SDL_VideoData *viddata = _this->driverdata;
2193+
struct xdg_toplevel *toplevel = NULL;
2194+
2195+
if (!viddata->xdg_toplevel_icon_manager_v1) {
2196+
SDL_SetError("wayland: cannot set icon; xdg_toplevel_icon_v1 protocol not supported");
2197+
return;
2198+
}
2199+
if (icon->w != icon->h) {
2200+
SDL_SetError("wayland: icon width and height must be equal, got %ix%i", icon->w, icon->h);
2201+
return;
2202+
}
2203+
if (wind->xdg_toplevel_icon_v1) {
2204+
xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1);
2205+
wind->xdg_toplevel_icon_v1 = NULL;
2206+
}
2207+
2208+
Wayland_ReleaseSHMBuffer(&wind->icon);
2209+
if (Wayland_AllocSHMBuffer(icon->w, icon->h, &wind->icon) != 0) {
2210+
SDL_SetError("wayland: failed to allocate SHM buffer for the icon");
2211+
return;
2212+
}
2213+
SDL_PremultiplyAlpha(icon->w, icon->h, icon->format->format, icon->pixels, icon->pitch, SDL_PIXELFORMAT_ARGB8888, wind->icon.shm_data, icon->w * 4);
2214+
wind->xdg_toplevel_icon_v1 = xdg_toplevel_icon_manager_v1_create_icon(viddata->xdg_toplevel_icon_manager_v1);
2215+
xdg_toplevel_icon_v1_add_buffer(wind->xdg_toplevel_icon_v1, wind->icon.wl_buffer, 1);
2216+
#ifdef HAVE_LIBDECOR_H
2217+
if (wind->shell_surface_type == WAYLAND_SURFACE_LIBDECOR && wind->shell_surface.libdecor.frame) {
2218+
toplevel = libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame);
2219+
} else
2220+
#endif
2221+
if (wind->shell_surface_type == WAYLAND_SURFACE_XDG_TOPLEVEL && wind->shell_surface.xdg.roleobj.toplevel) {
2222+
toplevel = wind->shell_surface.xdg.roleobj.toplevel;
2223+
}
2224+
if (toplevel) {
2225+
xdg_toplevel_icon_manager_v1_set_icon(viddata->xdg_toplevel_icon_manager_v1, toplevel, wind->xdg_toplevel_icon_v1);
2226+
}
2227+
}
2228+
21752229
void Wayland_SuspendScreenSaver(_THIS)
21762230
{
21772231
SDL_VideoData *data = (SDL_VideoData *)_this->driverdata;
@@ -2240,6 +2294,12 @@ void Wayland_DestroyWindow(_THIS, SDL_Window *window)
22402294
wp_fractional_scale_v1_destroy(wind->fractional_scale);
22412295
}
22422296

2297+
if (wind->xdg_toplevel_icon_v1) {
2298+
xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1);
2299+
}
2300+
2301+
Wayland_ReleaseSHMBuffer(&wind->icon);
2302+
22432303
SDL_free(wind->outputs);
22442304

22452305
if (wind->gles_swap_frame_callback) {

src/video/wayland/SDL_waylandwindow.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "../../events/SDL_touch_c.h"
3030

3131
#include "SDL_waylandvideo.h"
32+
#include "SDL_waylandshmbuffer.h"
3233

3334
struct SDL_WaylandInput;
3435

@@ -89,6 +90,9 @@ typedef struct
8990
struct xdg_activation_token_v1 *activation_token;
9091
struct wp_viewport *draw_viewport;
9192
struct wp_fractional_scale_v1 *fractional_scale;
93+
struct xdg_toplevel_icon_v1 *xdg_toplevel_icon_v1;
94+
95+
struct Wayland_SHMBuffer icon;
9296

9397
/* floating dimensions for restoring from maximized and fullscreen */
9498
int floating_width, floating_height;
@@ -142,6 +146,7 @@ extern int Wayland_SetWindowModalFor(_THIS, SDL_Window *modal_window, SDL_Window
142146
extern void Wayland_SetWindowTitle(_THIS, SDL_Window *window);
143147
extern void Wayland_DestroyWindow(_THIS, SDL_Window *window);
144148
extern void Wayland_SuspendScreenSaver(_THIS);
149+
extern void Wayland_SetWindowIcon(_THIS, SDL_Window *window, SDL_Surface *icon);
145150

146151
extern SDL_bool
147152
Wayland_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info);
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<protocol name="xdg_toplevel_icon_v1">
3+
4+
<copyright>
5+
Copyright © 2023-2024 Matthias Klumpp
6+
Copyright © 2024 David Edmundson
7+
8+
Permission is hereby granted, free of charge, to any person obtaining a
9+
copy of this software and associated documentation files (the "Software"),
10+
to deal in the Software without restriction, including without limitation
11+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
12+
and/or sell copies of the Software, and to permit persons to whom the
13+
Software is furnished to do so, subject to the following conditions:
14+
15+
The above copyright notice and this permission notice (including the next
16+
paragraph) shall be included in all copies or substantial portions of the
17+
Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25+
DEALINGS IN THE SOFTWARE.
26+
</copyright>
27+
28+
<description summary="protocol to assign icons to toplevels">
29+
This protocol allows clients to set icons for their toplevel surfaces
30+
either via the XDG icon stock (using an icon name), or from pixel data.
31+
32+
A toplevel icon represents the individual toplevel (unlike the application
33+
or launcher icon, which represents the application as a whole), and may be
34+
shown in window switchers, window overviews and taskbars that list
35+
individual windows.
36+
37+
This document adheres to RFC 2119 when using words like "must",
38+
"should", "may", etc.
39+
40+
Warning! The protocol described in this file is currently in the testing
41+
phase. Backward compatible changes may be added together with the
42+
corresponding interface version bump. Backward incompatible changes can
43+
only be done by creating a new major version of the extension.
44+
</description>
45+
46+
<interface name="xdg_toplevel_icon_manager_v1" version="1">
47+
<description summary="interface to manage toplevel icons">
48+
This interface allows clients to create toplevel window icons and set
49+
them on toplevel windows to be displayed to the user.
50+
</description>
51+
52+
<request name="destroy" type="destructor">
53+
<description summary="destroy the toplevel icon manager">
54+
Destroy the toplevel icon manager.
55+
This does not destroy objects created with the manager.
56+
</description>
57+
</request>
58+
59+
<request name="create_icon">
60+
<description summary="create a new icon instance">
61+
Creates a new icon object. This icon can then be attached to a
62+
xdg_toplevel via the 'set_icon' request.
63+
</description>
64+
<arg name="id" type="new_id" interface="xdg_toplevel_icon_v1"/>
65+
</request>
66+
67+
<request name="set_icon">
68+
<description summary="set an icon on a toplevel window">
69+
This request assigns the icon 'icon' to 'toplevel', or clears the
70+
toplevel icon if 'icon' was null.
71+
This state is double-buffered and is applied on the next
72+
wl_surface.commit of the toplevel.
73+
74+
After making this call, the xdg_toplevel_icon_v1 provided as 'icon'
75+
can be destroyed by the client without 'toplevel' losing its icon.
76+
The xdg_toplevel_icon_v1 is immutable from this point, and any
77+
future attempts to change it must raise the
78+
'xdg_toplevel_icon_v1.immutable' protocol error.
79+
80+
The compositor must set the toplevel icon from either the pixel data
81+
the icon provides, or by loading a stock icon using the icon name.
82+
See the description of 'xdg_toplevel_icon_v1' for details.
83+
84+
If 'icon' is set to null, the icon of the respective toplevel is reset
85+
to its default icon (usually the icon of the application, derived from
86+
its desktop-entry file, or a placeholder icon).
87+
If this request is passed an icon with no pixel buffers or icon name
88+
assigned, the icon must be reset just like if 'icon' was null.
89+
</description>
90+
<arg name="toplevel" type="object" interface="xdg_toplevel" summary="the toplevel to act on"/>
91+
<arg name="icon" type="object" interface="xdg_toplevel_icon_v1" allow-null="true"/>
92+
</request>
93+
94+
<event name="icon_size">
95+
<description summary="describes a supported &amp; preferred icon size">
96+
This event indicates an icon size the compositor prefers to be
97+
available if the client has scalable icons and can render to any size.
98+
99+
When the 'xdg_toplevel_icon_manager_v1' object is created, the
100+
compositor may send one or more 'icon_size' events to describe the list
101+
of preferred icon sizes. If the compositor has no size preference, it
102+
may not send any 'icon_size' event, and it is up to the client to
103+
decide a suitable icon size.
104+
105+
A sequence of 'icon_size' events must be finished with a 'done' event.
106+
If the compositor has no size preferences, it must still send the
107+
'done' event, without any preceding 'icon_size' events.
108+
</description>
109+
<arg name="size" type="int"
110+
summary="the edge size of the square icon in surface-local coordinates, e.g. 64"/>
111+
</event>
112+
113+
<event name="done">
114+
<description summary="all information has been sent">
115+
This event is sent after all 'icon_size' events have been sent.
116+
</description>
117+
</event>
118+
</interface>
119+
120+
<interface name="xdg_toplevel_icon_v1" version="1">
121+
<description summary="a toplevel window icon">
122+
This interface defines a toplevel icon.
123+
An icon can have a name, and multiple buffers.
124+
In order to be applied, the icon must have either a name, or at least
125+
one buffer assigned. Applying an empty icon (with no buffer or name) to
126+
a toplevel should reset its icon to the default icon.
127+
128+
It is up to compositor policy whether to prefer using a buffer or loading
129+
an icon via its name. See 'set_name' and 'add_buffer' for details.
130+
</description>
131+
132+
<enum name="error">
133+
<entry name="invalid_buffer"
134+
summary="the provided buffer does not satisfy requirements"
135+
value="1"/>
136+
<entry name="immutable"
137+
summary="the icon has already been assigned to a toplevel and must not be changed"
138+
value="2"/>
139+
<entry name="no_buffer"
140+
summary="the provided buffer has been destroyed before the toplevel icon"
141+
value="3"/>
142+
</enum>
143+
144+
<request name="destroy" type="destructor">
145+
<description summary="destroy the icon object">
146+
Destroys the 'xdg_toplevel_icon_v1' object.
147+
The icon must still remain set on every toplevel it was assigned to,
148+
until the toplevel icon is reset explicitly.
149+
</description>
150+
</request>
151+
152+
<request name="set_name">
153+
<description summary="set an icon name">
154+
This request assigns an icon name to this icon.
155+
Any previously set name is overridden.
156+
157+
The compositor must resolve 'icon_name' according to the lookup rules
158+
described in the XDG icon theme specification[1] using the
159+
environment's current icon theme.
160+
161+
If the compositor does not support icon names or cannot resolve
162+
'icon_name' according to the XDG icon theme specification it must
163+
fall back to using pixel buffer data instead.
164+
165+
If this request is made after the icon has been assigned to a toplevel
166+
via 'set_icon', a 'immutable' error must be raised.
167+
168+
[1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
169+
</description>
170+
<arg name="icon_name" type="string"/>
171+
</request>
172+
173+
<request name="add_buffer">
174+
<description summary="add icon data from a pixel buffer">
175+
This request adds pixel data supplied as wl_buffer to the icon.
176+
177+
The client should add pixel data for all icon sizes and scales that
178+
it can provide, or which are explicitly requested by the compositor
179+
via 'icon_size' events on xdg_toplevel_icon_manager_v1.
180+
181+
The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm
182+
and must be a square (width and height being equal).
183+
If any of these buffer requirements are not fulfilled, a 'invalid_buffer'
184+
error must be raised.
185+
186+
If this icon instance already has a buffer of the same size and scale
187+
from a previous 'add_buffer' request, data from the last request
188+
overrides the preexisting pixel data.
189+
190+
The wl_buffer must be kept alive for as long as the xdg_toplevel_icon
191+
it is associated with is not destroyed, otherwise a 'no_buffer' error
192+
is raised. The buffer contents must not be modified after it was
193+
assigned to the icon. As a result, the region of the wl_shm_pool's
194+
backing storage used for the wl_buffer must not be modified after this
195+
request is sent. The wl_buffer.release event is unused.
196+
197+
If this request is made after the icon has been assigned to a toplevel
198+
via 'set_icon', a 'immutable' error must be raised.
199+
</description>
200+
<arg name="buffer" type="object" interface="wl_buffer"/>
201+
<arg name="scale" type="int"
202+
summary="the scaling factor of the icon, e.g. 1"/>
203+
</request>
204+
</interface>
205+
</protocol>

0 commit comments

Comments
 (0)