Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions common/linux/dxr_linux_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <linux/input-event-codes.h> // raw evdev keycodes (the no-xkbcommon fallback table)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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 "
Expand All @@ -3597,20 +3625,25 @@ 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
// wherever the compositor maps the window before it is moved, which
// 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
Expand All @@ -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 "
Expand Down
8 changes: 8 additions & 0 deletions common/linux/dxr_linux_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

/*!
Expand Down
21 changes: 21 additions & 0 deletions common/linux/dxr_wl_placement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include "dxr_wl_placement.h"

#include "util/u_wayland_layout.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions common/linux/dxr_wl_placement.h
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
82 changes: 82 additions & 0 deletions common/linux/dxr_wl_scale.h
Original file line number Diff line number Diff line change
@@ -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 <cmath>
#include <cstdint>

/*!
* 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);
}
133 changes: 133 additions & 0 deletions common/linux/xrt_aux/util/u_wayland_layout.h
Original file line number Diff line number Diff line change
@@ -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 <stdbool.h>
#include <stddef.h>

#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 <string.h> 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
Loading
Loading