diff --git a/glfw/glfw.py b/glfw/glfw.py index 79894ef51..1afc8c602 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -318,6 +318,7 @@ def generate_wrappers(glfw_header: str) -> None: void glfwCocoaSetWindowChrome(GLFWwindow* window, unsigned int color, bool use_system_color, unsigned int system_color,\ int background_blur, unsigned int hide_window_decorations, bool show_text_in_titlebar, int color_space, float background_opacity, bool resizable) void glfwCocoaRegisterMIMETypes(GLFWwindow *window, const char **mimes, size_t count) + void glfwCocoaSetWindowLevel(GLFWwindow *window, const char *level_spec) const char* glfwGetPrimarySelectionString(GLFWwindow* window, void) int glfwGetNativeKeyForName(const char* key_name, int case_sensitive) void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback) @@ -326,6 +327,7 @@ def generate_wrappers(glfw_header: str) -> None: void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data) bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color) void glfwWaylandSetTitlebarHidden(GLFWwindow *handle, bool hidden) + void glfwWaylandSetInitialWindowSizeCallback(GLFWwaylandinitialsizefun callback) void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) bool glfwWaylandIsWindowFullyCreated(GLFWwindow *handle) bool glfwWaylandBeep(GLFWwindow *handle) diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 2e99fb2e5..bceae97ae 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1933,6 +1933,7 @@ typedef void (* GLFWjoystickfun)(int,int); typedef void (* GLFWuserdatafun)(unsigned long long, void*); typedef void (* GLFWtickcallback)(void*); typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data); +typedef void (* GLFWwaylandinitialsizefun)(GLFWwindow *window, float xscale, float yscale, int *width, int *height); typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool is_single_glyph); typedef char* (* GLFWcurrentselectionfun)(void); typedef bool (* GLFWhascurrentselectionfun)(void); diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 6abb77d17..dc351eba9 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -440,8 +440,41 @@ clipboard_mime(void) { return buf; } +static GLFWwaylandinitialsizefun initial_window_size_callback = NULL; + +GLFWAPI void +glfwWaylandSetInitialWindowSizeCallback(GLFWwaylandinitialsizefun callback) { + initial_window_size_callback = callback; +} + +static void +maybe_recompute_initial_window_size(_GLFWwindow *window) { + // Before the initial window is mapped, the compositor tells us the real + // (possibly fractional) scale. For sizes specified in cells this changes + // the correct logical window size, because cell metrics are not a linear + // function of scale (integer-pixel rounding). Ask the embedder to recompute + // the logical size now, while the window is still unmapped, so it is mapped + // at the right size and the compositor's authoritative configure agrees. + if (window->wl.window_fully_created) return; + if (!initial_window_size_callback) return; + if (!window->wl.xdg.toplevel) return; // only ordinary toplevels have cell-based sizes + if (window->wl.layer_shell.zwlr_layer_surface_v1) return; // layer-shell surfaces size themselves + double scale = _glfwWaylandWindowScale(window); + int w = window->wl.width, h = window->wl.height; + initial_window_size_callback((GLFWwindow*)window, (float)scale, (float)scale, &w, &h); + if (w > 0 && h > 0 && (w != window->wl.width || h != window->wl.height)) { + debug("Recomputed initial size of window %llu for scale %.3f: %dx%d -> %dx%d\n", + window->id, scale, window->wl.width, window->wl.height, w, h); + window->wl.width = w; window->wl.height = h; + window->wl.user_requested_content_size.width = w; + window->wl.user_requested_content_size.height = h; + update_regions(window); + } +} + static void apply_scale_changes(_GLFWwindow *window, bool resize_framebuffer, bool update_csd) { + maybe_recompute_initial_window_size(window); double scale = _glfwWaylandWindowScale(window); if (resize_framebuffer) resizeFramebuffer(window); _glfwInputWindowContentScale(window, (float)scale, (float)scale); diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 91d925a6d..f79dc7760 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -497,15 +497,15 @@ load_glfw(const char* path) { *(void **) (&glfwCocoaCycleThroughOSWindows_impl) = dlsym(handle, "glfwCocoaCycleThroughOSWindows"); if (glfwCocoaCycleThroughOSWindows_impl == NULL) dlerror(); // clear error indicator - *(void **) (&glfwCocoaSetWindowLevel_impl) = dlsym(handle, "glfwCocoaSetWindowLevel"); - if (glfwCocoaSetWindowLevel_impl == NULL) dlerror(); // clear error indicator - *(void **) (&glfwCocoaSetWindowChrome_impl) = dlsym(handle, "glfwCocoaSetWindowChrome"); if (glfwCocoaSetWindowChrome_impl == NULL) dlerror(); // clear error indicator *(void **) (&glfwCocoaRegisterMIMETypes_impl) = dlsym(handle, "glfwCocoaRegisterMIMETypes"); if (glfwCocoaRegisterMIMETypes_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwCocoaSetWindowLevel_impl) = dlsym(handle, "glfwCocoaSetWindowLevel"); + if (glfwCocoaSetWindowLevel_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwGetPrimarySelectionString_impl) = dlsym(handle, "glfwGetPrimarySelectionString"); if (glfwGetPrimarySelectionString_impl == NULL) dlerror(); // clear error indicator @@ -530,6 +530,9 @@ load_glfw(const char* path) { *(void **) (&glfwWaylandSetTitlebarHidden_impl) = dlsym(handle, "glfwWaylandSetTitlebarHidden"); if (glfwWaylandSetTitlebarHidden_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwWaylandSetInitialWindowSizeCallback_impl) = dlsym(handle, "glfwWaylandSetInitialWindowSizeCallback"); + if (glfwWaylandSetInitialWindowSizeCallback_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwWaylandRedrawCSDWindowTitle_impl) = dlsym(handle, "glfwWaylandRedrawCSDWindowTitle"); if (glfwWaylandRedrawCSDWindowTitle_impl == NULL) dlerror(); // clear error indicator diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index ec4d42933..142ef0cfb 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1661,6 +1661,7 @@ typedef void (* GLFWjoystickfun)(int,int); typedef void (* GLFWuserdatafun)(unsigned long long, void*); typedef void (* GLFWtickcallback)(void*); typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data); +typedef void (* GLFWwaylandinitialsizefun)(GLFWwindow *window, float xscale, float yscale, int *width, int *height); typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin, bool is_single_glyph); typedef char* (* GLFWcurrentselectionfun)(void); typedef bool (* GLFWhascurrentselectionfun)(void); @@ -2479,10 +2480,6 @@ typedef void (*glfwCocoaCycleThroughOSWindows_func)(bool); GFW_EXTERN glfwCocoaCycleThroughOSWindows_func glfwCocoaCycleThroughOSWindows_impl; #define glfwCocoaCycleThroughOSWindows glfwCocoaCycleThroughOSWindows_impl -typedef void (*glfwCocoaSetWindowLevel_func)(GLFWwindow*, const char*); -GFW_EXTERN glfwCocoaSetWindowLevel_func glfwCocoaSetWindowLevel_impl; -#define glfwCocoaSetWindowLevel glfwCocoaSetWindowLevel_impl - typedef void (*glfwCocoaSetWindowChrome_func)(GLFWwindow*, unsigned int, bool, unsigned int, int, unsigned int, bool, int, float, bool); GFW_EXTERN glfwCocoaSetWindowChrome_func glfwCocoaSetWindowChrome_impl; #define glfwCocoaSetWindowChrome glfwCocoaSetWindowChrome_impl @@ -2491,6 +2488,10 @@ typedef void (*glfwCocoaRegisterMIMETypes_func)(GLFWwindow*, const char**, size_ GFW_EXTERN glfwCocoaRegisterMIMETypes_func glfwCocoaRegisterMIMETypes_impl; #define glfwCocoaRegisterMIMETypes glfwCocoaRegisterMIMETypes_impl +typedef void (*glfwCocoaSetWindowLevel_func)(GLFWwindow*, const char*); +GFW_EXTERN glfwCocoaSetWindowLevel_func glfwCocoaSetWindowLevel_impl; +#define glfwCocoaSetWindowLevel glfwCocoaSetWindowLevel_impl + typedef const char* (*glfwGetPrimarySelectionString_func)(GLFWwindow*); GFW_EXTERN glfwGetPrimarySelectionString_func glfwGetPrimarySelectionString_impl; #define glfwGetPrimarySelectionString glfwGetPrimarySelectionString_impl @@ -2523,6 +2524,10 @@ typedef void (*glfwWaylandSetTitlebarHidden_func)(GLFWwindow*, bool); GFW_EXTERN glfwWaylandSetTitlebarHidden_func glfwWaylandSetTitlebarHidden_impl; #define glfwWaylandSetTitlebarHidden glfwWaylandSetTitlebarHidden_impl +typedef void (*glfwWaylandSetInitialWindowSizeCallback_func)(GLFWwaylandinitialsizefun); +GFW_EXTERN glfwWaylandSetInitialWindowSizeCallback_func glfwWaylandSetInitialWindowSizeCallback_impl; +#define glfwWaylandSetInitialWindowSizeCallback glfwWaylandSetInitialWindowSizeCallback_impl + typedef void (*glfwWaylandRedrawCSDWindowTitle_func)(GLFWwindow*); GFW_EXTERN glfwWaylandRedrawCSDWindowTitle_func glfwWaylandRedrawCSDWindowTitle_impl; #define glfwWaylandRedrawCSDWindowTitle glfwWaylandRedrawCSDWindowTitle_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 8be1474b1..fe8a8be68 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1705,6 +1705,32 @@ os_window_update_size_increments(OSWindow *window) { } +// Borrowed reference to the get_window_size callable, valid only for the +// duration of a create_os_window() call. Used by the Wayland initial-size hook +// below, which fires synchronously from inside glfwCreateWindow(). +static PyObject *initial_window_size_py_callback = NULL; + +static void +wayland_initial_size_callback(GLFWwindow *window UNUSED, float xscale, float yscale, int *width, int *height) { + // The compositor has told us the real scale before the window is mapped; + // recompute the cell-based logical size for that scale so the window is + // mapped at the correct size in the first place. + if (!initial_window_size_py_callback) return; + double xdpi, ydpi; + dpi_from_scale(xscale, yscale, &xdpi, &ydpi); + FONTS_DATA_HANDLE fonts_data = load_fonts_data(OPT(font_size), xdpi, ydpi); + if (!fonts_data) return; + PyObject *ret = PyObject_CallFunction(initial_window_size_py_callback, "IIddff", + fonts_data->fcm.cell_width, fonts_data->fcm.cell_height, + fonts_data->logical_dpi_x, fonts_data->logical_dpi_y, xscale, yscale); + if (ret) { + int w = PyLong_AsLong(PyTuple_GET_ITEM(ret, 0)), h = PyLong_AsLong(PyTuple_GET_ITEM(ret, 1)); + if (!PyErr_Occurred() && w > 0 && h > 0) { *width = w; *height = h; } + else PyErr_Clear(); + Py_DECREF(ret); + } else PyErr_Clear(); +} + static PyObject* create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { int x = INT_MIN, y = INT_MIN, window_state = WINDOW_NORMAL, disallow_override_title = 0; @@ -1822,7 +1848,16 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (!layer_shell_config_from_python(layer_shell_config, lsc)) return NULL; lsc->expected.xscale = xscale; lsc->expected.yscale = yscale; } + // On Wayland the true (fractional) scale is only known after the surface + // exists. Register a hook so the window is mapped at the correct cell-based + // size once the compositor reveals the scale, rather than being resized + // afterwards (which loses to the compositor's authoritative configure). + if (global_state.is_wayland && glfwWaylandSetInitialWindowSizeCallback) { + glfwWaylandSetInitialWindowSizeCallback(wayland_initial_size_callback); + initial_window_size_py_callback = get_window_size; + } GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, temp_window ? temp_window : common_context, lsc); + initial_window_size_py_callback = NULL; if (temp_window) { glfwDestroyWindow(temp_window); temp_window = NULL; } if (glfw_window == NULL) glfw_failure; #undef glfw_failure