From adf5917325a8937c911a37480498ba9196e88f52 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 26 Mar 2024 12:48:45 +0530 Subject: [PATCH] Wayland: Only launch child after OS Window achieves its final size Avoids a bunch of SIGIWNCH during child startup as not all programs handle these correctly. Sadly adds about 0.1 seconds of latency to startup. Will have to look into reducing that. The Wayland protocol is *so badly* designed. --- glfw/glfw.py | 1 + glfw/wl_platform.h | 6 ++++-- glfw/wl_window.c | 29 ++++++++++++++++++++--------- kitty/fast_data_types.pyi | 1 + kitty/glfw-wrapper.c | 3 +++ kitty/glfw-wrapper.h | 4 ++++ kitty/glfw.c | 11 +++++++++++ kitty/window.py | 13 ++++++++----- 8 files changed, 52 insertions(+), 16 deletions(-) diff --git a/glfw/glfw.py b/glfw/glfw.py index 96475ac9b..77fc977ea 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -316,6 +316,7 @@ def generate_wrappers(glfw_header: str) -> None: void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) void glfwWaylandSetupLayerShellForNextWindow(GLFWLayerShellConfig c) pid_t glfwWaylandCompositorPID(void) + bool glfwWaylandWindowFullyCreated(GLFWwindow *handle) unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data) void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 0f4a37a76..912c67b91 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -168,7 +168,10 @@ typedef struct _GLFWwindowWayland struct wp_fractional_scale_v1 *wp_fractional_scale_v1; struct wp_viewport *wp_viewport; struct org_kde_kwin_blur *org_kde_kwin_blur; - bool has_blur, expect_scale_from_compositor; + bool has_blur, expect_scale_from_compositor, window_fully_created; + struct { + bool surface_configured, fractional_scale_received, preferred_scale_received; + } once; struct { GLFWLayerShellConfig config; struct zwlr_layer_surface_v1* zwlr_layer_surface_v1; @@ -243,7 +246,6 @@ typedef struct _GLFWwindowWayland struct { unsigned int x, y; } axis_discrete_count; - bool surface_configured_once; uint32_t pending_state; struct { diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 36a431f7d..c8a375410 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -285,7 +285,7 @@ setCursorImage(_GLFWwindow* window, bool on_theme_change) { static bool checkScaleChange(_GLFWwindow* window) { - if (window->wl.fractional_scale || window->wl.integer_scale.preferred) return false; + if (window->wl.expect_scale_from_compositor) return false; unsigned int scale = 1, monitorScale; int i; @@ -493,9 +493,11 @@ static void surfaceHandleLeave(void *data, static void surface_preferred_buffer_scale(void *data, struct wl_surface *surface UNUSED, int32_t scale) { _GLFWwindow* window = data; - if ((int)window->wl.integer_scale.preferred == scale) return; + window->wl.once.preferred_scale_received = true; + if ((int)window->wl.integer_scale.preferred == scale && window->wl.window_fully_created) return; debug("Preferred integer buffer scale changed to: %d\n", scale); window->wl.integer_scale.preferred = scale; + window->wl.window_fully_created = true; if (!window->wl.fractional_scale) apply_scale_changes(window, true, true); } @@ -518,9 +520,12 @@ static const struct wl_surface_listener surfaceListener = { static void fractional_scale_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1 UNUSED, uint32_t scale) { _GLFWwindow *window = data; - if (scale == window->wl.fractional_scale) return; + window->wl.once.fractional_scale_received = true; + if (scale == window->wl.fractional_scale && window->wl.window_fully_created) return; debug("Fractional scale requested: %u/120 = %.2f\n", scale, scale / 120.); window->wl.fractional_scale = scale; + // Hyprland sends a fraction scale = 1 event before configuring the xdg surface and then another after with the correct scale + window->wl.window_fully_created = window->wl.once.surface_configured || scale != 120; apply_scale_changes(window, true, true); } @@ -565,6 +570,7 @@ static bool createSurface(_GLFWwindow* window, } } } + window->wl.window_fully_created = !window->wl.expect_scale_from_compositor; if (_glfw.wl.org_kde_kwin_blur_manager && wndconfig->blur_radius > 0) _glfwPlatformSetWindowBlur(window, wndconfig->blur_radius); window->wl.integer_scale.deduced = scale; @@ -699,10 +705,10 @@ apply_xdg_configure_changes(_GLFWwindow *window) { uint32_t new_states = window->wl.pending.toplevel_states; int width = window->wl.pending.width; int height = window->wl.pending.height; - if (!window->wl.surface_configured_once) { + if (!window->wl.once.surface_configured) { window->swaps_disallowed = false; window->wl.waiting_for_swap_to_commit = true; - window->wl.surface_configured_once = true; + window->wl.once.surface_configured = true; } if (new_states != window->wl.current.toplevel_states || @@ -734,7 +740,7 @@ apply_xdg_configure_changes(_GLFWwindow *window) { } else { ensure_csd_resources(window); } - debug("final window content size: %dx%d resized: %d\n", width, height, resized); + debug("Final window content size: %dx%d resized: %d\n", width, height, resized); } inform_compositor_of_window_geometry(window, "configure"); @@ -852,10 +858,10 @@ static void layer_surface_handle_configure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial, uint32_t width, uint32_t height) { debug("Layer shell configure event: width: %u height: %u\n", width, height); _GLFWwindow* window = data; - if (!window->wl.surface_configured_once) { + if (!window->wl.once.surface_configured) { window->swaps_disallowed = false; window->wl.waiting_for_swap_to_commit = true; - window->wl.surface_configured_once = true; + window->wl.once.surface_configured = true; } GLFWvidmode m = {0}; if (window->wl.monitorsCount) _glfwPlatformGetVideoMode(window->wl.monitors[0], &m); @@ -1431,7 +1437,7 @@ void _glfwPlatformHideWindow(_GLFWwindow* window) xdg_surface_destroy(window->wl.xdg.surface); window->wl.xdg.toplevel = NULL; window->wl.xdg.surface = NULL; - window->wl.surface_configured_once = false; + window->wl.once.surface_configured = false; window->swaps_disallowed = true; } window->wl.visible = false; @@ -2579,3 +2585,8 @@ GLFWAPI void glfwWaylandSetupLayerShellForNextWindow(GLFWLayerShellConfig c) { if (layer_shell_config_for_next_window.output_name && !layer_shell_config_for_next_window.output_name[0]) layer_shell_config_for_next_window.output_name = NULL; if (layer_shell_config_for_next_window.output_name) layer_shell_config_for_next_window.output_name = strdup(layer_shell_config_for_next_window.output_name); } + +GLFWAPI bool glfwWaylandWindowFullyCreated(GLFWwindow *handle) { + _GLFWwindow* window = (_GLFWwindow*) handle; + return window->wl.window_fully_created; +} diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 257867db1..932ffda2f 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1574,3 +1574,4 @@ def replace_c0_codes_except_nl_space_tab(text: str) -> str:... def replace_c0_codes_except_nl_space_tab(text: Union[bytes, memoryview, bytearray]) -> bytes:... def terminfo_data() -> bytes:... def wayland_compositor_pid() -> int:... +def window_fully_created(kitty_window_id: int) -> bool: ... diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index ceb1855b5..78158d775 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -485,6 +485,9 @@ load_glfw(const char* path) { *(void **) (&glfwWaylandCompositorPID_impl) = dlsym(handle, "glfwWaylandCompositorPID"); if (glfwWaylandCompositorPID_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwWaylandWindowFullyCreated_impl) = dlsym(handle, "glfwWaylandWindowFullyCreated"); + if (glfwWaylandWindowFullyCreated_impl == NULL) dlerror(); // clear error indicator + *(void **) (&glfwDBusUserNotify_impl) = dlsym(handle, "glfwDBusUserNotify"); if (glfwDBusUserNotify_impl == NULL) dlerror(); // clear error indicator diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index d5b8e7678..fa58afca8 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -2316,6 +2316,10 @@ typedef pid_t (*glfwWaylandCompositorPID_func)(void); GFW_EXTERN glfwWaylandCompositorPID_func glfwWaylandCompositorPID_impl; #define glfwWaylandCompositorPID glfwWaylandCompositorPID_impl +typedef bool (*glfwWaylandWindowFullyCreated_func)(GLFWwindow*); +GFW_EXTERN glfwWaylandWindowFullyCreated_func glfwWaylandWindowFullyCreated_impl; +#define glfwWaylandWindowFullyCreated glfwWaylandWindowFullyCreated_impl + typedef unsigned long long (*glfwDBusUserNotify_func)(const char*, const char*, const char*, const char*, const char*, int32_t, GLFWDBusnotificationcreatedfun, void*); GFW_EXTERN glfwDBusUserNotify_func glfwDBusUserNotify_impl; #define glfwDBusUserNotify glfwDBusUserNotify_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 65be2725d..191dfc6c1 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -2208,6 +2208,16 @@ make_x11_window_a_dock_window(PyObject *self UNUSED, PyObject *args UNUSED) { Py_RETURN_NONE; } +static PyObject* +window_fully_created(PyObject *self UNUSED, PyObject *wid) { + // On Wayland a window does not receive its final size by the time glfwCreateWindow returns + if (!global_state.is_wayland) Py_RETURN_TRUE; + OSWindow *w = os_window_for_kitty_window(PyLong_AsUnsignedLongLong(wid)); + if (!w || !w->handle) Py_RETURN_FALSE; + if (glfwWaylandWindowFullyCreated(w->handle)) Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + // Boilerplate {{{ static PyMethodDef module_methods[] = { @@ -2227,6 +2237,7 @@ static PyMethodDef module_methods[] = { METHODB(glfw_window_hint, METH_VARARGS), METHODB(x11_display, METH_NOARGS), METHODB(wayland_compositor_pid, METH_NOARGS), + METHODB(window_fully_created, METH_O), METHODB(get_click_interval, METH_NOARGS), METHODB(x11_window_id, METH_O), METHODB(make_x11_window_a_dock_window, METH_VARARGS), diff --git a/kitty/window.py b/kitty/window.py index 7cc0b73f2..e65c1b36e 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -85,6 +85,7 @@ from .fast_data_types import ( update_window_title, update_window_visibility, wakeup_main_loop, + window_fully_created, ) from .keys import keyboard_mode_name, mod_mask from .notify import ( @@ -554,7 +555,7 @@ class Window: self.actions_on_removal: List[Callable[['Window'], None]] = [] self.current_marker_spec: Optional[Tuple[str, Union[str, Tuple[Tuple[int, str], ...]]]] = None self.kitten_result_processors: List[Callable[['Window', Any], None]] = [] - self.pty_resized_once = False + self.child_is_launched = False self.last_reported_pty_size = (-1, -1, -1, -1) self.needs_attention = False self.ignore_focus_changes = self.initial_ignore_focus_changes @@ -836,13 +837,15 @@ class Window: if current_pty_size != self.last_reported_pty_size: boss = get_boss() boss.child_monitor.resize_pty(self.id, *current_pty_size) - if boss.args.debug_rendering: - print(f'SIGWINCH sent to child in window: {self.id} with size: {current_pty_size}', file=sys.stderr) self.last_resized_at = monotonic() - if not self.pty_resized_once: - self.pty_resized_once = True + if not self.child_is_launched and window_fully_created(self.id): self.child.mark_terminal_ready() + self.child_is_launched = True update_ime_position = True + if boss.args.debug_rendering: + print(f'Child launched {monotonic() - self.started_at:.2f} seconds after window creation') + if boss.args.debug_rendering and self.child_is_launched: + print(f'SIGWINCH sent to child in window: {self.id} with size: {current_pty_size}', file=sys.stderr) self.last_reported_pty_size = current_pty_size else: mark_os_window_dirty(self.os_window_id)