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.
This commit is contained in:
Kovid Goyal 2024-03-26 12:48:45 +05:30
parent f5314cb862
commit adf5917325
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
8 changed files with 52 additions and 16 deletions

View file

@ -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)

6
glfw/wl_platform.h vendored
View file

@ -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 {

29
glfw/wl_window.c vendored
View file

@ -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;
}

View file

@ -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: ...

3
kitty/glfw-wrapper.c generated
View file

@ -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

4
kitty/glfw-wrapper.h generated
View file

@ -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

View file

@ -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),

View file

@ -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)