From f77986b863d8141edb5169774926f5d05ea3bda5 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 17 Jul 2026 03:12:06 +1000 Subject: [PATCH] [client] wayland: set the application icon --- client/displayservers/Wayland/CMakeLists.txt | 2 + .../Wayland/desktops/libdecor/libdecor.c | 5 + .../displayservers/Wayland/desktops/xdg/xdg.c | 1 + client/displayservers/Wayland/icon.c | 136 ++++++++++++++++++ .../Wayland/protocol/CMakeLists.txt | 3 + client/displayservers/Wayland/registry.c | 3 + client/displayservers/Wayland/wayland.c | 2 + client/displayservers/Wayland/wayland.h | 10 ++ client/displayservers/X11/x11.c | 2 +- resources/CMakeLists.txt | 6 +- resources/icondata.c | 2 +- resources/icondata.h | 22 +++ 12 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 client/displayservers/Wayland/icon.c create mode 100644 resources/icondata.h diff --git a/client/displayservers/Wayland/CMakeLists.txt b/client/displayservers/Wayland/CMakeLists.txt index 359d62e7..5ffb97b3 100644 --- a/client/displayservers/Wayland/CMakeLists.txt +++ b/client/displayservers/Wayland/CMakeLists.txt @@ -16,6 +16,7 @@ add_library(displayserver_Wayland STATIC colormgmt.c cursor.c gl.c + icon.c idle.c input.c output.c @@ -33,6 +34,7 @@ add_subdirectory(desktops) target_link_libraries(displayserver_Wayland PkgConfig::DISPLAYSERVER_Wayland lg_common + lg_resources wayland_protocol wayland_desktops ) diff --git a/client/displayservers/Wayland/desktops/libdecor/libdecor.c b/client/displayservers/Wayland/desktops/libdecor/libdecor.c index 2525034c..2021bd5e 100644 --- a/client/displayservers/Wayland/desktops/libdecor/libdecor.c +++ b/client/displayservers/Wayland/desktops/libdecor/libdecor.c @@ -142,6 +142,11 @@ static bool libdecor_shellInit( libdecor_frame_set_title(state.libdecorFrame, title); libdecor_frame_map(state.libdecorFrame); + // Get the xdg_toplevel for icon setting + struct xdg_surface * xdgSurface = libdecor_frame_get_xdg_surface(state.libdecorFrame); + if (xdgSurface) + wlWm.xdgToplevel = xdg_surface_get_toplevel(xdgSurface); + if (fullscreen) libdecor_frame_set_fullscreen(state.libdecorFrame, NULL); diff --git a/client/displayservers/Wayland/desktops/xdg/xdg.c b/client/displayservers/Wayland/desktops/xdg/xdg.c index 9851e841..e6eb7dc8 100644 --- a/client/displayservers/Wayland/desktops/xdg/xdg.c +++ b/client/displayservers/Wayland/desktops/xdg/xdg.c @@ -149,6 +149,7 @@ bool xdg_shellInit(struct wl_display * display, struct wl_surface * surface, xdg_toplevel_add_listener(state.toplevel, &xdgToplevelListener, NULL); xdg_toplevel_set_title(state.toplevel, title); xdg_toplevel_set_app_id(state.toplevel, appId); + wlWm.xdgToplevel = state.toplevel; if (fullscreen) xdg_toplevel_set_fullscreen(state.toplevel, NULL); diff --git a/client/displayservers/Wayland/icon.c b/client/displayservers/Wayland/icon.c new file mode 100644 index 00000000..4a104432 --- /dev/null +++ b/client/displayservers/Wayland/icon.c @@ -0,0 +1,136 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define _GNU_SOURCE +#include "wayland.h" + +#include +#include + +#include +#include +#include +#include + +#include "common/debug.h" +#include "resources/icondata.h" + +// icondata is defined as an array of unsigned long, but on 64-bit platforms +// unsigned long is 8 bytes while the actual data is 32-bit ARGB packed in +// unsigned long values (the top 32 bits are zero). The first two values are +// width and height (both 64), followed by pixel rows. +// We need to convert this to a uint32_t buffer for wl_shm (ARGB8888). +// +// Note: sizeof(icondata) / sizeof(icondata[0]) gives the total array length, +// which includes the 2 header values + 64*64 = 4096 pixel values = 4098 total. + +#define ICON_SIZE 64 +// header: width and height, then pixel data +#define ICON_HEADER_WORDS 2 +#define ICON_PIXEL_WORDS (ICON_SIZE * ICON_SIZE) +#define ICON_TOTAL_WORDS (ICON_HEADER_WORDS + ICON_PIXEL_WORDS) + +static uint32_t g_iconPixels[ICON_SIZE * ICON_SIZE]; + +bool waylandIconInit(void) +{ + if (!wlWm.iconManager || !wlWm.xdgToplevel || !wlWm.shm) + return true; // not an error, just no support + + for (size_t i = 0; i < ICON_TOTAL_WORDS; ++i) + { + if (i >= icondataSize / sizeof(icondata[0])) + { + DEBUG_ERROR("Icon data array is smaller than expected"); + return true; + } + uint32_t val = (uint32_t)icondata[i]; + // First two words are width/height, skip them + if (i >= ICON_HEADER_WORDS) + g_iconPixels[i - ICON_HEADER_WORDS] = val; + } + + // Create shared memory buffer for the icon + size_t dataSize = sizeof(g_iconPixels); + int fd = memfd_create("lg-icon", 0); + if (fd < 0) + { + DEBUG_ERROR("Failed to create icon shared memory: %d", errno); + return true; // not fatal + } + + struct wl_buffer * buffer = NULL; + struct xdg_toplevel_icon_v1 * icon = NULL; + + if (ftruncate(fd, (off_t)dataSize) < 0) + { + DEBUG_ERROR("Failed to ftruncate icon shared memory: %d", errno); + goto fail; + } + + void * shm_data = mmap(NULL, dataSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (shm_data == MAP_FAILED) + { + DEBUG_ERROR("Failed to mmap icon shared memory: %d", errno); + goto fail; + } + + memcpy(shm_data, g_iconPixels, dataSize); + munmap(shm_data, dataSize); + + struct wl_shm_pool * pool = wl_shm_create_pool(wlWm.shm, fd, (int32_t)dataSize); + buffer = wl_shm_pool_create_buffer(pool, 0, ICON_SIZE, ICON_SIZE, + ICON_SIZE * 4, WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + if (!buffer) + { + DEBUG_ERROR("Failed to create wl_buffer for icon"); + goto fail; + } + + icon = xdg_toplevel_icon_manager_v1_create_icon(wlWm.iconManager); + if (!icon) + { + DEBUG_ERROR("Failed to create icon object"); + goto fail; + } + + xdg_toplevel_icon_v1_add_buffer(icon, buffer, 1); + xdg_toplevel_icon_manager_v1_set_icon(wlWm.iconManager, + (struct xdg_toplevel *)wlWm.xdgToplevel, icon); + + // After set_icon, the icon object is immutable. We can destroy it; + // the compositor retains its data. + xdg_toplevel_icon_v1_destroy(icon); + wl_buffer_destroy(buffer); + + close(fd); + return true; + +fail: + if (icon) + xdg_toplevel_icon_v1_destroy(icon); + if (buffer) + wl_buffer_destroy(buffer); + close(fd); + + return true; // not fatal +} diff --git a/client/displayservers/Wayland/protocol/CMakeLists.txt b/client/displayservers/Wayland/protocol/CMakeLists.txt index c2756a2a..ada8b7d1 100644 --- a/client/displayservers/Wayland/protocol/CMakeLists.txt +++ b/client/displayservers/Wayland/protocol/CMakeLists.txt @@ -69,6 +69,9 @@ wayland_generate( wayland_generate( "${WAYLAND_PROTOCOLS_BASE}/staging/color-management/color-management-v1.xml" "${CMAKE_BINARY_DIR}/wayland/wayland-color-management-v1-client-protocol") +wayland_generate( + "${WAYLAND_PROTOCOLS_BASE}/staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml" + "${CMAKE_BINARY_DIR}/wayland/wayland-xdg-toplevel-icon-v1-client-protocol") target_link_libraries(wayland_protocol PkgConfig::WAYLAND diff --git a/client/displayservers/Wayland/registry.c b/client/displayservers/Wayland/registry.c index a659fd7a..1aa56f0c 100644 --- a/client/displayservers/Wayland/registry.c +++ b/client/displayservers/Wayland/registry.c @@ -77,6 +77,9 @@ static void registryGlobalHandler(void * data, struct wl_registry * registry, else if (!strcmp(interface, wp_color_manager_v1_interface.name)) wlWm.colorManager = wl_registry_bind(wlWm.registry, name, &wp_color_manager_v1_interface, 1); + else if (!strcmp(interface, xdg_toplevel_icon_manager_v1_interface.name)) + wlWm.iconManager = wl_registry_bind(wlWm.registry, name, + &xdg_toplevel_icon_manager_v1_interface, 1); else if (wlWm.desktop->registryGlobalHandler( data, registry, name, interface, version)) return; diff --git a/client/displayservers/Wayland/wayland.c b/client/displayservers/Wayland/wayland.c index 9b65e54f..9b6e91cd 100644 --- a/client/displayservers/Wayland/wayland.c +++ b/client/displayservers/Wayland/wayland.c @@ -170,6 +170,8 @@ static bool waylandInit(const LG_DSInitParams params) if (!waylandEGLInit(params.w, params.h)) return false; + waylandIconInit(); + #ifdef ENABLE_OPENGL if (params.opengl && !waylandOpenGLInit()) return false; diff --git a/client/displayservers/Wayland/wayland.h b/client/displayservers/Wayland/wayland.h index dca9e07e..2d912f65 100644 --- a/client/displayservers/Wayland/wayland.h +++ b/client/displayservers/Wayland/wayland.h @@ -50,6 +50,7 @@ #include "wayland-fractional-scale-v1-client-protocol.h" #include "wayland-content-type-v1-client-protocol.h" #include "wayland-color-management-v1-client-protocol.h" +#include "wayland-xdg-toplevel-icon-v1-client-protocol.h" #include "scale.h" @@ -205,6 +206,12 @@ struct WaylandDSState bool cmHasPerceptualIntent; bool cmCanDoHDR; // true if compositor supports features needed for HDR + // toplevel icon manager + struct xdg_toplevel_icon_manager_v1 * iconManager; + + // set by the active desktop backend during shellInit + void * xdgToplevel; + // Pending HDR format to apply (set by frame thread, applied in swap buffers). // pendingHDRApply and pendingHDRClear are accessed from multiple threads // without a lock, so they must be atomic. @@ -370,3 +377,6 @@ bool waylandWaitFrame(void); void waylandSkipFrame(void); void waylandStopWaitFrame(void); void waylandNeedsResize(void); + +// icon module +bool waylandIconInit(void); diff --git a/client/displayservers/X11/x11.c b/client/displayservers/X11/x11.c index 74eec90f..5e342fe7 100644 --- a/client/displayservers/X11/x11.c +++ b/client/displayservers/X11/x11.c @@ -617,7 +617,7 @@ static bool x11Init(const LG_DSInitParams params) 32, PropModeReplace, (unsigned char *)icondata, - sizeof(icondata) / sizeof(icondata[0]) + icondataSize / sizeof(icondata[0]) ); /* create the blank cursor */ diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index 50f32322..a6b8da1a 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -14,6 +14,10 @@ function(build_resources) set(LG_RESOURCES_INCS "${LG_RESOURCES_INCS}" PARENT_SCOPE) endfunction() +set(RESOURCES_SOURCES + icondata.c +) + build_resources( lg-logo.svg status/spice.svg @@ -22,5 +26,5 @@ build_resources( no-input-cursor/32.xcur ) -add_library(lg_resources STATIC ${LG_RESOURCES_OBJS}) +add_library(lg_resources STATIC ${RESOURCES_SOURCES} ${LG_RESOURCES_OBJS}) set_target_properties(lg_resources PROPERTIES LINKER_LANGUAGE C) diff --git a/resources/icondata.c b/resources/icondata.c index 1ce1f464..ffe9cbdc 100644 --- a/resources/icondata.c +++ b/resources/icondata.c @@ -536,4 +536,4 @@ const unsigned long icondata[] = { 0xff9761c4, 0xff9761c4, 0xff9660c3, 0xf0905cbc, 0xaa905cbc, 0x4e8554af, 0x068150aa, 0x00000000, 0x00000000, 0x00000000 }; - +const unsigned icondataSize = sizeof(icondata); diff --git a/resources/icondata.h b/resources/icondata.h new file mode 100644 index 00000000..6ebd2a40 --- /dev/null +++ b/resources/icondata.h @@ -0,0 +1,22 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +extern const unsigned long icondata[]; +extern const unsigned icondataSize;