From: Etaash Mathamsetty etaash.mathamsetty@gmail.com
--- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/wayland.c | 5 + dlls/winewayland.drv/wayland_surface.c | 15 ++ dlls/winewayland.drv/waylanddrv.h | 30 ++- dlls/winewayland.drv/waylanddrv_main.c | 1 + dlls/winewayland.drv/window.c | 200 +++++++++++++++++ dlls/winewayland.drv/xdg-toplevel-icon-v1.xml | 205 ++++++++++++++++++ 7 files changed, 445 insertions(+), 12 deletions(-) create mode 100644 dlls/winewayland.drv/xdg-toplevel-icon-v1.xml
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index 9ad1ad6889d..27374c28316 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -9,6 +9,7 @@ SOURCES = \ opengl.c \ pointer-constraints-unstable-v1.xml \ relative-pointer-unstable-v1.xml \ + xdg-toplevel-icon-v1.xml \ version.rc \ viewporter.xml \ vulkan.c \ diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 9432dc934c9..ccbc94b7593 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -164,6 +164,11 @@ static void registry_handle_global(void *data, struct wl_registry *registry, process_wayland.zwp_relative_pointer_manager_v1 = wl_registry_bind(registry, id, &zwp_relative_pointer_manager_v1_interface, 1); } + else if(strcmp(interface, "xdg_toplevel_icon_manager_v1") == 0) + { + process_wayland.xdg_toplevel_icon_manager_v1 = + wl_registry_bind(registry, id, &xdg_toplevel_icon_manager_v1_interface, 1); + } }
static void registry_handle_global_remove(void *data, struct wl_registry *registry, diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 2178f5431cb..1572f21bce3 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -324,6 +324,21 @@ void wayland_surface_clear_role(struct wayland_surface *surface) break;
case WAYLAND_SURFACE_ROLE_TOPLEVEL: + if (surface->xdg_toplevel_icon) + { + xdg_toplevel_icon_manager_v1_set_icon( + process_wayland.xdg_toplevel_icon_manager_v1, + surface->xdg_toplevel, NULL); + if (surface->big_icon_buffer) + wayland_shm_buffer_unref(surface->big_icon_buffer); + if (surface->small_icon_buffer) + wayland_shm_buffer_unref(surface->small_icon_buffer); + xdg_toplevel_icon_v1_destroy(surface->xdg_toplevel_icon); + surface->big_icon_buffer = NULL; + surface->small_icon_buffer = NULL; + surface->xdg_toplevel_icon = NULL; + } + if (surface->xdg_toplevel) { xdg_toplevel_destroy(surface->xdg_toplevel); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 72a37cb3ffb..932fc137e4a 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -34,6 +34,7 @@ #include "viewporter-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"
#include "windef.h" #include "winbase.h" @@ -132,6 +133,7 @@ struct wayland struct wl_subcompositor *wl_subcompositor; struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1; struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1; + struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1; struct wayland_seat seat; struct wayland_keyboard keyboard; struct wayland_pointer pointer; @@ -198,6 +200,18 @@ struct wayland_client_surface struct wp_viewport *wp_viewport; };
+struct wayland_shm_buffer +{ + struct wl_list link; + struct wl_buffer *wl_buffer; + int width, height; + void *map_data; + size_t map_size; + BOOL busy; + LONG ref; + HRGN damage_region; +}; + struct wayland_surface { HWND hwnd; @@ -212,6 +226,9 @@ struct wayland_surface { struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; + struct xdg_toplevel_icon_v1 *xdg_toplevel_icon; + struct wayland_shm_buffer *small_icon_buffer; + struct wayland_shm_buffer *big_icon_buffer; }; struct { @@ -227,18 +244,6 @@ struct wayland_surface HCURSOR hcursor; };
-struct wayland_shm_buffer -{ - struct wl_list link; - struct wl_buffer *wl_buffer; - int width, height; - void *map_data; - size_t map_size; - BOOL busy; - LONG ref; - HRGN damage_region; -}; - /********************************************************************** * Wayland initialization */ @@ -375,6 +380,7 @@ BOOL WAYLAND_ClipCursor(const RECT *clip, BOOL reset); LRESULT WAYLAND_DesktopWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); void WAYLAND_DestroyWindow(HWND hwnd); void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor); +void WAYLAND_SetWindowIcon(HWND hwnd, UINT type, HICON icon); void WAYLAND_SetWindowText(HWND hwnd, LPCWSTR text); LRESULT WAYLAND_SysCommand(HWND hwnd, WPARAM wparam, LPARAM lparam, const POINT *pos); UINT WAYLAND_UpdateDisplayDevices(const struct gdi_device_manager *device_manager, void *param); diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index 47c1299dd01..c2d72ecd08a 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -41,6 +41,7 @@ static const struct user_driver_funcs waylanddrv_funcs = .pKbdLayerDescriptor = WAYLAND_KbdLayerDescriptor, .pReleaseKbdTables = WAYLAND_ReleaseKbdTables, .pSetCursor = WAYLAND_SetCursor, + .pSetWindowIcon = WAYLAND_SetWindowIcon, .pSetWindowText = WAYLAND_SetWindowText, .pSysCommand = WAYLAND_SysCommand, .pUpdateDisplayDevices = WAYLAND_UpdateDisplayDevices, diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 67718936b53..485fe8b6286 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -431,6 +431,180 @@ BOOL WAYLAND_WindowPosChanging(HWND hwnd, UINT swp_flags, BOOL shaped, const str }
+/*********************************************************************** + * get_bitmap_argb + * + * Return the bitmap bits in ARGB format. Helper for setting icon hints. + */ +static unsigned int *get_bitmap_argb( HDC hdc, HBITMAP color, HBITMAP mask, unsigned int *size ) +{ + char buffer[FIELD_OFFSET( BITMAPINFO, bmiColors[256] )]; + BITMAPINFO *info = (BITMAPINFO *)buffer; + BITMAP bm; + unsigned int *bits = NULL; + unsigned int *ptr; + unsigned char *mask_bits = NULL; + int i, j; + BOOL has_alpha = FALSE; + + if (!NtGdiExtGetObjectW( color, sizeof(bm), &bm )) return NULL; + info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info->bmiHeader.biWidth = bm.bmWidth; + info->bmiHeader.biHeight = -bm.bmHeight; + info->bmiHeader.biPlanes = 1; + info->bmiHeader.biBitCount = 32; + info->bmiHeader.biCompression = BI_RGB; + info->bmiHeader.biSizeImage = bm.bmWidth * bm.bmHeight * WINEWAYLAND_BYTES_PER_PIXEL; + info->bmiHeader.biXPelsPerMeter = 0; + info->bmiHeader.biYPelsPerMeter = 0; + info->bmiHeader.biClrUsed = 0; + info->bmiHeader.biClrImportant = 0; + *size = info->bmiHeader.biSizeImage + 2 * sizeof(*bits); + if (!(bits = malloc( *size ))) goto failed; + ptr = bits; + if (!NtGdiGetDIBitsInternal( hdc, color, 0, bm.bmHeight, ptr + 2, info, DIB_RGB_COLORS, 0, 0 )) + goto failed; + + ptr[0] = bm.bmWidth; + ptr[1] = bm.bmHeight; + + for (i = 0; i < bm.bmWidth * bm.bmHeight; i++) + if ((has_alpha = (ptr[i + 2] & 0xff000000) != 0)) break; + + if (!has_alpha) + { + unsigned int width_bytes = (bm.bmWidth + 31) / 32 * 4; + /* generate alpha channel from the mask */ + info->bmiHeader.biBitCount = 1; + info->bmiHeader.biSizeImage = width_bytes * bm.bmHeight; + if (!(mask_bits = malloc( info->bmiHeader.biSizeImage ))) goto failed; + if (!NtGdiGetDIBitsInternal( hdc, mask, 0, bm.bmHeight, mask_bits, info, DIB_RGB_COLORS, 0, 0 )) + goto failed; + ptr = bits + 2; + for (i = 0; i < bm.bmHeight; i++) + for (j = 0; j < bm.bmWidth; j++, ptr++) + if (!((mask_bits[i * width_bytes + j / 8] << (j % 8)) & 0x80)) *ptr |= 0xff000000; + free( mask_bits ); + } + + return bits; + +failed: + free( bits ); + free( mask_bits ); + return NULL; +} + +static HICON get_icon_info( HICON icon, ICONINFO *ii ) +{ + return icon && NtUserGetIconInfo( icon, ii, NULL, NULL, NULL, 0 ) ? icon : NULL; +} + +static void fetch_icon_data(HWND hwnd, HICON icon_small, HICON icon_big) +{ + ICONINFO ii, ii_small; + HDC hDC; + unsigned int size; + unsigned int *bits; + struct wayland_win_data *data = NULL; + struct wayland_surface *surface = NULL; + struct wayland_shm_buffer *icon_big_buf = NULL, *icon_small_buf = NULL; + + if (!process_wayland.xdg_toplevel_icon_manager_v1) return; + + icon_big = get_icon_info( icon_big, &ii ); + if (!icon_big) + { + icon_big = get_icon_info( (HICON)send_message( hwnd, WM_GETICON, ICON_BIG, 0 ), &ii ); + if (!icon_big) + icon_big = get_icon_info( (HICON)NtUserGetClassLongPtrW( hwnd, GCLP_HICON ), &ii ); + if (!icon_big) + { + icon_big = LoadImageW( 0, (const WCHAR *)IDI_WINLOGO, IMAGE_ICON, 0, 0, + LR_SHARED | LR_DEFAULTSIZE ); + icon_big = get_icon_info( icon_big, &ii ); + } + } + + icon_small = get_icon_info( icon_small, &ii_small ); + if (!icon_small) + { + icon_small = get_icon_info( (HICON)send_message( hwnd, WM_GETICON, ICON_SMALL, 0 ), &ii_small ); + if (!icon_small) + icon_small = get_icon_info( (HICON)NtUserGetClassLongPtrW( hwnd, GCLP_HICONSM ), &ii_small ); + } + + if (!icon_big) return; + + hDC = NtGdiCreateCompatibleDC(0); + bits = get_bitmap_argb( hDC, ii.hbmColor, ii.hbmMask, &size ); + if (bits) + { + if(icon_small) + { + unsigned int size_small; + unsigned int *bits_small; + + if ((bits_small = get_bitmap_argb( hDC, ii_small.hbmColor, ii_small.hbmMask, &size_small )) && + (bits_small[0] != bits[0] || bits_small[1] != bits[1])) /* size must be different */ + { + icon_small_buf = wayland_shm_buffer_create(bits_small[0], bits_small[1], WL_SHM_FORMAT_ARGB8888); + memcpy(icon_small_buf->map_data, bits_small+2, size_small-2*sizeof(*bits_small)); + } + free( bits_small ); + NtGdiDeleteObjectApp( ii_small.hbmColor ); + NtGdiDeleteObjectApp( ii_small.hbmMask ); + } + + icon_big_buf = wayland_shm_buffer_create(bits[0], bits[1], WL_SHM_FORMAT_ARGB8888); + memcpy(icon_big_buf->map_data, bits+2, size-2*sizeof(*bits)); + } + + free(bits); + NtGdiDeleteObjectApp( ii.hbmColor ); + NtGdiDeleteObjectApp( ii.hbmMask ); + NtGdiDeleteObjectApp( hDC ); + + + if ((data = wayland_win_data_get(hwnd)) && (surface = data->wayland_surface) && surface->xdg_toplevel) + { + if (surface->xdg_toplevel_icon) + { + xdg_toplevel_icon_manager_v1_set_icon(process_wayland.xdg_toplevel_icon_manager_v1, surface->xdg_toplevel, NULL); + xdg_toplevel_icon_v1_destroy(surface->xdg_toplevel_icon); + if(surface->big_icon_buffer) wayland_shm_buffer_unref(surface->big_icon_buffer); + if(surface->small_icon_buffer) wayland_shm_buffer_unref(surface->small_icon_buffer); + surface->xdg_toplevel_icon = NULL; + surface->small_icon_buffer = NULL; + surface->big_icon_buffer = NULL; + } + + if (icon_big_buf) + { + surface->xdg_toplevel_icon = xdg_toplevel_icon_manager_v1_create_icon(process_wayland.xdg_toplevel_icon_manager_v1); + + /* FIXME: what to do with scale ? */ + xdg_toplevel_icon_v1_add_buffer(surface->xdg_toplevel_icon, icon_big_buf->wl_buffer, 1); + surface->big_icon_buffer = icon_big_buf; + if (icon_small_buf) + { + xdg_toplevel_icon_v1_add_buffer(surface->xdg_toplevel_icon, icon_small_buf->wl_buffer, 1); + surface->small_icon_buffer = icon_small_buf; + } + xdg_toplevel_icon_v1_set_name(surface->xdg_toplevel_icon, ""); + + xdg_toplevel_icon_manager_v1_set_icon(process_wayland.xdg_toplevel_icon_manager_v1, surface->xdg_toplevel, surface->xdg_toplevel_icon); + } + } + else + { + if (icon_big_buf) wayland_shm_buffer_unref(icon_big_buf); + if (icon_small_buf) wayland_shm_buffer_unref(icon_small_buf); + } + + if (data) wayland_win_data_release(data); +} + /*********************************************************************** * WAYLAND_WindowPosChanged */ @@ -480,6 +654,8 @@ void WAYLAND_WindowPosChanged(HWND hwnd, HWND insert_after, HWND owner_hint, UIN }
wayland_win_data_release(data); + + if (data->wayland_surface && !data->wayland_surface->big_icon_buffer) fetch_icon_data(hwnd, 0, 0); }
static void wayland_configure_window(HWND hwnd) @@ -650,6 +826,30 @@ static enum xdg_toplevel_resize_edge hittest_to_resize_edge(WPARAM hittest) } }
+/***************************************************************** + * WAYLAND_SetWindowIcon + */ +void WAYLAND_SetWindowIcon(HWND hwnd, UINT type, HICON icon) +{ + struct wayland_win_data *data; + + TRACE("hwnd=%p text=%u icon=%p\n", hwnd, type, icon); + + if (process_wayland.xdg_toplevel_icon_manager_v1) + { + if ((data = wayland_win_data_get(hwnd))) + { + wayland_win_data_release(data); + if (type == ICON_BIG) fetch_icon_data(hwnd, 0, icon); + else fetch_icon_data(hwnd, icon, 0); + } + } + else + { + FIXME("missing xdg_toplevel_icon_manager_v1, returning\n"); + } +} + /***************************************************************** * WAYLAND_SetWindowText */ diff --git a/dlls/winewayland.drv/xdg-toplevel-icon-v1.xml b/dlls/winewayland.drv/xdg-toplevel-icon-v1.xml new file mode 100644 index 00000000000..fc409fef7c6 --- /dev/null +++ b/dlls/winewayland.drv/xdg-toplevel-icon-v1.xml @@ -0,0 +1,205 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="xdg_toplevel_icon_v1"> + + <copyright> + 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. + </copyright> + + <description summary="protocol to assign icons to toplevels"> + 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. + </description> + + <interface name="xdg_toplevel_icon_manager_v1" version="1"> + <description summary="interface to manage toplevel icons"> + This interface allows clients to create toplevel window icons and set + them on toplevel windows to be displayed to the user. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the toplevel icon manager"> + Destroy the toplevel icon manager. + This does not destroy objects created with the manager. + </description> + </request> + + <request name="create_icon"> + <description summary="create a new icon instance"> + Creates a new icon object. This icon can then be attached to a + xdg_toplevel via the 'set_icon' request. + </description> + <arg name="id" type="new_id" interface="xdg_toplevel_icon_v1"/> + </request> + + <request name="set_icon"> + <description summary="set an icon on a toplevel window"> + 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. + </description> + <arg name="toplevel" type="object" interface="xdg_toplevel" summary="the toplevel to act on"/> + <arg name="icon" type="object" interface="xdg_toplevel_icon_v1" allow-null="true"/> + </request> + + <event name="icon_size"> + <description summary="describes a supported & preferred icon size"> + 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. + </description> + <arg name="size" type="int" + summary="the edge size of the square icon in surface-local coordinates, e.g. 64"/> + </event> + + <event name="done"> + <description summary="all information has been sent"> + This event is sent after all 'icon_size' events have been sent. + </description> + </event> + </interface> + + <interface name="xdg_toplevel_icon_v1" version="1"> + <description summary="a toplevel window icon"> + 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. + </description> + + <enum name="error"> + <entry name="invalid_buffer" + summary="the provided buffer does not satisfy requirements" + value="1"/> + <entry name="immutable" + summary="the icon has already been assigned to a toplevel and must not be changed" + value="2"/> + <entry name="no_buffer" + summary="the provided buffer has been destroyed before the toplevel icon" + value="3"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the icon object"> + 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. + </description> + </request> + + <request name="set_name"> + <description summary="set an icon name"> + 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-lates... + </description> + <arg name="icon_name" type="string"/> + </request> + + <request name="add_buffer"> + <description summary="add icon data from a pixel buffer"> + 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. + </description> + <arg name="buffer" type="object" interface="wl_buffer"/> + <arg name="scale" type="int" + summary="the scaling factor of the icon, e.g. 1"/> + </request> + </interface> +</protocol>