diff --git a/platform/linuxbsd/wayland/SCsub b/platform/linuxbsd/wayland/SCsub index bf05cbc0405..78e9ac4dde2 100644 --- a/platform/linuxbsd/wayland/SCsub +++ b/platform/linuxbsd/wayland/SCsub @@ -64,6 +64,9 @@ objects = [ generate_from_xml( "xdg_system_bell", "#thirdparty/wayland-protocols/staging/xdg-system-bell/xdg-system-bell-v1.xml" ), + generate_from_xml( + "xdg_toplevel_icon", "#thirdparty/wayland-protocols/staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml" + ), # Unstable protocols generate_from_xml( "idle_inhibit", "#thirdparty/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml" diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 3739ba0ff15..358444ebb14 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -177,6 +177,7 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const { case FEATURE_CURSOR_SHAPE: case FEATURE_CUSTOM_CURSOR_SHAPE: case FEATURE_WINDOW_TRANSPARENCY: + case FEATURE_ICON: case FEATURE_HIDPI: case FEATURE_SWAP_BUFFERS: case FEATURE_KEEP_SCREEN_ON: @@ -1810,6 +1811,11 @@ void DisplayServerWayland::swap_buffers() { #endif } +void DisplayServerWayland::set_icon(const Ref &p_icon) { + MutexLock mutex_lock(wayland_thread.mutex); + wayland_thread.set_icon(p_icon); +} + void DisplayServerWayland::set_context(Context p_context) { MutexLock mutex_lock(wayland_thread.mutex); diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index a31ae20f079..95dd66a7878 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -342,6 +342,8 @@ public: virtual void release_rendering_thread() override; virtual void swap_buffers() override; + virtual void set_icon(const Ref &p_icon) override; + virtual void set_context(Context p_context) override; virtual bool is_window_transparency_available() const override; diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 5f0b42ffec6..f3cf7aa2dbe 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -30,6 +30,8 @@ #include "wayland_thread.h" +#include "core/config/engine.h" + #ifdef WAYLAND_ENABLED #ifdef __FreeBSD__ @@ -574,6 +576,12 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re return; } + if (strcmp(interface, xdg_toplevel_icon_manager_v1_interface.name) == 0) { + registry->xdg_toplevel_icon_manager = (struct xdg_toplevel_icon_manager_v1 *)wl_registry_bind(wl_registry, name, &xdg_toplevel_icon_manager_v1_interface, 1); + registry->xdg_toplevel_icon_manager_name = name; + return; + } + if (strcmp(interface, xdg_activation_v1_interface.name) == 0) { registry->xdg_activation = (struct xdg_activation_v1 *)wl_registry_bind(wl_registry, name, &xdg_activation_v1_interface, 1); registry->xdg_activation_name = name; @@ -818,6 +826,25 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry return; } + if (name == registry->xdg_toplevel_icon_manager_name) { + if (registry->xdg_toplevel_icon_manager) { + xdg_toplevel_icon_manager_v1_destroy(registry->xdg_toplevel_icon_manager); + registry->xdg_toplevel_icon_manager = nullptr; + } + + if (registry->wayland_thread->xdg_icon) { + xdg_toplevel_icon_v1_destroy(registry->wayland_thread->xdg_icon); + } + + if (registry->wayland_thread->icon_buffer) { + wl_buffer_destroy(registry->wayland_thread->icon_buffer); + } + + registry->xdg_toplevel_icon_manager_name = 0; + + return; + } + if (name == registry->xdg_activation_name) { if (registry->xdg_activation) { xdg_activation_v1_destroy(registry->xdg_activation); @@ -3509,6 +3536,13 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_wid ws.libdecor_frame = libdecor_decorate(libdecor_context, ws.wl_surface, (struct libdecor_frame_interface *)&libdecor_frame_interface, &ws); libdecor_frame_map(ws.libdecor_frame); + if (registry.xdg_toplevel_icon_manager) { + xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(ws.libdecor_frame); + if (toplevel != nullptr) { + xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, toplevel, xdg_icon); + } + } + decorated = true; } #endif @@ -3529,6 +3563,10 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_wid decorated = true; } + + if (registry.xdg_toplevel_icon_manager) { + xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, ws.xdg_toplevel, xdg_icon); + } } ws.frame_callback = wl_surface_frame(ws.wl_surface); @@ -4081,6 +4119,81 @@ void WaylandThread::window_set_app_id(DisplayServer::WindowID p_window_id, const } } +void WaylandThread::set_icon(const Ref &p_icon) { + ERR_FAIL_COND(p_icon.is_null()); + + Size2i icon_size = p_icon->get_size(); + ERR_FAIL_COND(icon_size.width != icon_size.height); + + if (!registry.xdg_toplevel_icon_manager) { + return; + } + + if (xdg_icon) { + xdg_toplevel_icon_v1_destroy(xdg_icon); + } + + if (icon_buffer) { + wl_buffer_destroy(icon_buffer); + } + + // NOTE: The stride is the width of the icon in bytes. + uint32_t icon_stride = icon_size.width * 4; + uint32_t data_size = icon_stride * icon_size.height; + + // We need a shared memory object file descriptor in order to create a + // wl_buffer through wl_shm. + int fd = WaylandThread::_allocate_shm_file(data_size); + ERR_FAIL_COND(fd == -1); + + uint32_t *buffer_data = (uint32_t *)mmap(nullptr, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + // Create the Wayland buffer. + struct wl_shm_pool *shm_pool = wl_shm_create_pool(registry.wl_shm, fd, data_size); + icon_buffer = wl_shm_pool_create_buffer(shm_pool, 0, icon_size.width, icon_size.height, icon_stride, WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(shm_pool); + + // Fill the cursor buffer with the image data. + for (uint32_t index = 0; index < (uint32_t)(icon_size.width * icon_size.height); index++) { + int row_index = index / icon_size.width; + int column_index = (index % icon_size.width); + + buffer_data[index] = p_icon->get_pixel(column_index, row_index).to_argb32(); + + // Wayland buffers, unless specified, require associated alpha, so we'll just + // associate the alpha in-place. + uint8_t *pixel_data = (uint8_t *)&buffer_data[index]; + pixel_data[0] = pixel_data[0] * pixel_data[3] / 255; + pixel_data[1] = pixel_data[1] * pixel_data[3] / 255; + pixel_data[2] = pixel_data[2] * pixel_data[3] / 255; + } + + xdg_icon = xdg_toplevel_icon_manager_v1_create_icon(registry.xdg_toplevel_icon_manager); + xdg_toplevel_icon_v1_add_buffer(xdg_icon, icon_buffer, icon_size.width); + + if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) { + // Setting a name allows the godot icon to be overridden by a system theme. + // We only want the project manager and editor to get themed, + // Games will get icons with the protocol and themed icons with .desktop entries. + // NOTE: should be synced with the icon name in misc/dist/linuxbsd/Godot.desktop + xdg_toplevel_icon_v1_set_name(xdg_icon, "godot"); + } + + for (KeyValue &pair : windows) { + WindowState &ws = pair.value; +#ifdef LIBDECOR_ENABLED + if (ws.libdecor_frame) { + xdg_toplevel *toplevel = libdecor_frame_get_xdg_toplevel(ws.libdecor_frame); + ERR_FAIL_NULL(toplevel); + xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, toplevel, xdg_icon); + } +#endif + if (ws.xdg_toplevel) { + xdg_toplevel_icon_manager_v1_set_icon(registry.xdg_toplevel_icon_manager, ws.xdg_toplevel, xdg_icon); + } + } +} + DisplayServer::WindowMode WaylandThread::window_get_mode(DisplayServer::WindowID p_window_id) const { ERR_FAIL_COND_V(!windows.has(p_window_id), DisplayServer::WINDOW_MODE_WINDOWED); const WindowState &ws = windows[p_window_id]; @@ -4333,6 +4446,10 @@ Error WaylandThread::init() { WARN_PRINT("FIFO protocol not found! Frame pacing will be degraded."); } + if (!registry.xdg_toplevel_icon_manager_name) { + WARN_PRINT("xdg-toplevel-icon protocol not found! Cannot set window icon."); + } + // Wait for seat capabilities. wl_display_roundtrip(wl_display); @@ -5000,6 +5117,18 @@ void WaylandThread::destroy() { xdg_system_bell_v1_destroy(registry.xdg_system_bell); } + if (registry.xdg_toplevel_icon_manager) { + xdg_toplevel_icon_manager_v1_destroy(registry.xdg_toplevel_icon_manager); + + if (xdg_icon) { + xdg_toplevel_icon_v1_destroy(xdg_icon); + } + + if (icon_buffer) { + wl_buffer_destroy(icon_buffer); + } + } + if (registry.xdg_decoration_manager) { zxdg_decoration_manager_v1_destroy(registry.xdg_decoration_manager); } diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h index 64e1babcf67..4f9e15f222c 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -70,6 +70,7 @@ #include "wayland/protocol/xdg_foreign_v2.gen.h" #include "wayland/protocol/xdg_shell.gen.h" #include "wayland/protocol/xdg_system_bell.gen.h" +#include "wayland/protocol/xdg_toplevel_icon.gen.h" // NOTE: Deprecated. #include "wayland/protocol/xdg_foreign_v1.gen.h" @@ -197,6 +198,9 @@ public: struct xdg_system_bell_v1 *xdg_system_bell = nullptr; uint32_t xdg_system_bell_name = 0; + struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager = nullptr; + uint32_t xdg_toplevel_icon_manager_name = 0; + struct xdg_activation_v1 *xdg_activation = nullptr; uint32_t xdg_activation_name = 0; @@ -543,6 +547,9 @@ private: List> messages; + xdg_toplevel_icon_v1 *xdg_icon = nullptr; + wl_buffer *icon_buffer = nullptr; + String cursor_theme_name; int unscaled_cursor_size = 24; @@ -1023,6 +1030,8 @@ public: void beep() const; + void set_icon(const Ref &p_icon); + void window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height); void window_create_popup(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id, Rect2i p_rect); void window_destroy(DisplayServer::WindowID p_window_Id); diff --git a/thirdparty/wayland-protocols/staging/xdg-toplevel-icon/README b/thirdparty/wayland-protocols/staging/xdg-toplevel-icon/README new file mode 100644 index 00000000000..76a394f5da9 --- /dev/null +++ b/thirdparty/wayland-protocols/staging/xdg-toplevel-icon/README @@ -0,0 +1,4 @@ +xdg_toplevel_icon protocol + +Maintainers: +Matthias Klumpp (@mak) diff --git a/thirdparty/wayland-protocols/staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml b/thirdparty/wayland-protocols/staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml new file mode 100644 index 00000000000..fc409fef7c6 --- /dev/null +++ b/thirdparty/wayland-protocols/staging/xdg-toplevel-icon/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. + + + + + +