Wayland: get initial window size in cells working in most cases on hyprland/sway with fractional scaling by using the fractional scale of the primary monitor

This commit is contained in:
Kovid Goyal 2026-07-21 10:03:40 +05:30
parent a7d7183681
commit 6ed833e163
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
9 changed files with 163 additions and 13 deletions

View file

@ -186,6 +186,9 @@ Detailed list of changes
- Wayland: fix a regression in 0.47.3 that broke dragging on the Niri compositor (:iss:`10271`)
- Wayland: get initial window size in cells working in most cases on hyprland/sway with fractional scaling by using the fractional scale of the primary monitor
0.48.0 [2026-07-18]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -332,6 +332,7 @@ def generate_wrappers(glfw_header: str) -> None:
bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle)
bool glfwWaylandBeep(GLFWwindow *handle)
pid_t glfwWaylandCompositorPID(void)
double glfwGetWaylandPrimaryMonitorFractionalScale(void)
void glfwConfigureMomentumScroller(double friction, double min_velocity, double max_velocity, unsigned timer_interval)
unsigned long long glfwDBusUserNotify(const GLFWDBUSNotificationData *n, GLFWDBusnotificationcreatedfun callback, void *data)
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)

View file

@ -91,6 +91,7 @@
"staging/ext-background-effect/ext-background-effect-v1.xml",
"staging/xdg-toplevel-drag/xdg-toplevel-drag-v1.xml",
"unstable/pointer-gestures/pointer-gestures-unstable-v1.xml",
"unstable/xdg-output/xdg-output-unstable-v1.xml",
"kwin-blur-v1.xml",
"wlr-layer-shell-unstable-v1.xml"

8
glfw/wl_init.c vendored
View file

@ -728,6 +728,10 @@ static void registryHandleGlobal(void* data UNUSED,
_glfw.wl.xdg_toplevel_tag_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_tag_manager_v1_interface, 1);
} else if (is(xdg_toplevel_drag_manager_v1)) {
_glfw.wl.xdg_toplevel_drag_manager_v1 = wl_registry_bind(registry, name, &xdg_toplevel_drag_manager_v1_interface, 1);
} else if (is(zxdg_output_manager_v1)) {
_glfw.wl.xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, MIN(version, 3u));
for (int i = 0; i < _glfw.monitorCount; i++)
_glfwCreateXdgOutputWayland(_glfw.monitors[i]);
}
#undef is
}
@ -840,7 +844,7 @@ get_compositor_missing_capabilities(void) {
C(single_pixel_buffer, wp_single_pixel_buffer_manager_v1); C(preferred_scale, has_preferred_buffer_scale);
C(idle_inhibit, idle_inhibit_manager); C(icon, xdg_toplevel_icon_manager_v1); C(bell, xdg_system_bell_v1);
C(window-tag, xdg_toplevel_tag_manager_v1); C(keyboard_shortcuts_inhibit, keyboard_shortcuts_inhibit_manager);
C(key-repeat, has_key_repeat_events); C(top_level_drag, xdg_toplevel_drag_manager_v1);
C(key-repeat, has_key_repeat_events); C(top_level_drag, xdg_toplevel_drag_manager_v1); C(output_manager, xdg_output_manager);
C(pointer_gestures, pointer_gestures);
#define P(x) p += snprintf(p, sizeof(buf) - (p - buf), "%s ", x);
if (_glfw.wl.xdg_wm_base_version < 6) P("window-state-suspended");
@ -1031,6 +1035,8 @@ void _glfwPlatformTerminate(void)
xdg_toplevel_tag_manager_v1_destroy(_glfw.wl.xdg_toplevel_tag_manager_v1);
if (_glfw.wl.xdg_toplevel_drag_manager_v1)
xdg_toplevel_drag_manager_v1_destroy(_glfw.wl.xdg_toplevel_drag_manager_v1);
if (_glfw.wl.xdg_output_manager)
zxdg_output_manager_v1_destroy(_glfw.wl.xdg_output_manager);
if (_glfw.wl.wp_single_pixel_buffer_manager_v1)
wp_single_pixel_buffer_manager_v1_destroy(_glfw.wl.wp_single_pixel_buffer_manager_v1);
if (_glfw.wl.wp_cursor_shape_manager_v1)

122
glfw/wl_monitor.c vendored
View file

@ -43,13 +43,14 @@ static void outputHandleGeometry(void* data,
int32_t subpixel UNUSED,
const char* make UNUSED,
const char* model UNUSED,
int32_t transform UNUSED)
int32_t transform)
{
struct _GLFWmonitor *monitor = data;
monitor->wl.x = x;
monitor->wl.y = y;
monitor->widthMM = physicalWidth;
monitor->heightMM = physicalHeight;
monitor->wl.transform = transform;
}
static void outputHandleMode(void* data,
@ -126,10 +127,93 @@ static const struct wl_output_listener outputListener = {
};
static void xdgOutputHandleLogicalPosition(void *data,
struct zxdg_output_v1 *xdg_output UNUSED,
int32_t x,
int32_t y)
{
struct _GLFWmonitor *monitor = data;
monitor->wl.x = x;
monitor->wl.y = y;
}
static void xdgOutputHandleLogicalSize(void *data,
struct zxdg_output_v1 *xdg_output UNUSED,
int32_t width,
int32_t height)
{
struct _GLFWmonitor *monitor = data;
monitor->wl.xdg_logical_width = width;
monitor->wl.xdg_logical_height = height;
}
static void computeXdgFractionalScale(struct _GLFWmonitor *monitor)
{
if (monitor->wl.xdg_logical_width <= 0 || monitor->wl.xdg_logical_height <= 0) return;
if (monitor->modeCount == 0 || monitor->wl.currentMode < 0) return;
GLFWvidmode *mode = &monitor->modes[monitor->wl.currentMode];
if (mode->width <= 0 || mode->height <= 0) return;
// For 90° and 270° rotations the compositor maps physical width↔height,
// so use the swapped axis when dividing by the logical size.
int phys_w = mode->width, phys_h = mode->height;
int32_t t = monitor->wl.transform;
if (t == WL_OUTPUT_TRANSFORM_90 || t == WL_OUTPUT_TRANSFORM_270 ||
t == WL_OUTPUT_TRANSFORM_FLIPPED_90 || t == WL_OUTPUT_TRANSFORM_FLIPPED_270)
{
int tmp = phys_w; phys_w = phys_h; phys_h = tmp;
}
double sx = (double)phys_w / monitor->wl.xdg_logical_width;
double sy = (double)phys_h / monitor->wl.xdg_logical_height;
// Use the average in case of minor rounding differences between axes.
monitor->wl.fractional_scale = (sx + sy) * 0.5;
}
static void xdgOutputHandleDone(void *data,
struct zxdg_output_v1 *xdg_output UNUSED)
{
struct _GLFWmonitor *monitor = data;
computeXdgFractionalScale(monitor);
}
static void xdgOutputHandleName(void *data,
struct zxdg_output_v1 *xdg_output UNUSED,
const char *name)
{
// wl_output already provides the name; skip to avoid duplicate frees.
(void)data; (void)name;
}
static void xdgOutputHandleDescription(void *data,
struct zxdg_output_v1 *xdg_output UNUSED,
const char *description)
{
(void)data; (void)description;
}
static const struct zxdg_output_v1_listener xdgOutputListener = {
xdgOutputHandleLogicalPosition,
xdgOutputHandleLogicalSize,
xdgOutputHandleDone,
xdgOutputHandleName,
xdgOutputHandleDescription,
};
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
void _glfwCreateXdgOutputWayland(_GLFWmonitor* monitor)
{
if (!_glfw.wl.xdg_output_manager || monitor->wl.xdg_output) return;
monitor->wl.xdg_output = zxdg_output_manager_v1_get_xdg_output(
_glfw.wl.xdg_output_manager, monitor->wl.output);
if (monitor->wl.xdg_output)
zxdg_output_v1_add_listener(monitor->wl.xdg_output, &xdgOutputListener, monitor);
}
void _glfwAddOutputWayland(uint32_t name, uint32_t version)
{
_GLFWmonitor *monitor;
@ -160,6 +244,7 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version)
monitor->wl.name = name;
wl_output_add_listener(output, &outputListener, monitor);
_glfwCreateXdgOutputWayland(monitor);
}
@ -169,6 +254,8 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version)
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
{
if (monitor->wl.xdg_output)
zxdg_output_v1_destroy(monitor->wl.xdg_output);
if (monitor->wl.output)
wl_output_destroy(monitor->wl.output);
}
@ -184,10 +271,11 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
float* xscale, float* yscale)
{
if (xscale)
*xscale = (float) monitor->wl.scale;
if (yscale)
*yscale = (float) monitor->wl.scale;
float scale = monitor->wl.fractional_scale > 0.0
? (float)monitor->wl.fractional_scale
: (float)monitor->wl.scale;
if (xscale) *xscale = scale;
if (yscale) *yscale = scale;
}
void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
@ -246,3 +334,27 @@ GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->wl.output;
}
GLFWAPI double glfwGetWaylandPrimaryMonitorFractionalScale(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(1.0);
if (!_glfw.wl.xdg_output_manager || _glfw.monitorCount == 0)
return 1.0;
_GLFWmonitor *monitor = _glfw.monitors[0];
if (!monitor->wl.xdg_output)
return monitor->wl.scale > 0 ? (double)monitor->wl.scale : 1.0;
// Block until the compositor has delivered xdg_output logical_size.
while (monitor->wl.xdg_logical_width <= 0 || monitor->wl.xdg_logical_height <= 0)
{
if (wl_display_roundtrip(_glfw.wl.display) < 0) break;
}
if (monitor->wl.fractional_scale <= 0.0)
computeXdgFractionalScale(monitor);
return monitor->wl.fractional_scale > 0.0
? monitor->wl.fractional_scale
: (monitor->wl.scale > 0 ? (double)monitor->wl.scale : 1.0);
}

9
glfw/wl_platform.h vendored
View file

@ -72,6 +72,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "wayland-xdg-system-bell-v1-client-protocol.h"
#include "wayland-xdg-toplevel-tag-v1-client-protocol.h"
#include "wayland-xdg-toplevel-drag-v1-client-protocol.h"
#include "wayland-xdg-output-unstable-v1-client-protocol.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
@ -366,6 +367,7 @@ typedef struct _GLFWlibraryWayland
struct zwp_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit_manager;
struct zwp_pointer_gestures_v1* pointer_gestures;
struct zwp_pointer_gesture_hold_v1* pointer_gesture_hold;
struct zxdg_output_manager_v1* xdg_output_manager;
int compositorVersion;
int seatVersion;
@ -470,12 +472,18 @@ typedef struct _GLFWlibraryWayland
typedef struct _GLFWmonitorWayland
{
struct wl_output* output;
struct zxdg_output_v1* xdg_output;
uint32_t name;
int currentMode;
int x;
int y;
int scale;
int32_t transform;
int32_t xdg_logical_width;
int32_t xdg_logical_height;
double fractional_scale;
} _GLFWmonitorWayland;
@ -496,6 +504,7 @@ typedef struct _GLFWcursorWayland
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwCreateXdgOutputWayland(_GLFWmonitor* monitor);
void _glfwWaylandBeforeBufferSwap(_GLFWwindow *window);
void _glfwWaylandAfterBufferSwap(_GLFWwindow *window);
void _glfwSetupWaylandDataDevice(void);

3
kitty/glfw-wrapper.c generated
View file

@ -545,6 +545,9 @@ load_glfw(const char* path) {
*(void **) (&glfwWaylandCompositorPID_impl) = dlsym(handle, "glfwWaylandCompositorPID");
if (glfwWaylandCompositorPID_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwGetWaylandPrimaryMonitorFractionalScale_impl) = dlsym(handle, "glfwGetWaylandPrimaryMonitorFractionalScale");
if (glfwGetWaylandPrimaryMonitorFractionalScale_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwConfigureMomentumScroller_impl) = dlsym(handle, "glfwConfigureMomentumScroller");
if (glfwConfigureMomentumScroller_impl == NULL) dlerror(); // clear error indicator

4
kitty/glfw-wrapper.h generated
View file

@ -2544,6 +2544,10 @@ typedef pid_t (*glfwWaylandCompositorPID_func)(void);
GFW_EXTERN glfwWaylandCompositorPID_func glfwWaylandCompositorPID_impl;
#define glfwWaylandCompositorPID glfwWaylandCompositorPID_impl
typedef double (*glfwGetWaylandPrimaryMonitorFractionalScale_func)(void);
GFW_EXTERN glfwGetWaylandPrimaryMonitorFractionalScale_func glfwGetWaylandPrimaryMonitorFractionalScale_impl;
#define glfwGetWaylandPrimaryMonitorFractionalScale glfwGetWaylandPrimaryMonitorFractionalScale_impl
typedef void (*glfwConfigureMomentumScroller_func)(double, double, double, unsigned);
GFW_EXTERN glfwConfigureMomentumScroller_func glfwConfigureMomentumScroller_impl;
#define glfwConfigureMomentumScroller glfwConfigureMomentumScroller_impl

View file

@ -1821,13 +1821,24 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
float xscale, yscale;
double xdpi, ydpi;
if (global_state.is_wayland) {
// Cannot use temp window on Wayland as scale is only sent by compositor after window is displayed
get_window_content_scale(NULL, &xscale, &yscale, &xdpi, &ydpi);
for (unsigned i = 0; i < global_state.num_os_windows; i++) {
OSWindow *osw = global_state.os_windows + i;
if (osw->handle && glfwGetWindowAttrib(osw->handle, GLFW_FOCUSED)) {
get_window_content_scale(osw->handle, &xscale, &yscale, &xdpi, &ydpi);
break;
// Cannot use temp window on Wayland as scale is only sent by compositor after window is displayed.
// For the first window, query the xdg-output fractional scale of the primary monitor directly;
// this blocks until the compositor has sent size information so we get the true fractional scale
// before creating any window.
if (is_first_window && glfwGetWaylandPrimaryMonitorFractionalScale) {
debug_rendering("Querying Wayland compositor for primary monitor scale\n");
double fscale = glfwGetWaylandPrimaryMonitorFractionalScale();
debug_rendering("Primary monitor scale reported as: %.5f\n", fscale);
xscale = yscale = (float)fscale;
dpi_from_scale(xscale, yscale, &xdpi, &ydpi);
} else {
get_window_content_scale(NULL, &xscale, &yscale, &xdpi, &ydpi);
for (unsigned i = 0; i < global_state.num_os_windows; i++) {
OSWindow *osw = global_state.os_windows + i;
if (osw->handle && glfwGetWindowAttrib(osw->handle, GLFW_FOCUSED)) {
get_window_content_scale(osw->handle, &xscale, &yscale, &xdpi, &ydpi);
break;
}
}
}
} else {