From b0dd93e5780322acb2e74551b828c23e0a7301c7 Mon Sep 17 00:00:00 2001 From: Max Lee Date: Wed, 17 Apr 2024 17:35:32 +1000 Subject: [PATCH] vm_tools: sommelier: improve randr emulation behaviour 1. Fetch virtual screen info for the window to find the appropriate emulation rect. 2. Send ConfigureWindow event after emulation. Some games require manual triggering (ex. to re-enter fullscreen, etc). 3. Some games set max dimensions as INT_MAX. Take account of this in sl_window_is_containerized and sl_internal_toplevel_configure_size_containerized. 4. When emulation is active, override viewport destination size when window is non-fullscreen in compositor (so that window can be resized like non-emulated mode). 5. Make toplevel_configure_position always use emulated position. BUG=b:328699937,b:331688838 TEST=Test games: Stardew Valley, CS2 Change-Id: I79b945e15348ecdad61f63cac6b1192a4117ddf2 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/5460650 Commit-Queue: Max Lee Reviewed-by: Chloe Pelling Tested-by: Max Lee --- sommelier-output.cc | 1 + sommelier-window.cc | 80 ++++++++++++++++++++++++++++++++++++++------- sommelier-window.h | 8 +++++ sommelier.cc | 59 ++++++++++++++++++++++++++------- sommelier.h | 5 +++ 5 files changed, 131 insertions(+), 22 deletions(-) diff --git a/sommelier-output.cc b/sommelier-output.cc index 87d3b93..305afd2 100644 --- a/sommelier-output.cc +++ b/sommelier-output.cc @@ -611,6 +611,7 @@ static void sl_bind_host_output(struct wl_client* client, host->x = 0; host->y = 0; host->virt_x = 0; + host->virt_y = 0; host->logical_x = 0; host->logical_y = 0; host->physical_width = 0; diff --git a/sommelier-window.cc b/sommelier-window.cc index 968fd50..6522dd1 100644 --- a/sommelier-window.cc +++ b/sommelier-window.cc @@ -5,6 +5,7 @@ #include "sommelier-window.h" // NOLINT(build/include_directory) #include +#include #include #include #include @@ -413,17 +414,14 @@ void sl_internal_toplevel_configure_size_containerized(struct sl_window* window, int32_t width_in_pixels, int32_t height_in_pixels, int& mut_config_idx) { - // TODO(b/328699937): Re-visit the correctness of this function when XWayland - // is emulating screen size for the window. - // Forward resizes to the client if requested size fits within the min&max // dimensions. Note that min&max dimensions are strictly set by the X11 client // only (and forwarded to Exo immediately). If min&max dimensions are not set, // we will set the size of the window to screen size (see below). - if (window->max_width >= width_in_pixels && + if ((window->max_width >= width_in_pixels || !window->max_width) && window->min_width <= width_in_pixels && - window->max_height >= height_in_pixels && - window->max_height <= height_in_pixels) { + (window->max_height >= height_in_pixels || !window->max_height) && + window->min_height <= height_in_pixels && !window->use_emulated_rects) { // TODO(b/330639760): Consider unset aspect ratio every frame in // surface_commit. zaura_surface_set_aspect_ratio(window->aura_surface, -1, -1); @@ -453,7 +451,14 @@ void sl_internal_toplevel_configure_size_containerized(struct sl_window* window, window->max_width ? window->max_width : window->min_width; int safe_window_height = window->max_height ? window->max_height : window->min_height; - if (!safe_window_width || !safe_window_height || window->fullscreen) { + if (window->use_emulated_rects) { + // If screen size emulation is set by XWayland, set the window size to the + // emulated size and adjust the viewport size as Exo had requested it to be. + safe_window_width = window->emulated_width; + safe_window_height = window->emulated_height; + } else if (!safe_window_width || !safe_window_height || + window->max_width > output->width || + window->max_height > output->height || window->fullscreen) { safe_window_width = output->width; safe_window_height = output->height; } @@ -465,7 +470,23 @@ void sl_internal_toplevel_configure_size_containerized(struct sl_window* window, window->next_config.values[mut_config_idx++] = safe_window_height; window->next_config.values[mut_config_idx++] = 0; - // Use viewport to resize surface as per Exo request. + if (window->use_emulated_rects && window->compositor_fullscreen) { + // If we are using emulated rects and the window is fullscreen in + // compositor, we only have to report the emulated size (done above). Aspect + // ratio changes are moot (since compositor fullscreen). We don't have to + // set viewport overrides since we can fully rely on viewport setup by + // Xwayland. + // If the window is windowed in compositor, then we have to do additional + // viewport operations to make the window fit into the size that compositor + // wants it to be, without disrupting the emulation. This has identical code + // path as non-emulated mode, except pointer movement scaling (see below). + sl_window_reset_viewport(window); + return; + } + + // Use viewport to resize surface as per Exo request if we are not using + // emulated rects, or window is not fullscreen in Exo (and using emulated + // rects). window->viewport_override = true; int32_t safe_window_width_in_wl = safe_window_width; @@ -516,8 +537,18 @@ void sl_internal_toplevel_configure_size_containerized(struct sl_window* window, safe_window_width; window->viewport_width = host_width; } - window->viewport_pointer_scale = - static_cast(safe_window_width_in_wl) / window->viewport_width; + + if (window->use_emulated_rects) { + // Pointer scaling is being done in XWayland as well, assuming the viewport + // is the size of the screen. Map the movement from viewport to logical + // width of the screen (which XWayland is expecting). + window->viewport_pointer_scale = + static_cast(output->logical_width) / window->viewport_width; + } else { + // Map movement from viewport to the window's space. + window->viewport_pointer_scale = + static_cast(safe_window_width_in_wl) / window->viewport_width; + } if (window->xdg_toplevel) { // Override X11 client-defined min and max size to a value relative to @@ -601,6 +632,10 @@ void sl_internal_toplevel_configure_size(struct sl_window* window, window->next_config.values[mut_config_idx++] = window->width; window->next_config.values[mut_config_idx++] = window->height; window->next_config.values[mut_config_idx++] = 0; + } else if (window->use_emulated_rects) { + window->next_config.values[mut_config_idx++] = window->emulated_width; + window->next_config.values[mut_config_idx++] = window->emulated_height; + window->next_config.values[mut_config_idx++] = 0; } else { window->next_config.values[mut_config_idx++] = width_in_pixels; window->next_config.values[mut_config_idx++] = height_in_pixels; @@ -614,7 +649,14 @@ void sl_internal_toplevel_configure_position(struct sl_window* window, int32_t width_in_pixels, int32_t height_in_pixels, int& mut_config_idx) { - if (x != kUnspecifiedCoord && y != kUnspecifiedCoord) { + if (window->use_emulated_rects) { + // Ignore requests from the compositor and set the coordinates as emulation + // requested. + window->next_config.mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; + window->next_config.values[mut_config_idx++] = window->emulated_x; + window->next_config.values[mut_config_idx++] = window->emulated_y; + + } else if (x != kUnspecifiedCoord && y != kUnspecifiedCoord) { // Convert to virtual coordinates int32_t guest_x = x; int32_t guest_y = y; @@ -1274,3 +1316,19 @@ void sl_window_get_width_height(struct sl_window* window, } } } + +bool sl_window_get_output_virt_position(struct sl_window* window, + uint32_t& mut_x, + uint32_t& mut_y) { + for (auto output : window->ctx->host_outputs) { + if (window->x >= output->virt_x && + window->x < output->virt_x + output->virt_width && + window->y >= output->virt_y && + window->y < output->virt_y + output->virt_height) { + mut_x = output->virt_x; + mut_y = output->virt_y; + return true; + } + } + return false; +} diff --git a/sommelier-window.h b/sommelier-window.h index 16f6116..b16dc45 100644 --- a/sommelier-window.h +++ b/sommelier-window.h @@ -67,6 +67,8 @@ struct sl_window { bool use_emulated_rects = false; int emulated_width = 0; int emulated_height = 0; + int emulated_x = 0; + int emulated_y = 0; int border_width = 0; int depth = 0; @@ -333,4 +335,10 @@ void sl_window_update_should_be_containerized_from_pid( bool sl_window_is_containerized(struct sl_window* window); void sl_window_reset_viewport(struct sl_window* window); +// Get the virtual screen position that the window is in. Returns true if mut_x +// and mut_y were updated with the found screen, false otherwise. +bool sl_window_get_output_virt_position(struct sl_window* window, + uint32_t& mut_x, + uint32_t& mut_y); + #endif // VM_TOOLS_SOMMELIER_SOMMELIER_WINDOW_H_ diff --git a/sommelier.cc b/sommelier.cc index be9f068..a08e19c 100644 --- a/sommelier.cc +++ b/sommelier.cc @@ -1459,8 +1459,16 @@ void sl_handle_map_request(struct sl_context* ctx, window->min_height = size_hints.min_height; } if (window->size_flags & P_MAX_SIZE) { - window->max_width = size_hints.max_width; - window->max_height = size_hints.max_height; + if (size_hints.max_width < INT_MAX) { + window->max_width = size_hints.max_width; + } else { + window->max_width = 0; + } + if (size_hints.max_height < INT_MAX) { + window->max_height = size_hints.max_height; + } else { + window->max_height = 0; + } } window->border_width = 0; @@ -1900,6 +1908,8 @@ void sl_handle_client_message(struct sl_context* ctx, if (window && window->xdg_toplevel) { xdg_toplevel_set_minimized(window->xdg_toplevel); #ifdef QUIRKS_SUPPORT + // TODO(b/333819234): Migrate FEATURE_BLACK_SCREEN_FIX to quirk condition + // `runtime: Proton`. if (window->ctx->quirks.IsEnabled(window, quirks::FEATURE_BLACK_SCREEN_FIX)) { // Workaround for some borealis apps showing a black screen after losing @@ -2311,8 +2321,16 @@ void sl_handle_property_notify(struct sl_context* ctx, window->min_height = size_hints.min_height; } if (window->size_flags & P_MAX_SIZE) { - window->max_width = size_hints.max_width; - window->max_height = size_hints.max_height; + if (size_hints.max_width < INT_MAX) { + window->max_width = size_hints.max_width; + } else { + window->max_width = 0; + } + if (size_hints.max_height < INT_MAX) { + window->max_height = size_hints.max_height; + } else { + window->max_height = 0; + } } } @@ -2490,23 +2508,42 @@ void sl_handle_property_notify(struct sl_context* ctx, return; } + // We need to select the appropriate emulated screen based on the screen the + // window is in. + uint32_t target_x = 0; + uint32_t target_y = 0; + if (!sl_window_get_output_virt_position(window, target_x, target_y)) { + window->use_emulated_rects = false; + free(reply); + return; + } + for (uint32_t i = 0; i < reply->value_len; i += 4) { // Assume the fullscreen window position is the screen's position. // XWayland emulation must match this x/y coordinates. - if (xywh[i] == window->x && xywh[i + 1] == window->y) { - // NOTE: XWayland will send a wp_viewport.set_source request based on - // ConfigureNotify request. ConfigureNotify with emulated values will be - // sent in the next sl_host_surface_commit(). - window->use_emulated_rects = true; + if (xywh[i] == target_x && xywh[i + 1] == target_y) { + window->emulated_x = xywh[i]; + window->emulated_y = xywh[i + 1]; window->emulated_width = xywh[i + 2]; window->emulated_height = xywh[i + 3]; + window->use_emulated_rects = true; + // XWayland will send a wp_viewport.set_source and destination request + // (plus, do other busy work such as setting pointer scaling) based on + // window property set. Configure window manually here to trigger + // necessary changes since some games do not do anything further than + // adding the attributes. + xcb()->configure_window(window->ctx->connection, window->id, + XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + &xywh[i]); free(reply); return; } } - fprintf(stderr, "failed to find screen with position %u,%u\n", window->x, - window->y); + fprintf(stderr, "failed to find screen with position %u,%u\n", target_x, + target_y); window->use_emulated_rects = false; free(reply); diff --git a/sommelier.h b/sommelier.h index cc6c503..2af5b90 100644 --- a/sommelier.h +++ b/sommelier.h @@ -323,6 +323,11 @@ struct sl_host_output { double xdg_scale_x; double xdg_scale_y; int virt_x; + // Note that all virtual screens are repositioned laterally in a row + // (regardless of host's positioning), which means y coordinate is always 0. + // However, in case future implementation changes, utilize this attribute + // whenever we reference virtual y coordinates. + int virt_y; }; MAP_STRUCTS(wl_output, sl_host_output);