This MR improves driver support for scaled HiDPI displays, by respecting the application/window DPI.
Since we don't support per-monitor DPI information and awareness modes yet, the user should set the Wine system DPI to match the DPI/scale of the display the app will be running on, for best results.
Here is how things look with a few scenarios:
1. Screen scaled at 2x, application at 96dpi (e.g., dpi unaware apps) => the compositor automatically scales the surface: ![wine-wayland-2x-96dpi](/uploads/712bf2e64991889c91861d63dfd4fd60/wine-wayland-2x-96dpi.png)
2. Screen scaled at 2x, application at 192dpi (dpi matches compositor scale) => no compositor scaling: ![wine-wayland-2x-192dpi](/uploads/85c1201e1208f8b7e1c63804c7f83d1c/wine-wayland-2x-192dpi.png)
3. Screen scaled at 1x, application at 96dpi (dpi matches compositor scale) => no compositor scaling: ![wine-wayland-1x-96dpi](/uploads/8dfaaff1fc62defdb26f57c78cf9a427/wine-wayland-1x-96dpi.png)
4. Screen scaled at 1x, application at 192dpi, not typically used, just wanted to show what happens with such a mismatch: ![wine-wayland-1x-192dpi](/uploads/bb035140dd9c91ee3a22832c7280ab1b/wine-wayland-1x-192dpi.png)
Thanks!
-- v4: winewayland.drv: Ignore spurious size hints. winewayland.drv: Present cursors with the correct scale. winewayland.drv: Refactor cursor code to prepare for scaling support. winewayland.drv: Present surfaces with the correct scale. winewayland.drv: Prepare to handle different coordinate spaces.
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Introduce and use functions to convert between the window logical and Wayland surface coordinate spaces. At the moment the two are the same but this will change with the introduction of scaling support. --- dlls/winewayland.drv/wayland_pointer.c | 36 ++++++++++++------ dlls/winewayland.drv/wayland_surface.c | 51 +++++++++++++++++++++----- dlls/winewayland.drv/waylanddrv.h | 6 +++ dlls/winewayland.drv/window.c | 22 +++++++---- 4 files changed, 86 insertions(+), 29 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index ff376882d73..025793c15d6 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -50,30 +50,42 @@ static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { INPUT input = {0}; - RECT window_rect; + RECT *window_rect; HWND hwnd; - int screen_x, screen_y; + POINT screen; + struct wayland_surface *surface;
if (!(hwnd = wayland_pointer_get_focused_hwnd())) return; - if (!NtUserGetWindowRect(hwnd, &window_rect)) return; + if (!(surface = wayland_surface_lock_hwnd(hwnd))) return;
- screen_x = round(wl_fixed_to_double(sx)) + window_rect.left; - screen_y = round(wl_fixed_to_double(sy)) + window_rect.top; + window_rect = &surface->window.rect; + + wayland_surface_coords_to_window(surface, + wl_fixed_to_double(sx), + wl_fixed_to_double(sy), + (int *)&screen.x, (int *)&screen.y); + screen.x += window_rect->left; + screen.y += window_rect->top; /* Sometimes, due to rounding, we may end up with pointer coordinates * slightly outside the target window, so bring them within bounds. */ - if (screen_x >= window_rect.right) screen_x = window_rect.right - 1; - else if (screen_x < window_rect.left) screen_x = window_rect.left; - if (screen_y >= window_rect.bottom) screen_y = window_rect.bottom - 1; - else if (screen_y < window_rect.top) screen_y = window_rect.top; + if (screen.x >= window_rect->right) screen.x = window_rect->right - 1; + else if (screen.x < window_rect->left) screen.x = window_rect->left; + if (screen.y >= window_rect->bottom) screen.y = window_rect->bottom - 1; + else if (screen.y < window_rect->top) screen.y = window_rect->top; + + pthread_mutex_unlock(&surface->mutex); + + /* Hardware input events are in physical coordinates. */ + if (!NtUserLogicalToPerMonitorDPIPhysicalPoint(hwnd, &screen)) return;
input.type = INPUT_MOUSE; - input.mi.dx = screen_x; - input.mi.dy = screen_y; + input.mi.dx = screen.x; + input.mi.dy = screen.y; input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
TRACE("hwnd=%p wayland_xy=%.2f,%.2f screen_xy=%d,%d\n", hwnd, wl_fixed_to_double(sx), wl_fixed_to_double(sy), - screen_x, screen_y); + (int)screen.x, (int)screen.y);
__wine_send_input(hwnd, &input, NULL); } diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index ae4812ebb08..01d9704d511 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -380,14 +380,11 @@ static void wayland_surface_get_rect_in_monitor(struct wayland_surface *surface, * * Sets the xdg_surface geometry */ -static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface) +static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface, + int width, int height) { - int width, height; RECT rect;
- width = surface->window.rect.right - surface->window.rect.left; - height = surface->window.rect.bottom - surface->window.rect.top; - /* If the window size is bigger than the current state accepts, use the * largest visible (from Windows' perspective) subregion of the window. */ if ((surface->current.state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED | @@ -396,6 +393,11 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface { wayland_surface_get_rect_in_monitor(surface, &rect);
+ wayland_surface_coords_from_window(surface, rect.left, rect.top, + (int *)&rect.left, (int *)&rect.top); + wayland_surface_coords_from_window(surface, rect.right, rect.bottom, + (int *)&rect.right, (int *)&rect.bottom); + /* If the window rect in the monitor is smaller than required, * fall back to an appropriately sized rect at the top-left. */ if ((surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && @@ -436,15 +438,18 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface BOOL wayland_surface_reconfigure(struct wayland_surface *surface) { struct wayland_window_config *window = &surface->window; - int width, height; + int win_width, win_height, width, height;
if (!surface->xdg_toplevel) return TRUE;
- width = surface->window.rect.right - surface->window.rect.left; - height = surface->window.rect.bottom - surface->window.rect.top; + win_width = surface->window.rect.right - surface->window.rect.left; + win_height = surface->window.rect.bottom - surface->window.rect.top; + + wayland_surface_coords_from_window(surface, win_width, win_height, + &width, &height);
TRACE("hwnd=%p window=%dx%d,%#x processing=%dx%d,%#x current=%dx%d,%#x\n", - surface->hwnd, width, height, window->state, + surface->hwnd, win_width, win_height, window->state, surface->processing.width, surface->processing.height, surface->processing.state, surface->current.width, surface->current.height, surface->current.state); @@ -479,7 +484,7 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) return FALSE; }
- wayland_surface_reconfigure_geometry(surface); + wayland_surface_reconfigure_geometry(surface, width, height);
return TRUE; } @@ -616,3 +621,29 @@ err: if (shm_buffer) wayland_shm_buffer_unref(shm_buffer); return NULL; } + +/********************************************************************** + * wayland_surface_coords_from_window + * + * Converts the window (logical) coordinates to wayland surface-local coordinates. + */ +void wayland_surface_coords_from_window(struct wayland_surface *surface, + int window_x, int window_y, + int *surface_x, int *surface_y) +{ + *surface_x = window_x; + *surface_y = window_y; +} + +/********************************************************************** + * wayland_surface_coords_to_window + * + * Converts the surface-local coordinates to window (logical) coordinates. + */ +void wayland_surface_coords_to_window(struct wayland_surface *surface, + double surface_x, double surface_y, + int *window_x, int *window_y) +{ + *window_x = round(surface_x); + *window_y = round(surface_y); +} diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 4bcd9e6706e..7b883d48184 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -204,6 +204,12 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) DECLSPEC_HIDDE BOOL wayland_surface_config_is_compatible(struct wayland_surface_config *conf, int width, int height, enum wayland_surface_config_state state) DECLSPEC_HIDDEN; +void wayland_surface_coords_from_window(struct wayland_surface *surface, + int window_x, int window_y, + int *surface_x, int *surface_y) DECLSPEC_HIDDEN; +void wayland_surface_coords_to_window(struct wayland_surface *surface, + double surface_x, double surface_y, + int *window_x, int *window_y) DECLSPEC_HIDDEN;
/********************************************************************** * Wayland SHM buffer diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 57d2f15a06b..78022670a10 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -384,7 +384,8 @@ static void wayland_resize_desktop(void) static void wayland_configure_window(HWND hwnd) { struct wayland_surface *surface; - INT width, height; + INT width, height, window_width, window_height; + INT window_surf_width, window_surf_height; UINT flags = 0; uint32_t state; DWORD style; @@ -434,21 +435,28 @@ static void wayland_configure_window(HWND hwnd) flags |= SWP_FRAMECHANGED; }
+ wayland_surface_coords_from_window(surface, + surface->window.rect.right - + surface->window.rect.left, + surface->window.rect.bottom - + surface->window.rect.top, + &window_surf_width, &window_surf_height); + /* If the window is already fullscreen and its size is compatible with what * the compositor is requesting, don't force a resize, since some applications * are very insistent on a particular fullscreen size (which may not match * the monitor size). */ if ((surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) && wayland_surface_config_is_compatible(&surface->processing, - surface->window.rect.right - - surface->window.rect.left, - surface->window.rect.bottom - - surface->window.rect.top, + window_surf_width, window_surf_height, surface->window.state)) { flags |= SWP_NOSIZE; }
+ wayland_surface_coords_to_window(surface, width, height, + &window_width, &window_height); + pthread_mutex_unlock(&surface->mutex);
TRACE("processing=%dx%d,%#x\n", width, height, state); @@ -457,7 +465,7 @@ static void wayland_configure_window(HWND hwnd) if (needs_exit_size_move) send_message(hwnd, WM_EXITSIZEMOVE, 0, 0);
flags |= SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE; - if (width == 0 || height == 0) flags |= SWP_NOSIZE; + if (window_width == 0 || window_height == 0) flags |= SWP_NOSIZE;
style = NtUserGetWindowLongW(hwnd, GWL_STYLE); if (!(state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) != !(style & WS_MAXIMIZE)) @@ -474,7 +482,7 @@ static void wayland_configure_window(HWND hwnd) flags |= SWP_NOSENDCHANGING; }
- NtUserSetWindowPos(hwnd, 0, 0, 0, width, height, flags); + NtUserSetWindowPos(hwnd, 0, 0, 0, window_width, window_height, flags); }
/**********************************************************************
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Take into account the window scale factor and use wp_viewport to instruct the compositor to present surfaces at that scale. --- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/viewporter.xml | 180 +++++++++++++++++++++++++ dlls/winewayland.drv/wayland.c | 5 + dlls/winewayland.drv/wayland_surface.c | 43 +++++- dlls/winewayland.drv/waylanddrv.h | 5 + dlls/winewayland.drv/window.c | 1 + 6 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 dlls/winewayland.drv/viewporter.xml
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index e1019ad8348..3be5d0513f7 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -7,6 +7,7 @@ SOURCES = \ display.c \ dllmain.c \ version.rc \ + viewporter.xml \ wayland.c \ wayland_output.c \ wayland_pointer.c \ diff --git a/dlls/winewayland.drv/viewporter.xml b/dlls/winewayland.drv/viewporter.xml new file mode 100644 index 00000000000..d1048d1f332 --- /dev/null +++ b/dlls/winewayland.drv/viewporter.xml @@ -0,0 +1,180 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="viewporter"> + + <copyright> + Copyright © 2013-2016 Collabora, Ltd. + + 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> + + <interface name="wp_viewporter" version="1"> + <description summary="surface cropping and scaling"> + The global interface exposing surface cropping and scaling + capabilities is used to instantiate an interface extension for a + wl_surface object. This extended interface will then allow + cropping and scaling the surface contents, effectively + disconnecting the direct relationship between the buffer and the + surface size. + </description> + + <request name="destroy" type="destructor"> + <description summary="unbind from the cropping and scaling interface"> + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + wp_viewport objects included. + </description> + </request> + + <enum name="error"> + <entry name="viewport_exists" value="0" + summary="the surface already has a viewport object associated"/> + </enum> + + <request name="get_viewport"> + <description summary="extend surface interface for crop and scale"> + Instantiate an interface extension for the given wl_surface to + crop and scale its content. If the given wl_surface already has + a wp_viewport object associated, the viewport_exists + protocol error is raised. + </description> + <arg name="id" type="new_id" interface="wp_viewport" + summary="the new viewport interface id"/> + <arg name="surface" type="object" interface="wl_surface" + summary="the surface"/> + </request> + </interface> + + <interface name="wp_viewport" version="1"> + <description summary="crop and scale interface to a wl_surface"> + An additional interface to a wl_surface object, which allows the + client to specify the cropping and scaling of the surface + contents. + + This interface works with two concepts: the source rectangle (src_x, + src_y, src_width, src_height), and the destination size (dst_width, + dst_height). The contents of the source rectangle are scaled to the + destination size, and content outside the source rectangle is ignored. + This state is double-buffered, and is applied on the next + wl_surface.commit. + + The two parts of crop and scale state are independent: the source + rectangle, and the destination size. Initially both are unset, that + is, no scaling is applied. The whole of the current wl_buffer is + used as the source, and the surface size is as defined in + wl_surface.attach. + + If the destination size is set, it causes the surface size to become + dst_width, dst_height. The source (rectangle) is scaled to exactly + this size. This overrides whatever the attached wl_buffer size is, + unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + has no content and therefore no size. Otherwise, the size is always + at least 1x1 in surface local coordinates. + + If the source rectangle is set, it defines what area of the wl_buffer is + taken as the source. If the source rectangle is set and the destination + size is not set, then src_width and src_height must be integers, and the + surface size becomes the source rectangle size. This results in cropping + without scaling. If src_width or src_height are not integers and + destination size is not set, the bad_size protocol error is raised when + the surface state is applied. + + The coordinate transformations from buffer pixel coordinates up to + the surface-local coordinates happen in the following order: + 1. buffer_transform (wl_surface.set_buffer_transform) + 2. buffer_scale (wl_surface.set_buffer_scale) + 3. crop and scale (wp_viewport.set*) + This means, that the source rectangle coordinates of crop and scale + are given in the coordinates after the buffer transform and scale, + i.e. in the coordinates that would be the surface-local coordinates + if the crop and scale was not applied. + + If src_x or src_y are negative, the bad_value protocol error is raised. + Otherwise, if the source rectangle is partially or completely outside of + the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + when the surface state is applied. A NULL wl_buffer does not raise the + out_of_buffer error. + + If the wl_surface associated with the wp_viewport is destroyed, + all wp_viewport requests except 'destroy' raise the protocol error + no_surface. + + If the wp_viewport object is destroyed, the crop and scale + state is removed from the wl_surface. The change will be applied + on the next wl_surface.commit. + </description> + + <request name="destroy" type="destructor"> + <description summary="remove scaling and cropping from the surface"> + The associated wl_surface's crop and scale state is removed. + The change is applied on the next wl_surface.commit. + </description> + </request> + + <enum name="error"> + <entry name="bad_value" value="0" + summary="negative or zero values in width or height"/> + <entry name="bad_size" value="1" + summary="destination size is not integer"/> + <entry name="out_of_buffer" value="2" + summary="source rectangle extends outside of the content area"/> + <entry name="no_surface" value="3" + summary="the wl_surface was destroyed"/> + </enum> + + <request name="set_source"> + <description summary="set the source rectangle for cropping"> + Set the source rectangle of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If all of x, y, width and height are -1.0, the source rectangle is + unset instead. Any other set of values where width or height are zero + or negative, or x or y are negative, raise the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + </description> + <arg name="x" type="fixed" summary="source rectangle x"/> + <arg name="y" type="fixed" summary="source rectangle y"/> + <arg name="width" type="fixed" summary="source rectangle width"/> + <arg name="height" type="fixed" summary="source rectangle height"/> + </request> + + <request name="set_destination"> + <description summary="set the surface size for scaling"> + Set the destination size of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If width is -1 and height is -1, the destination size is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + </description> + <arg name="width" type="int" summary="surface width"/> + <arg name="height" type="int" summary="surface height"/> + </request> + </interface> + +</protocol> diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index b8c69a105cb..392ef35b53a 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -138,6 +138,11 @@ static void registry_handle_global(void *data, struct wl_registry *registry, wl_seat_add_listener(seat->wl_seat, &seat_listener, NULL); pthread_mutex_unlock(&seat->mutex); } + else if (strcmp(interface, "wp_viewporter") == 0) + { + process_wayland.wp_viewporter = + wl_registry_bind(registry, id, &wp_viewporter_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 01d9704d511..092ff67e824 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -159,6 +159,15 @@ struct wayland_surface *wayland_surface_create(HWND hwnd) } wl_surface_set_user_data(surface->wl_surface, hwnd);
+ if (process_wayland.wp_viewporter) + { + surface->wp_viewport = + wp_viewporter_get_viewport(process_wayland.wp_viewporter, + surface->wl_surface); + } + + surface->window.scale = 1.0; + return surface;
err: @@ -183,6 +192,12 @@ void wayland_surface_destroy(struct wayland_surface *surface)
pthread_mutex_lock(&surface->mutex);
+ if (surface->wp_viewport) + { + wp_viewport_destroy(surface->wp_viewport); + surface->wp_viewport = NULL; + } + if (surface->xdg_toplevel) { xdg_toplevel_destroy(surface->xdg_toplevel); @@ -429,6 +444,25 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface } }
+/********************************************************************** + * wayland_surface_reconfigure_size + * + * Sets the surface size with viewporter + */ +static void wayland_surface_reconfigure_size(struct wayland_surface *surface, + int width, int height) +{ + TRACE("hwnd=%p size=%dx%d\n", surface->hwnd, width, height); + + if (surface->wp_viewport) + { + if (width != 0 && height != 0) + wp_viewport_set_destination(surface->wp_viewport, width, height); + else + wp_viewport_set_destination(surface->wp_viewport, -1, -1); + } +} + /********************************************************************** * wayland_surface_reconfigure * @@ -485,6 +519,7 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) }
wayland_surface_reconfigure_geometry(surface, width, height); + wayland_surface_reconfigure_size(surface, width, height);
return TRUE; } @@ -631,8 +666,8 @@ void wayland_surface_coords_from_window(struct wayland_surface *surface, int window_x, int window_y, int *surface_x, int *surface_y) { - *surface_x = window_x; - *surface_y = window_y; + *surface_x = round(window_x / surface->window.scale); + *surface_y = round(window_y / surface->window.scale); }
/********************************************************************** @@ -644,6 +679,6 @@ void wayland_surface_coords_to_window(struct wayland_surface *surface, double surface_x, double surface_y, int *window_x, int *window_y) { - *window_x = round(surface_x); - *window_y = round(surface_y); + *window_x = round(surface_x * surface->window.scale); + *window_y = round(surface_y * surface->window.scale); } diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 7b883d48184..3c56ff619e2 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -27,6 +27,7 @@
#include <pthread.h> #include <wayland-client.h> +#include "viewporter-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h"
@@ -99,6 +100,7 @@ struct wayland struct wl_compositor *wl_compositor; struct xdg_wm_base *xdg_wm_base; struct wl_shm *wl_shm; + struct wp_viewporter *wp_viewporter; struct wayland_seat seat; struct wayland_pointer pointer; struct wl_list output_list; @@ -146,6 +148,8 @@ struct wayland_window_config { RECT rect; enum wayland_surface_config_state state; + /* The scale (i.e., normalized dpi) the window is rendering at. */ + double scale; };
struct wayland_surface @@ -154,6 +158,7 @@ struct wayland_surface struct wl_surface *wl_surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; + struct wp_viewport *wp_viewport; pthread_mutex_t mutex; struct wayland_surface_config pending, requested, processing, current; struct wayland_shm_buffer *latest_window_buffer; diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 78022670a10..8666a93f6c2 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -175,6 +175,7 @@ static void wayland_win_data_get_config(struct wayland_win_data *data, }
conf->state = window_state; + conf->scale = NtUserGetDpiForWindow(data->hwnd) / 96.0; }
static void wayland_win_data_update_wayland_surface(struct wayland_win_data *data)
From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/winewayland.drv/wayland_pointer.c | 49 ++++++++++++++++++-------- 1 file changed, 35 insertions(+), 14 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index 025793c15d6..0951c0952e7 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -90,6 +90,8 @@ static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, __wine_send_input(hwnd, &input, NULL); }
+static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor); + static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface, wl_fixed_t sx, wl_fixed_t sy) @@ -107,14 +109,11 @@ 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; + pthread_mutex_unlock(&pointer->mutex); + /* 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); + wayland_set_cursor(hwnd, NULL, FALSE);
/* Handle the enter as a motion, to account for cases where the * window first appears beneath the pointer and won't get a separate @@ -418,7 +417,7 @@ static BOOL get_icon_info(HICON handle, ICONINFOEXW *ret) return TRUE; }
-static void wayland_pointer_update_cursor(HCURSOR hcursor) +static void wayland_pointer_update_cursor_buffer(HCURSOR hcursor) { struct wayland_cursor *cursor = &process_wayland.pointer.cursor; ICONINFOEXW info = {0}; @@ -470,6 +469,22 @@ static void wayland_pointer_update_cursor(HCURSOR hcursor) cursor->hotspot_y = cursor->shm_buffer->height / 2; }
+ return; + +clear_cursor: + if (cursor->shm_buffer) + { + wayland_shm_buffer_unref(cursor->shm_buffer); + cursor->shm_buffer = NULL; + } +} + +static void wayland_pointer_update_cursor_surface(void) +{ + struct wayland_cursor *cursor = &process_wayland.pointer.cursor; + + if (!cursor->shm_buffer) goto clear_cursor; + if (!cursor->wl_surface) { cursor->wl_surface = @@ -504,19 +519,15 @@ clear_cursor: } }
-/*********************************************************************** - * WAYLAND_SetCursor - */ -void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) +static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_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); + if (use_hcursor) wayland_pointer_update_cursor_buffer(hcursor); + wayland_pointer_update_cursor_surface(); wl_pointer_set_cursor(pointer->wl_pointer, pointer->enter_serial, pointer->cursor.wl_surface, @@ -526,3 +537,13 @@ void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) } pthread_mutex_unlock(&pointer->mutex); } + +/*********************************************************************** + * WAYLAND_SetCursor + */ +void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) +{ + TRACE("hwnd=%p hcursor=%p\n", hwnd, hcursor); + + wayland_set_cursor(hwnd, hcursor, TRUE); +}
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Take into account the window scale factor and use wp_viewport to instruct the compositor to present cursors with that scale. --- dlls/winewayland.drv/wayland_pointer.c | 48 +++++++++++++++++++++++--- dlls/winewayland.drv/waylanddrv.h | 1 + 2 files changed, 45 insertions(+), 4 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index 0951c0952e7..33fd14fa0c5 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -417,7 +417,7 @@ static BOOL get_icon_info(HICON handle, ICONINFOEXW *ret) return TRUE; }
-static void wayland_pointer_update_cursor_buffer(HCURSOR hcursor) +static void wayland_pointer_update_cursor_buffer(HCURSOR hcursor, double scale) { struct wayland_cursor *cursor = &process_wayland.pointer.cursor; ICONINFOEXW info = {0}; @@ -469,6 +469,9 @@ static void wayland_pointer_update_cursor_buffer(HCURSOR hcursor) cursor->hotspot_y = cursor->shm_buffer->height / 2; }
+ cursor->hotspot_x = round(cursor->hotspot_x / scale); + cursor->hotspot_y = round(cursor->hotspot_y / scale); + return;
clear_cursor: @@ -479,7 +482,7 @@ clear_cursor: } }
-static void wayland_pointer_update_cursor_surface(void) +static void wayland_pointer_update_cursor_surface(double scale) { struct wayland_cursor *cursor = &process_wayland.pointer.cursor;
@@ -496,12 +499,32 @@ static void wayland_pointer_update_cursor_surface(void) } }
+ if (!cursor->wp_viewport && process_wayland.wp_viewporter) + { + cursor->wp_viewport = + wp_viewporter_get_viewport(process_wayland.wp_viewporter, + cursor->wl_surface); + if (!cursor->wp_viewport) + WARN("Failed to create wp_viewport for cursor\n"); + } + /* 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); + /* Setting only the viewport is enough, but some compositors don't + * support wp_viewport for cursor surfaces, so also set the buffer + * scale. Note that setting the viewport destination overrides + * the buffer scale, so it's fine to set both. */ + wl_surface_set_buffer_scale(cursor->wl_surface, round(scale)); + if (cursor->wp_viewport) + { + wp_viewport_set_destination(cursor->wp_viewport, + round(cursor->shm_buffer->width / scale), + round(cursor->shm_buffer->height / scale)); + } wl_surface_commit(cursor->wl_surface);
return; @@ -512,6 +535,11 @@ clear_cursor: wayland_shm_buffer_unref(cursor->shm_buffer); cursor->shm_buffer = NULL; } + if (cursor->wp_viewport) + { + wp_viewport_destroy(cursor->wp_viewport); + cursor->wp_viewport = NULL; + } if (cursor->wl_surface) { wl_surface_destroy(cursor->wl_surface); @@ -522,12 +550,24 @@ clear_cursor: static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) { struct wayland_pointer *pointer = &process_wayland.pointer; + struct wayland_surface *surface; + double scale; + + if ((surface = wayland_surface_lock_hwnd(hwnd))) + { + scale = surface->window.scale; + pthread_mutex_unlock(&surface->mutex); + } + else + { + scale = 1.0; + }
pthread_mutex_lock(&pointer->mutex); if (pointer->focused_hwnd == hwnd) { - if (use_hcursor) wayland_pointer_update_cursor_buffer(hcursor); - wayland_pointer_update_cursor_surface(); + if (use_hcursor) wayland_pointer_update_cursor_buffer(hcursor, scale); + wayland_pointer_update_cursor_surface(scale); wl_pointer_set_cursor(pointer->wl_pointer, pointer->enter_serial, pointer->cursor.wl_surface, diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 3c56ff619e2..bdb265fd94c 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -70,6 +70,7 @@ struct wayland_cursor { struct wayland_shm_buffer *shm_buffer; struct wl_surface *wl_surface; + struct wp_viewport *wp_viewport; int hotspot_x, hotspot_y; };
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Due to the asynchronous nature of Wayland events, and the design of the xdg_toplevel protocol, an xdg configure event may be a reaction to a request in the application's configuration past, and the size hint may be out of date. For example:
1. The client commits a 100x100 buffer to a surface. 2.1 The compositor sends xdg configure(100x100, state=activated). 2.2 In the meantime, the client resizes and commits a 50x50 buffer. 3. The client receives the event from (2.1). If we respect the size hint, we will resize back to 100x100, although this was neither the client's nor the compositor's intention.
To mitigate this we ignore size hints for states that do not require strict size adherence. --- dlls/winewayland.drv/window.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 8666a93f6c2..1423164834e 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -412,9 +412,18 @@ static void wayland_configure_window(HWND hwnd) surface->processing = surface->requested; memset(&surface->requested, 0, sizeof(surface->requested));
- width = surface->processing.width; - height = surface->processing.height; state = surface->processing.state; + /* Ignore size hints if we don't have a state that requires strict + * size adherence, in order to avoid spurious resizes. */ + if (state) + { + width = surface->processing.width; + height = surface->processing.height; + } + else + { + width = height = 0; + }
if ((state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && !surface->resizing) {
On Fri Nov 10 09:46:41 2023 +0000, Alexandre Julliard wrote:
This is not correct for DPI-aware apps. It should be something like `window_dpi / system_dpi` (or better, `window_dpi / monitor_dpi`, but we don't quite support per-monitor DPI yet).
Under Wayland the compositor is responsible for applying the monitor scale, so we don't need to factor it in our client side calculation. We just need to communicate the normalized dpi (`scale` in Wayland parlance) at which the contents are rendered. This is done implicitly by setting the surface-local size (e.g., through `wp_viewport`) accordingly. The compositor then uses this information and the scale factor of the output the surface is on, to present the contents at the proper physical size (and thus autoscale on dpi mismatches).
Here is a table describing how this works (also see images in description):
| window size | window_dpi | surf_scale (= window_dpi / 96.0) | surface-local size (= window_size / surf_scale) | compositor physical (= surf_size _* monitor_scale = window_size_ * monitor_scale / surf_scale), monitor scale 1x | compositor physical, monitor scale 2x | |-------------|------------|----------------------------------|-------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|---------------------------------------| | 512 | 96 | 1 | 512 | 512 | 1024 | | 512 | 192 | 2 | 256 | 256 | 512 |
I have added a comment to the `scale` field, but perhaps also using a different name, e.g., `normalized_dpi` or `surface_scale` (open to suggestions), would help with clarity?
Do you think we want a different scaling behavior from what's implemented in this MR?
On Fri Nov 10 16:37:58 2023 +0000, Alexandre Julliard wrote:
The wayland surface coordinates are inherently expressed as real
numbers, so `wayland_surface_coords_from_wine` would normally be `int -> double`. I am fine dropping the `rounded` if you prefer, and if we need the `int -> double` variant I can do a `wayland_surface_coords_from_wine_unrounded` or similar. Does that sound OK? Sure.
For `wayland_surface` functions, the Wayland surface-local system is
the natural system to express coordinates in, hence I chose `x` (i.e., less context since it's implied by the object we are working with) and `wine_x`. I am fine inverting the names along the lines of `wayland_x` and `x` if you feel strongly about this. It's better to avoid naming things `wine` or `wayland`, because in the driver basically everything is both Wine and Wayland stuff. I'd suggest terms like logical/physical (cf. `NtUserPerMonitorDPIPhysicalToLogicalPoint`), or surface/window.
Ack. Changed functions to `wayland_surface_coords_(to|from)_window` with arguments `window_*` and `surface_*`.
v3: - Changed function names and arguments: `wayland_surface_coords_(to|from)_window` - Pass physical coordinates to mouse events we send with `__wine_send_input`.
@julliard Thanks for the feedback and the recent dpi related fix at 7710e37c479da9e78a03d14dcebaa001163e147c.
I have proposed a couple of other dpi related fixes at !4387.
On Tue Nov 14 11:21:00 2023 +0000, Alexandros Frantzis wrote:
Under Wayland the compositor is responsible for applying the monitor scale, so we don't need to factor it in our client side calculation. We just need to communicate the normalized dpi (`scale` in Wayland parlance) at which the contents are rendered. This is done implicitly by setting the surface-local size (e.g., through `wp_viewport`) accordingly. The compositor then uses this information and the scale factor of the output the surface is on, to present the contents at the proper physical size (and thus autoscale on dpi mismatches). Here is a table describing how this works (also see images in description): | window size | window_dpi | surf_scale (= window_dpi / 96.0) | surface-local size (= window_size / surf_scale) | compositor physical (= surf_size _* monitor_scale = window_size_ * monitor_scale / surf_scale), monitor scale 1x | compositor physical, monitor scale 2x | |-------------|------------|----------------------------------|-------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|---------------------------------------| | 512 | 96 | 1 | 512 | 512 | 1024 | | 512 | 192 | 2 | 256 | 256 | 512 | I have added a comment to the `scale` field, but perhaps also using a different name, e.g., `normalized_dpi` or `surface_scale` (open to suggestions), would help with clarity? Do you think we want a different scaling behavior from what's implemented in this MR?
A proper DPI-aware app should never be scaled, because it should draw at the right DPI for the monitor. It seemed to me that this wasn't true with your MR, but maybe I misunderstood how this works on the Wayland side.
On Tue Nov 14 11:58:46 2023 +0000, Alexandre Julliard wrote:
A proper DPI-aware app should never be scaled, because it should draw at the right DPI for the monitor. It seemed to me that this wasn't true with your MR, but maybe I misunderstood how this works on the Wayland side.
In this MR, if the application is using the same DPI/scale as the monitor it will not be scaled. For example, in the table above, 512@96dpi will remain 512 physical when presented on 1x scaled monitor (example 3 in the description), and the same for 512@192dpi on a 2x scaled monitor (example 2 in the description).
Without this MR the compositor will scale all windows, even if their dpi matches the monitor (so, e.g., 512@192dpi would become 1024 physical on a 2x scaled monitor, instead of remaining at 512 physical).
Of course, at the moment we don't have the infrastructure to report per-monitor dpi values to applications (or set the system dpi from the actually monitor info to properly support SYSTEM_AWARE), so the user needs to manually set the proper system dpi through, e.g., `winecfg`, or in the app itself if there is such support, to get the desired result and avoid being scaled.
On Tue Nov 14 13:24:51 2023 +0000, Alexandros Frantzis wrote:
In this MR, if the application is using the same DPI/scale as the monitor it will not be scaled. For example, in the table above, 512@96dpi will remain 512 physical when presented on 1x scaled monitor (example 3 in the description), and the same for 512@192dpi on a 2x scaled monitor (example 2 in the description). Without this MR the compositor will scale all windows on a scaled output, even if their dpi matches the monitor, because we never inform it about the existing surface scale (so, e.g., 512@192dpi would become 1024 physical on a 2x scaled monitor, instead of remaining at 512 physical). Of course, at the moment we don't have the infrastructure to report per-monitor dpi values to applications (or set the system dpi from the actually monitor info to properly support SYSTEM_AWARE), so the user needs to manually set the proper system dpi through, e.g., `winecfg`, or in the app itself if there is such support, to get the desired result and avoid being scaled.
OK thanks, that's how it should be then. I probably screwed something up in my testing.