From d66e1323cc42e8a22edc61835a405e68a6886189 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 1 Oct 2026 23:00:18 -0700 Subject: [PATCH] fix(linux_window): separate the surface scale from the stage factor in mutter's PHYSICAL layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mutter has two monitor layout modes. In LOGICAL (fractional scaling; mutter 50's default) the stage is logical px and one number, the monitor scale, is both the surface scale and the stage factor. In PHYSICAL (Ubuntu 24.04 / GNOME 46 at an integer scale, out of the box) the stage already is device px: at 200 % the stage factor is 1 while clients still draw at 2. The Wayland window treated them as one number, in two places: - Sizing: `mode / xdg_output.logical_size` (the stage factor, 1 in PHYSICAL) was used as the surface scale, so request_initial_rect() declared a 720x400 rect as 1440x800 and held it, and the deferred fullscreen's first configure guess was twice the panel. - Positions: the window-geometry extension's rects were converted by its monitor.scale (2 in PHYSICAL), so the initial rect's landing check read (600, 400) 2880x1600 for a window at (300, 200) 720x400, and the drag lattice map used the wrong factor. New dxr_wl_scale.h keeps the two apart: dxr_wl_surface_scale_estimate() (the integer wl_output.scale when the stage factor is 1, else the stage factor) sizes; dxr_wl_stage_factor() converts published rects, preferring the extension's own answer (monitor.device_scale / layout_mode, extension version 11), then this client's output at the monitor's origin (mode / logical size, right in both modes), then the monitor scale. The runtime's new u_wayland_layout.h is vendored verbatim in xrt_aux/util. Measured on a private headless mutter 50.1 (3840x2160 virtual monitor, gdctl --layout-mode physical --scale 2), linux_window_test with DXR_LW_TEST_WAYLAND=1: before: declared 1440x800, wanted 720x400; "the rect spans monitors of different scales" at (600, 400) 2880x1600 after: initial rect: LANDED — (300, 200) 720x400 device px, off by (+0, +0); same with the extension at version 10 (the output fallback) and unchanged in LOGICAL at 200 %. (The panel-fullscreen check fails under mutter in both modes, before and after; it is written against weston.) Tests: tests/linux_window_scale_test.cpp (new). Co-Authored-By: Claude Opus 5.5 (1M context) --- common/linux/dxr_linux_window.cpp | 47 ++++++- common/linux/dxr_linux_window.h | 8 ++ common/linux/dxr_wl_placement.cpp | 21 +++ common/linux/dxr_wl_placement.h | 9 ++ common/linux/dxr_wl_scale.h | 82 ++++++++++++ common/linux/xrt_aux/util/u_wayland_layout.h | 133 +++++++++++++++++++ tests/CMakeLists.txt | 11 ++ tests/linux_window_scale_test.cpp | 74 +++++++++++ 8 files changed, 378 insertions(+), 7 deletions(-) create mode 100644 common/linux/dxr_wl_scale.h create mode 100644 common/linux/xrt_aux/util/u_wayland_layout.h create mode 100644 tests/linux_window_scale_test.cpp diff --git a/common/linux/dxr_linux_window.cpp b/common/linux/dxr_linux_window.cpp index f6ab3eb..5b12776 100644 --- a/common/linux/dxr_linux_window.cpp +++ b/common/linux/dxr_linux_window.cpp @@ -35,6 +35,7 @@ // app's output match and the runtime's window-rect conversion cannot drift // apart (#1595/#1596). Header-only; see src/xrt/auxiliary/util/. #include "util/u_wayland_geom.h" +#include "dxr_wl_scale.h" #include "dxr_wl_chrome.h" // DxrWlPointerEvent (content pointer forwarding) #include // raw evdev keycodes (the no-xkbcommon fallback table) @@ -2994,7 +2995,9 @@ DxrLinuxWindow::wl_lattice_prepare_map(bool quiet) } m_wl_lattice_map.rel0_x = g.buffer[0] - g.monitor[0]; m_wl_lattice_map.rel0_y = g.buffer[1] - g.monitor[1]; - m_wl_lattice_map.scale = g.monitor_scale; + // Device px per STAGE px — the monitor scale only in mutter's + // LOGICAL layout mode (1 in PHYSICAL). + m_wl_lattice_map.scale = wl_geometry_stage_factor(g); m_wl_lattice_start_frame_x = g.frame[0]; m_wl_lattice_start_frame_y = g.frame[1]; m_wl_lattice_explicit = true; @@ -3156,6 +3159,26 @@ wl_logical_offset_for(int32_t device_offset, double scale) } } // namespace +#ifdef DXR_APP_HAVE_WL_CHROME +double +DxrLinuxWindow::wl_geometry_stage_factor(const DxrWlPlacement::OwnGeometry &g) const +{ + // This client's own view of the monitor: the output at its stage origin. + // mode / xdg_output.logical_size is the stage factor in both of mutter's + // layout modes — the fallback for an extension older than version 11. + double output_stage = 0.0; + for (const auto &out : m_wl_outputs) { + if (out.have_logical_size && out.logical_w > 0 && out.mode_w > 0 && out.logical_x == g.monitor[0] && + out.logical_y == g.monitor[1]) { + output_stage = (double)out.mode_w / (double)out.logical_w; + break; + } + } + return dxr_wl_stage_factor(g.monitor_scale, g.device_scale, (enum u_wl_layout_mode)g.layout_mode, + output_stage); +} +#endif + const DxrLinuxWindow::WlOutput * DxrLinuxWindow::wl_rect_target_output() const { @@ -3299,7 +3322,8 @@ DxrLinuxWindow::wl_rect_tick() if (finish) { // Report in the requester's space: the monitor the window is on now, // through the runtime's own conversion. - const double sc = g.monitor_scale > 0.0 ? g.monitor_scale : 1.0; + const double stage = wl_geometry_stage_factor(g); + const double sc = stage > 0.0 ? stage : 1.0; const int32_t px = u_wl_logical_to_px(g.monitor[0], sc) + u_wl_logical_to_px(g.buffer[0] - g.monitor[0], sc); const int32_t py = @@ -3574,8 +3598,12 @@ DxrLinuxWindow::create_wayland(const DxrLinuxWindowDesc &desc) m_wl_fs_on_panel = true; for (const auto &out : m_wl_outputs) { if (out.output == m_wl_panel_output && out.have_logical_size) { - m_wl_config_w = out.logical_w; - m_wl_config_h = out.logical_h; + // SURFACE units: the mode over the surface scale. That is + // the logical size in mutter's LOGICAL layout, and half of + // it at 200 % in PHYSICAL (dxr_wl_scale.h). + const double s = dxr_wl_surface_scale_estimate(out.mode_w, out.logical_w, out.int_scale); + m_wl_config_w = (int32_t)((double)out.mode_w / s + 0.5); + m_wl_config_h = (int32_t)((double)out.mode_h / s + 0.5); } } DXRW_INFO("Wayland: fullscreen on %s requested — DEFERRED until the surface is mapped (mutter " @@ -3597,12 +3625,16 @@ DxrLinuxWindow::create_wayland(const DxrLinuxWindowDesc &desc) // scale is only known once it is on an output, so start from the 3D // panel's scale (where the app aims to be) and correct on the first // wp_fractional_scale_v1.preferred_scale (s_frac_preferred_scale). + // Two factors (dxr_wl_scale.h): `est` sizes the surface (device px + // per SURFACE unit), `rect_scale` places it (device px per STAGE px). + // One number in mutter's LOGICAL layout, 2 vs 1 at 200 % in PHYSICAL. double est = 1.0; for (const auto &out : m_wl_outputs) { if (out.output == m_wl_panel_output && out.have_logical_size && out.logical_w > 0) { - est = (double)out.mode_w / (double)out.logical_w; + est = dxr_wl_surface_scale_estimate(out.mode_w, out.logical_w, out.int_scale); } } + double stage_scale = 0.0; // request_initial_rect(): the window is going to the output the rect // covers most, so size it at THAT output's scale, and hold the size // (m_wl_size_from_desc off) — the first preferred_scale comes from @@ -3610,7 +3642,8 @@ DxrLinuxWindow::create_wayland(const DxrLinuxWindowDesc &desc) // may be another output at another scale. rect_out = m_rect.active ? wl_rect_target_output() : nullptr; if (rect_out != nullptr) { - est = (double)rect_out->mode_w / (double)rect_out->logical_w; + est = dxr_wl_surface_scale_estimate(rect_out->mode_w, rect_out->logical_w, rect_out->int_scale); + stage_scale = (double)rect_out->mode_w / (double)rect_out->logical_w; } if (m_wl_frac_manager == nullptr) { // No preferred scale will ever arrive, so the declared buffer is the @@ -3620,7 +3653,7 @@ DxrLinuxWindow::create_wayland(const DxrLinuxWindowDesc &desc) m_wl_config_w = (int32_t)((double)desc.width / est + 0.5); m_wl_config_h = (int32_t)((double)desc.height / est + 0.5); m_wl_size_from_desc = m_wl_frac_manager != nullptr && rect_out == nullptr; - rect_scale = est; + rect_scale = stage_scale; m_wl_windowed_w = m_wl_config_w; m_wl_windowed_h = m_wl_config_h; DXRW_INFO("Wayland: requested %ux%u device px -> %dx%d logical at an estimated scale %.4f " diff --git a/common/linux/dxr_linux_window.h b/common/linux/dxr_linux_window.h index d2f12e0..c3e1706 100644 --- a/common/linux/dxr_linux_window.h +++ b/common/linux/dxr_linux_window.h @@ -1228,6 +1228,14 @@ class DxrLinuxWindow double press_to_table_ms = -1.0; //!< press to the first table the compositor accepted } m_wl_drag_stats; /*! @} */ + + /*! + * Device px per STAGE px for the monitor in a window-geometry snapshot: + * the factor its rects convert by. The monitor scale only in mutter's + * LOGICAL layout mode; 1 in PHYSICAL (dxr_wl_scale.h). + */ + double + wl_geometry_stage_factor(const DxrWlPlacement::OwnGeometry &g) const; #endif // DXR_APP_HAVE_WL_CHROME /*! diff --git a/common/linux/dxr_wl_placement.cpp b/common/linux/dxr_wl_placement.cpp index a687eb7..7c4e785 100644 --- a/common/linux/dxr_wl_placement.cpp +++ b/common/linux/dxr_wl_placement.cpp @@ -7,6 +7,8 @@ #include "dxr_wl_placement.h" +#include "util/u_wayland_layout.h" + #include #include #include @@ -378,6 +380,25 @@ DxrWlPlacement::get_own_geometry(OwnGeometry *out) json_num(mon, end, "\"y\":", &my) && json_num(mon, end, "\"w\":", &mw) && json_num(mon, end, "\"h\":", &mh) && json_num(mon, end, "\"scale\":", &out->monitor_scale) && out->monitor_scale > 0.0; + // Extension v11: device px per stage px, inside the monitor + // object (after "scale"; `"scale":` above never matches the + // `"device_scale":` key, whose quote precedes "device"). + out->device_scale = 0.0; + if (!json_num(mon, end, "\"device_scale\":", &out->device_scale) || !(out->device_scale > 0.0)) { + out->device_scale = 0.0; + } + // Top level, before the window list. + const char *windows = strstr(json, "\"windows\":"); + const char *lm = strstr(json, "\"layout_mode\":\""); + out->layout_mode = 0; + if (lm != nullptr && (windows == nullptr || lm < windows)) { + lm += strlen("\"layout_mode\":\""); + if (strncmp(lm, "logical\"", 8) == 0) { + out->layout_mode = U_WL_LAYOUT_MODE_LOGICAL; + } else if (strncmp(lm, "physical\"", 9) == 0) { + out->layout_mode = U_WL_LAYOUT_MODE_PHYSICAL; + } + } out->monitor[0] = (int32_t)mx; out->monitor[1] = (int32_t)my; out->monitor[2] = (int32_t)mw; diff --git a/common/linux/dxr_wl_placement.h b/common/linux/dxr_wl_placement.h index 6859702..0bf054e 100644 --- a/common/linux/dxr_wl_placement.h +++ b/common/linux/dxr_wl_placement.h @@ -130,7 +130,16 @@ class DxrWlPlacement int32_t frame[4] = {0, 0, 0, 0}; int32_t buffer[4] = {0, 0, 0, 0}; int32_t monitor[4] = {0, 0, 0, 0}; + //! Mutter's monitor scale (`monitor.scale`). NOT the factor the + //! rects above convert by: in mutter's PHYSICAL layout mode the stage + //! already is device px. Convert with dxr_wl_stage_factor(). double monitor_scale = 0.0; + //! Device px per stage px (`monitor.device_scale`, extension version + //! 11); 0 when the publisher does not say. + double device_scale = 0.0; + //! `layout_mode` (extension version 11): a u_wl_layout_mode value, + //! 0 (unknown) when the publisher does not say. + int layout_mode = 0; //! The runtime runs MOVE SYNC for this process (extension version 9, //! runtime#1748): every frame is shown where it was woven for, so a //! drag needs no lattice table. False when absent (older publisher). diff --git a/common/linux/dxr_wl_scale.h b/common/linux/dxr_wl_scale.h new file mode 100644 index 0000000..9af829b --- /dev/null +++ b/common/linux/dxr_wl_scale.h @@ -0,0 +1,82 @@ +// Copyright 2026, The DisplayXR Project +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief The two Wayland scale factors a GNOME window needs, in BOTH of + * mutter's layout modes. Pure: unit tested without a compositor + * (tests/linux_window_scale_test.cpp). + * + * A Wayland window on GNOME deals in three spaces: DEVICE px (the buffer, the + * panel), SURFACE units (the configure size, wp_viewport destinations) and + * mutter's STAGE (window positions, the window-geometry extension's rects, + * xdg_output's logical position and size). Two factors join them: + * + * - device px per SURFACE unit: what a buffer is declared at. The + * compositor's preferred scale (wp_fractional_scale_v1) once it arrives, + * estimated before then by @ref dxr_wl_surface_scale_estimate. + * - device px per STAGE px: what a position or a published rect converts + * by, @ref dxr_wl_stage_factor. + * + * In mutter's LOGICAL layout mode (fractional scaling; mutter 50's default) + * the two are the same number, the monitor scale. In its PHYSICAL mode (Ubuntu + * 24.04 / GNOME 46 at an integer scale, out of the box) the stage IS device px, + * so the stage factor is 1 while clients still draw at the integer scale. + * Treating them as one number there sized a 720x400 initial rect at 1440x800, + * and read a window on a 3840x2160 monitor at 200 % as twice its size. + * See the runtime's u_wayland_layout.h (vendored in xrt_aux/util). + */ +#pragma once + +#include "util/u_wayland_layout.h" + +#include +#include + +/*! + * Device px per SURFACE unit on an output, before the surface's preferred + * scale is known. + * + * `mode / xdg_output.logical_size` is the STAGE factor: mutter reports the + * stage size as the logical size in both layout modes. In LOGICAL that is also + * the surface scale. In PHYSICAL it is 1 while wl_output.scale is the (always + * integer) scale clients draw at, which is the signature used here: a stage + * factor of 1 next to an integer scale above 1. (In LOGICAL a scaled output + * never has a stage factor of 1.) + * + * @param int_scale wl_output.scale for that output (1 when unknown). + * @return the estimate; 1.0 when nothing is known. + */ +inline double +dxr_wl_surface_scale_estimate(int32_t mode_w, int32_t logical_w, int32_t int_scale) +{ + if (mode_w <= 0 || logical_w <= 0) { + return 1.0; + } + const double stage = (double)mode_w / (double)logical_w; + if (std::fabs(stage - 1.0) < 1e-3 && int_scale > 1) { + return (double)int_scale; // PHYSICAL layout + } + return stage; +} + +/*! + * Device px per STAGE px for a monitor the window-geometry extension + * published: the factor its `frame` / `buffer` / `monitor` rects convert by. + * + * In order: the extension's own answer (`monitor.device_scale`, or its + * `layout_mode`; extension version 11), then this client's own output at the + * monitor's position (`mode / xdg_output.logical_size`, @p output_stage_factor, + * 0 when there is none), then the monitor scale (what every consumer used + * before version 11 — right in LOGICAL only). + */ +inline double +dxr_wl_stage_factor(double monitor_scale, double device_scale, enum u_wl_layout_mode layout, double output_stage_factor) +{ + if (device_scale > 0.0 || layout != U_WL_LAYOUT_MODE_UNKNOWN) { + return u_wl_stage_to_device_scale(monitor_scale, device_scale, layout); + } + if (output_stage_factor > 0.0) { + return output_stage_factor; + } + return u_wl_stage_to_device_scale(monitor_scale, 0.0, U_WL_LAYOUT_MODE_UNKNOWN); +} diff --git a/common/linux/xrt_aux/util/u_wayland_layout.h b/common/linux/xrt_aux/util/u_wayland_layout.h new file mode 100644 index 0000000..1293e02 --- /dev/null +++ b/common/linux/xrt_aux/util/u_wayland_layout.h @@ -0,0 +1,133 @@ +// Copyright 2026, The DisplayXR Project +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Which space Mutter's stage coordinates are in: the factor that turns + * them into device pixels. + * @ingroup aux_util + * + * ## Mutter's two layout modes: the monitor scale is not always the factor + * + * @ref u_wayland_geom.h converts "logical" rects by a monitor's scale. For a + * stage-coordinate publisher (the `window-geometry@displayxr.org` GNOME Shell + * extension) "logical" means Mutter's STAGE coordinates, and Mutter has two + * layout modes that put the stage in different spaces: + * + * - LOGICAL (`layout-mode` 1): the stage is in logical px. A 3840x2160 + * monitor at scale 2 is a 1920x1080 stage rect, and device px are stage px + * x the monitor scale. Every fractional-scaling desktop is in this mode + * (older GNOME enables it with the `scale-monitor-framebuffer` + * experimental feature; mutter 50 starts in it by default). + * - PHYSICAL (`layout-mode` 2): the stage is in DEVICE px. The same monitor + * at scale 2 is a 3840x2160 stage rect, and the scale only says how big + * clients draw. Ubuntu 24.04 / GNOME 46 at an integer scale, out of the + * box. + * + * Multiplying stage coordinates by the monitor scale is right in the first and + * doubles everything in the second: a window on a 3840x2160 panel at 200 % + * reads as 7680x4320 on a 7680x4320 output, so it is "not on the panel" and + * never weaves. The factor is therefore resolved per layout mode — + * @ref u_wl_stage_to_device_scale — and THAT is what goes into + * `u_wl_monitor::scale` for a stage-coordinate publisher. + * + * A client's own `wl_output.mode / xdg_output.logical_size` needs no such + * care: Mutter reports the stage size as the logical size in both modes, so + * that ratio already is the stage factor (measured: 1.0 in PHYSICAL at 200 %). + * + * Separate from u_wayland_geom.h on purpose: that header is shared byte-for-byte + * with displayxr-common (test_apps/common/dxr_linux_window_aux_guard.cmake), and + * this rule is the runtime's to apply at its boundary. + * + * Pure arithmetic, no Wayland or D-Bus dependency, no platform guard. Pinned by + * `tests/tests_aux_wayland_geom.cpp`. + */ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * Mutter's monitor layout mode: which space its stage coordinates are in (see + * the file comment). The values are Mutter's own `layout-mode` + * (org.gnome.Mutter.DisplayConfig.GetCurrentState). + * + * @ingroup aux_util + */ +enum u_wl_layout_mode +{ + //! Not known: no publisher said, and no compositor answered. + U_WL_LAYOUT_MODE_UNKNOWN = 0, + //! Stage = logical px; device = stage x the monitor scale. + U_WL_LAYOUT_MODE_LOGICAL = 1, + //! Stage = device px, whatever the monitor scale. + U_WL_LAYOUT_MODE_PHYSICAL = 2, +}; + +/*! + * The layout mode as the `window-geometry@displayxr.org` payload spells it + * (`"layout_mode": "logical" | "physical"`). Anything else is UNKNOWN. + * + * @ingroup aux_util + */ +static inline enum u_wl_layout_mode +u_wl_layout_mode_from_string(const char *s) +{ + if (s == NULL) { + return U_WL_LAYOUT_MODE_UNKNOWN; + } + // No needed; the two spellings are short. + const char *logical = "logical"; + const char *physical = "physical"; + size_t i = 0; + for (i = 0; s[i] != '\0' && s[i] == logical[i]; i++) { + } + if (s[i] == '\0' && logical[i] == '\0') { + return U_WL_LAYOUT_MODE_LOGICAL; + } + for (i = 0; s[i] != '\0' && s[i] == physical[i]; i++) { + } + if (s[i] == '\0' && physical[i] == '\0') { + return U_WL_LAYOUT_MODE_PHYSICAL; + } + return U_WL_LAYOUT_MODE_UNKNOWN; +} + +/*! + * Device px per STAGE px on one monitor: the factor that converts a + * stage-coordinate publisher's rects (the GNOME Shell extension's) to device + * pixels. + * + * In order of authority: + * 1. @p published_device_scale, when the publisher states it (extension + * version 11+: the scale of the stage view painting that monitor, which + * IS the factor in either layout mode); + * 2. PHYSICAL layout: 1.0 — the stage already is device px, and the + * monitor scale must NOT be applied; + * 3. otherwise (LOGICAL, or not known): @p monitor_scale. Unknown keeps the + * behaviour every earlier consumer had, which is right on every + * fractionally-scaled desktop (those are always LOGICAL). + * + * @return the factor, or 0.0 when none of the three is usable. + * + * @ingroup aux_util + */ +static inline double +u_wl_stage_to_device_scale(double monitor_scale, double published_device_scale, enum u_wl_layout_mode mode) +{ + if (published_device_scale > 0.0) { + return published_device_scale; + } + if (mode == U_WL_LAYOUT_MODE_PHYSICAL) { + return 1.0; + } + return monitor_scale > 0.0 ? monitor_scale : 0.0; +} + +#ifdef __cplusplus +} +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bc449c9..aeed6dc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -81,6 +81,17 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON) add_test(NAME displayxr_linux_window_lattice_test COMMAND displayxr_linux_window_lattice_test) + # The two Wayland scale factors (dxr_wl_scale.h, pure) in both of mutter's + # layout modes: device px per SURFACE unit (sizing) and per STAGE px + # (positions, the window-geometry extension's rects). + add_executable(displayxr_linux_window_scale_test linux_window_scale_test.cpp) + target_include_directories(displayxr_linux_window_scale_test PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../common/linux" + "${CMAKE_CURRENT_SOURCE_DIR}/../common/linux/xrt_aux") + set_target_properties(displayxr_linux_window_scale_test PROPERTIES + CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON) + add_test(NAME displayxr_linux_window_scale_test COMMAND displayxr_linux_window_scale_test) + # The window-drag decisions (dxr_drag.h, pure): when an X11 / Wayland drag # starts and ends, and which phase-correct position an X11 drag step lands # on (over a model of XWayland at 200 %). diff --git a/tests/linux_window_scale_test.cpp b/tests/linux_window_scale_test.cpp new file mode 100644 index 0000000..7d0cdb6 --- /dev/null +++ b/tests/linux_window_scale_test.cpp @@ -0,0 +1,74 @@ +// Copyright 2026, The DisplayXR Project +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief The two Wayland scale factors in both of mutter's layout modes + * (common/linux/dxr_wl_scale.h). No display, no compositor. + * + * Inputs are measured on a headless mutter 50.1 with one 3840x2160 virtual + * monitor, switching only `gdctl set --layout-mode`: + * + * layout scale wl_output.mode xdg_output.logical_size wl_output.scale + * physical 2 3840x2160 3840x2160 2 + * logical 2 3840x2160 1920x1080 2 + * logical 1.5 3840x2160 2560x1440 2 + * + * 1. The surface-scale estimate is the integer scale in PHYSICAL and the + * fractional stage factor in LOGICAL, so a 720x400 initial rect is a + * 360x200 surface in both (it was 720x400 surface units — 1440x800 px — + * in PHYSICAL). + * 2. The stage factor of a published monitor: the extension's device_scale / + * layout_mode first, then the client's own output, then the monitor scale. + */ +#include "dxr_wl_scale.h" + +#include +#include + +static int g_fail = 0; +#define CHECK(c) \ + do { \ + if (!(c)) { \ + std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #c); \ + g_fail++; \ + } \ + } while (0) + +int +main() +{ + // 1. Surface-scale estimate. + CHECK(dxr_wl_surface_scale_estimate(3840, 3840, 2) == 2.0); // physical at 2 + CHECK(dxr_wl_surface_scale_estimate(3840, 1920, 2) == 2.0); // logical at 2 + CHECK(dxr_wl_surface_scale_estimate(3840, 2560, 2) == 1.5); // logical at 1.5 (int scale says 2) + CHECK(dxr_wl_surface_scale_estimate(3840, 3840, 1) == 1.0); // 100 %, either mode + CHECK(dxr_wl_surface_scale_estimate(2880, 1728, 2) > 1.66 && dxr_wl_surface_scale_estimate(2880, 1728, 2) < 1.67); + CHECK(dxr_wl_surface_scale_estimate(0, 1920, 2) == 1.0); // nothing known + CHECK(dxr_wl_surface_scale_estimate(3840, 0, 2) == 1.0); + // A 720x400 device-px rect is a 360x200 surface in both modes. + for (const int32_t logical_w : {3840, 1920}) { + const double s = dxr_wl_surface_scale_estimate(3840, logical_w, 2); + CHECK((int32_t)(720.0 / s + 0.5) == 360); + CHECK((int32_t)(400.0 / s + 0.5) == 200); + } + + // 2. Stage factor of a published monitor. + // Extension v11 answers: authoritative. + CHECK(dxr_wl_stage_factor(2.0, 1.0, U_WL_LAYOUT_MODE_PHYSICAL, 0.0) == 1.0); + CHECK(dxr_wl_stage_factor(2.0, 2.0, U_WL_LAYOUT_MODE_LOGICAL, 0.0) == 2.0); + CHECK(dxr_wl_stage_factor(2.0, 0.0, U_WL_LAYOUT_MODE_PHYSICAL, 2.0) == 1.0); // beats the output + CHECK(dxr_wl_stage_factor(1.5, 1.5, U_WL_LAYOUT_MODE_UNKNOWN, 0.0) == 1.5); + // An older extension: the client's own output decides. + CHECK(dxr_wl_stage_factor(2.0, 0.0, U_WL_LAYOUT_MODE_UNKNOWN, 3840.0 / 3840.0) == 1.0); // physical + CHECK(dxr_wl_stage_factor(2.0, 0.0, U_WL_LAYOUT_MODE_UNKNOWN, 3840.0 / 1920.0) == 2.0); // logical + // Nothing else known: the monitor scale (the pre-v11 behaviour). + CHECK(dxr_wl_stage_factor(2.0, 0.0, U_WL_LAYOUT_MODE_UNKNOWN, 0.0) == 2.0); + CHECK(dxr_wl_stage_factor(0.0, 0.0, U_WL_LAYOUT_MODE_UNKNOWN, 0.0) == 0.0); + + if (g_fail != 0) { + std::printf("linux_window_scale_test: %d failure(s)\n", g_fail); + return 1; + } + std::printf("linux_window_scale_test: OK\n"); + return 0; +}