From: Alexandros Frantzis alexandros.frantzis@collabora.com
Set the cursor image used for Wayland surfaces by using the Windows cursor bitmap data. --- dlls/winewayland.drv/wayland_pointer.c | 286 +++++++++++++++++++++++++ dlls/winewayland.drv/wayland_surface.c | 3 + dlls/winewayland.drv/waylanddrv.h | 10 + dlls/winewayland.drv/waylanddrv_main.c | 1 + 4 files changed, 300 insertions(+)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index 222c35f6692..69f3a7c16a7 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -27,6 +27,7 @@ #include <linux/input.h> #undef SW_MAX /* Also defined in winuser.rh */ #include <math.h> +#include <stdlib.h>
#include "waylanddrv.h" #include "wine/debug.h" @@ -93,6 +94,14 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer,
pthread_mutex_lock(&pointer->mutex); pointer->focused_hwnd = hwnd; + pointer->enter_serial = serial; + /* The cursor is undefined at every enter, so we set it again with + * the latest information we have. */ + wl_pointer_set_cursor(pointer->wl_pointer, + pointer->enter_serial, + pointer->cursor.wl_surface, + pointer->cursor.hotspot_x, + pointer->cursor.hotspot_y); pthread_mutex_unlock(&pointer->mutex);
/* Handle the enter as a motion, to account for cases where the @@ -112,6 +121,7 @@ static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer,
pthread_mutex_lock(&pointer->mutex); pointer->focused_hwnd = NULL; + pointer->enter_serial = 0; pthread_mutex_unlock(&pointer->mutex); }
@@ -208,6 +218,7 @@ void wayland_pointer_init(struct wl_pointer *wl_pointer) pthread_mutex_lock(&pointer->mutex); pointer->wl_pointer = wl_pointer; pointer->focused_hwnd = NULL; + pointer->enter_serial = 0; pthread_mutex_unlock(&pointer->mutex); wl_pointer_add_listener(pointer->wl_pointer, &pointer_listener, NULL); } @@ -220,5 +231,280 @@ void wayland_pointer_deinit(void) wl_pointer_release(pointer->wl_pointer); pointer->wl_pointer = NULL; pointer->focused_hwnd = NULL; + pointer->enter_serial = 0; + pthread_mutex_unlock(&pointer->mutex); +} + +/*********************************************************************** + * create_mono_cursor_buffer + * + * Create a wayland_shm_buffer for a monochrome cursor bitmap. + * + * Adapted from wineandroid.drv code. + */ +static struct wayland_shm_buffer *create_mono_cursor_buffer(HBITMAP bmp) +{ + struct wayland_shm_buffer *shm_buffer = NULL; + BITMAP bm; + char *mask = NULL; + unsigned int i, j, stride, mask_size, *ptr; + + if (!NtGdiExtGetObjectW(bmp, sizeof(bm), &bm)) goto done; + stride = ((bm.bmWidth + 15) >> 3) & ~1; + mask_size = stride * bm.bmHeight; + if (!(mask = malloc(mask_size))) goto done; + if (!NtGdiGetBitmapBits(bmp, mask_size, mask)) goto done; + + bm.bmHeight /= 2; + shm_buffer = wayland_shm_buffer_create(bm.bmWidth, bm.bmHeight, + WL_SHM_FORMAT_ARGB8888); + if (!shm_buffer) goto done; + + ptr = shm_buffer->map_data; + for (i = 0; i < bm.bmHeight; i++) + { + for (j = 0; j < bm.bmWidth; j++, ptr++) + { + int and = ((mask[i * stride + j / 8] << (j % 8)) & 0x80); + int xor = ((mask[(i + bm.bmHeight) * stride + j / 8] << (j % 8)) & 0x80); + if (!xor && and) + *ptr = 0; + else if (xor && !and) + *ptr = 0xffffffff; + else + /* we can't draw "invert" pixels, so render them as black instead */ + *ptr = 0xff000000; + } + } + +done: + free(mask); + return shm_buffer; +} + +/*********************************************************************** + * create_color_cursor_buffer + * + * Create a wayland_shm_buffer for a color cursor bitmap. + * + * Adapted from wineandroid.drv code. + */ +static struct wayland_shm_buffer *create_color_cursor_buffer(HDC hdc, HBITMAP color, + HBITMAP mask) +{ + struct wayland_shm_buffer *shm_buffer = NULL; + char buffer[FIELD_OFFSET(BITMAPINFO, bmiColors[256])]; + BITMAPINFO *info = (BITMAPINFO *)buffer; + BITMAP bm; + unsigned int *ptr, *bits = NULL; + unsigned char *mask_bits = NULL; + int i, j; + BOOL has_alpha = FALSE; + + if (!NtGdiExtGetObjectW(color, sizeof(bm), &bm)) goto failed; + + shm_buffer = wayland_shm_buffer_create(bm.bmWidth, bm.bmHeight, + WL_SHM_FORMAT_ARGB8888); + if (!shm_buffer) goto failed; + bits = shm_buffer->map_data; + + 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 * 4; + info->bmiHeader.biXPelsPerMeter = 0; + info->bmiHeader.biYPelsPerMeter = 0; + info->bmiHeader.biClrUsed = 0; + info->bmiHeader.biClrImportant = 0; + + if (!NtGdiGetDIBitsInternal(hdc, color, 0, bm.bmHeight, bits, info, + DIB_RGB_COLORS, 0, 0)) + goto failed; + + for (i = 0; i < bm.bmWidth * bm.bmHeight; i++) + if ((has_alpha = (bits[i] & 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; + 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); + } + + /* Wayland requires pre-multiplied alpha values */ + for (ptr = bits, i = 0; i < bm.bmWidth * bm.bmHeight; ptr++, i++) + { + unsigned char alpha = *ptr >> 24; + if (alpha == 0) + { + *ptr = 0; + } + else if (alpha != 255) + { + *ptr = (alpha << 24) | + (((BYTE)(*ptr >> 16) * alpha / 255) << 16) | + (((BYTE)(*ptr >> 8) * alpha / 255) << 8) | + (((BYTE)*ptr * alpha / 255)); + } + } + + return shm_buffer; + +failed: + if (shm_buffer) wayland_shm_buffer_unref(shm_buffer); + free(mask_bits); + return NULL; +} + +/*********************************************************************** + * get_icon_info + * + * Local GetIconInfoExW helper implementation. + */ +static BOOL get_icon_info(HICON handle, ICONINFOEXW *ret) +{ + UNICODE_STRING module, res_name; + ICONINFO info; + + module.Buffer = ret->szModName; + module.MaximumLength = sizeof(ret->szModName) - sizeof(WCHAR); + res_name.Buffer = ret->szResName; + res_name.MaximumLength = sizeof(ret->szResName) - sizeof(WCHAR); + if (!NtUserGetIconInfo(handle, &info, &module, &res_name, NULL, 0)) return FALSE; + ret->fIcon = info.fIcon; + ret->xHotspot = info.xHotspot; + ret->yHotspot = info.yHotspot; + ret->hbmColor = info.hbmColor; + ret->hbmMask = info.hbmMask; + ret->wResID = res_name.Length ? 0 : LOWORD(res_name.Buffer); + ret->szModName[module.Length] = 0; + ret->szResName[res_name.Length] = 0; + return TRUE; +} + +static void wayland_pointer_update_cursor(HCURSOR hcursor) +{ + struct wayland_cursor *cursor = &process_wayland.pointer.cursor; + ICONINFOEXW info = {0}; + + if (!hcursor) goto clear_cursor; + + /* Create a new buffer for the specified cursor. */ + if (cursor->shm_buffer) + { + wayland_shm_buffer_unref(cursor->shm_buffer); + cursor->shm_buffer = NULL; + } + + if (!get_icon_info(hcursor, &info)) + { + ERR("Failed to get icon info for cursor=%p\n", hcursor); + goto clear_cursor; + } + + if (info.hbmColor) + { + HDC hdc = NtGdiCreateCompatibleDC(0); + cursor->shm_buffer = + create_color_cursor_buffer(hdc, info.hbmColor, info.hbmMask); + NtGdiDeleteObjectApp(hdc); + } + else + { + cursor->shm_buffer = create_mono_cursor_buffer(info.hbmMask); + } + + if (info.hbmColor) NtGdiDeleteObjectApp(info.hbmColor); + if (info.hbmMask) NtGdiDeleteObjectApp(info.hbmMask); + + cursor->hotspot_x = info.xHotspot; + cursor->hotspot_y = info.yHotspot; + + if (!cursor->shm_buffer) + { + ERR("Failed to create shm_buffer for cursor=%p\n", hcursor); + goto clear_cursor; + } + + /* Make sure the hotspot is valid. */ + if (cursor->hotspot_x >= cursor->shm_buffer->width || + cursor->hotspot_y >= cursor->shm_buffer->height) + { + cursor->hotspot_x = cursor->shm_buffer->width / 2; + cursor->hotspot_y = cursor->shm_buffer->height / 2; + } + + if (!cursor->wl_surface) + { + cursor->wl_surface = + wl_compositor_create_surface(process_wayland.wl_compositor); + if (!cursor->wl_surface) + { + ERR("Failed to create wl_surface for cursor\n"); + goto clear_cursor; + } + } + + /* Commit the cursor buffer to the cursor surface. */ + wl_surface_attach(cursor->wl_surface, + cursor->shm_buffer->wl_buffer, 0, 0); + wl_surface_damage_buffer(cursor->wl_surface, 0, 0, + cursor->shm_buffer->width, + cursor->shm_buffer->height); + wl_surface_commit(cursor->wl_surface); + + return; + +clear_cursor: + if (cursor->shm_buffer) + { + wayland_shm_buffer_unref(cursor->shm_buffer); + cursor->shm_buffer = NULL; + } + if (cursor->wl_surface) + { + wl_surface_destroy(cursor->wl_surface); + cursor->wl_surface = NULL; + } +} + +/*********************************************************************** + * WAYLAND_SetCursor + */ +void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + + TRACE("hwnd=%p hcursor=%p\n", hwnd, hcursor); + + pthread_mutex_lock(&pointer->mutex); + if (pointer->focused_hwnd == hwnd) + { + wayland_pointer_update_cursor(hcursor); + wl_pointer_set_cursor(pointer->wl_pointer, + pointer->enter_serial, + pointer->cursor.wl_surface, + pointer->cursor.hotspot_x, + pointer->cursor.hotspot_y); + wl_display_flush(process_wayland.wl_display); + } pthread_mutex_unlock(&pointer->mutex); } diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index bc42da72b1e..7e77c9ba55f 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -126,7 +126,10 @@ void wayland_surface_destroy(struct wayland_surface *surface) { pthread_mutex_lock(&process_wayland.pointer.mutex); if (process_wayland.pointer.focused_hwnd == surface->hwnd) + { process_wayland.pointer.focused_hwnd = NULL; + process_wayland.pointer.enter_serial = 0; + } pthread_mutex_unlock(&process_wayland.pointer.mutex);
pthread_mutex_lock(&xdg_data_mutex); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index e5c456d19e8..f4292e0ae28 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -56,10 +56,19 @@ enum wayland_window_message WM_WAYLAND_INIT_DISPLAY_DEVICES = 0x80001000 };
+struct wayland_cursor +{ + struct wayland_shm_buffer *shm_buffer; + struct wl_surface *wl_surface; + int hotspot_x, hotspot_y; +}; + struct wayland_pointer { struct wl_pointer *wl_pointer; HWND focused_hwnd; + uint32_t enter_serial; + struct wayland_cursor cursor; pthread_mutex_t mutex; };
@@ -204,6 +213,7 @@ RGNDATA *get_region_data(HRGN region) DECLSPEC_HIDDEN;
LRESULT WAYLAND_DesktopWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) DECLSPEC_HIDDEN; void WAYLAND_DestroyWindow(HWND hwnd) DECLSPEC_HIDDEN; +void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) DECLSPEC_HIDDEN; BOOL WAYLAND_UpdateDisplayDevices(const struct gdi_device_manager *device_manager, BOOL force, void *param) DECLSPEC_HIDDEN; LRESULT WAYLAND_WindowMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) DECLSPEC_HIDDEN; diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index 530cdd83d90..eb54efe1180 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -33,6 +33,7 @@ static const struct user_driver_funcs waylanddrv_funcs = { .pDesktopWindowProc = WAYLAND_DesktopWindowProc, .pDestroyWindow = WAYLAND_DestroyWindow, + .pSetCursor = WAYLAND_SetCursor, .pUpdateDisplayDevices = WAYLAND_UpdateDisplayDevices, .pWindowMessage = WAYLAND_WindowMessage, .pWindowPosChanged = WAYLAND_WindowPosChanged,