From 89875af5e6988474111a35e76058f2e79105af77 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 7 Jun 2023 14:31:39 +0530 Subject: [PATCH] Fix jerky behavior when live resizing an OS window on platforms that report live resize being and end events Also disable live resize begin/end events on Wayland since they are not reliable. On platforms that support live resize begin/end use a separately configurable pause time before the screen is redrawn. Fixes #6341 --- glfw/wl_window.c | 10 ++++++++-- kitty/child-monitor.c | 14 ++++++++++++-- kitty/options/definition.py | 16 +++++++++++----- kitty/options/parse.py | 14 +++++++------- kitty/options/to-c-generated.h | 2 +- kitty/options/to-c.h | 6 ++++++ kitty/options/types.py | 2 +- kitty/options/utils.py | 7 +++++++ kitty/state.h | 2 +- 9 files changed, 54 insertions(+), 19 deletions(-) diff --git a/glfw/wl_window.c b/glfw/wl_window.c index f1294978d..7e70695a9 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -504,6 +504,12 @@ _glfwPlatformToggleFullscreen(_GLFWwindow *window, unsigned int flags UNUSED) { return !already_fullscreen; } +static void +report_live_resize(_GLFWwindow *w, bool started) { + // disabled as mutter, for instance, does not send a configure event when the user stops resizing (aka releases the mouse button) + if (false) _glfwInputLiveResize(w, started); +} + static void xdgToplevelHandleConfigure(void* data, struct xdg_toplevel* toplevel UNUSED, @@ -536,7 +542,7 @@ xdgToplevelHandleConfigure(void* data, if (new_states & TOPLEVEL_STATE_RESIZING) { if (width) window->wl.user_requested_content_size.width = width; if (height) window->wl.user_requested_content_size.height = height; - if (!(window->wl.current.toplevel_states & TOPLEVEL_STATE_RESIZING)) _glfwInputLiveResize(window, true); + if (!(window->wl.current.toplevel_states & TOPLEVEL_STATE_RESIZING)) report_live_resize(window, true); } if (width != 0 && height != 0) { @@ -606,7 +612,7 @@ static void xdgSurfaceHandleConfigure(void* data, window->wl.current.width = width; window->wl.current.height = height; _glfwInputWindowFocus(window, window->wl.current.toplevel_states & TOPLEVEL_STATE_ACTIVATED); - if (live_resize_done) _glfwInputLiveResize(window, false); + if (live_resize_done) report_live_resize(window, false); } } diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 08637fa92..ad3110578 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -1004,9 +1004,19 @@ process_pending_resizes(monotonic_t now) { if (w->live_resize.in_progress) { bool update_viewport = false; if (w->live_resize.from_os_notification) { - if (w->live_resize.os_says_resize_complete || (now - w->live_resize.last_resize_event_at) > 1) update_viewport = true; + if (w->live_resize.os_says_resize_complete) update_viewport = true; + else { + // prevent a "hang" if the OS never sends a resize complete event + // also reflow the screen when the user pauses resizing so the user can see what the resized + // screen will look like. + if ((now - w->live_resize.last_resize_event_at) > OPT(resize_debounce_time).on_pause) update_viewport = true; + else { + global_state.has_pending_resizes = true; + set_maximum_wait(s_double_to_monotonic_t(0.05)); + } + } } else { - monotonic_t debounce_time = OPT(resize_debounce_time); + monotonic_t debounce_time = OPT(resize_debounce_time).on_end; // if more than one resize event has occurred, wait at least 0.2 secs // before repainting, to avoid rapid transitions between the cells banner // and the normal screen diff --git a/kitty/options/definition.py b/kitty/options/definition.py index e700e2695..75babd98c 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1065,12 +1065,18 @@ faded and one being fully opaque. ) -opt('resize_debounce_time', '0.1', - option_type='positive_float', ctype='time', +opt('resize_debounce_time', '0.1 0.5', + option_type='resize_debounce_time', ctype='!resize_debounce_time', long_text=''' -The time to wait before redrawing the screen when a resize event is received (in -seconds). On platforms such as macOS, where the operating system sends events -corresponding to the start and end of a resize, this number is ignored. +The time to wait before redrawing the screen during a live resize of the OS +window, when no new resize events have been received, i.e. when resizing is +either paused or finished. On platforms such as macOS, where the operating +system sends events corresponding to the start and end of a live resize, the +second number is used for redraw-after-pause since kitty can distinguish +between a pause and end of resizing. On such systems the first number is +ignored and redraw is immediate after end of resize. On other systems the +first number is used so that kitty is "ready" quickly after the end of +resizing, while not also continuously redrawing, to save energy. ''' ) diff --git a/kitty/options/parse.py b/kitty/options/parse.py index caa679873..799475847 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -13,12 +13,12 @@ from kitty.options.utils import ( deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias, deprecated_send_text, disable_ligatures, edge_width, env, font_features, hide_window_decorations, macos_option_as_alt, macos_titlebar_color, modify_font, narrow_symbols, optional_edge_width, - parse_map, parse_mouse_map, paste_actions, remote_control_password, resize_draw_strategy, - scrollback_lines, scrollback_pager_history_size, shell_integration, store_multiple, symbol_map, - tab_activity_symbol, tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade, - tab_font_style, tab_separator, tab_title_template, titlebar_color, to_cursor_shape, to_font_size, - to_layout_names, to_modifiers, url_prefixes, url_style, visual_window_select_characters, - window_border_width, window_size + parse_map, parse_mouse_map, paste_actions, remote_control_password, resize_debounce_time, + resize_draw_strategy, scrollback_lines, scrollback_pager_history_size, shell_integration, + store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge, tab_bar_margin_height, + tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, tab_title_template, titlebar_color, + to_cursor_shape, to_font_size, to_layout_names, to_modifiers, url_prefixes, url_style, + visual_window_select_characters, window_border_width, window_size ) @@ -1151,7 +1151,7 @@ class Parser: ans['repaint_delay'] = positive_int(val) def resize_debounce_time(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: - ans['resize_debounce_time'] = positive_float(val) + ans['resize_debounce_time'] = resize_debounce_time(val) def resize_draw_strategy(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['resize_draw_strategy'] = resize_draw_strategy(val) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index c1ea350a2..027fd398e 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -592,7 +592,7 @@ convert_from_opts_window_logo_alpha(PyObject *py_opts, Options *opts) { static void convert_from_python_resize_debounce_time(PyObject *val, Options *opts) { - opts->resize_debounce_time = parse_s_double_to_monotonic_t(val); + resize_debounce_time(val, opts); } static void diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index 935d0e175..2988b5a61 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -255,3 +255,9 @@ tab_bar_margin_height(PyObject *val, Options *opts) { opts->tab_bar_margin_height.outer = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 0)); opts->tab_bar_margin_height.inner = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 1)); } + +static void +resize_debounce_time(PyObject *src, Options *opts) { + opts->resize_debounce_time.on_end = s_double_to_monotonic_t(PyFloat_AsDouble(PyTuple_GET_ITEM(src, 0))); + opts->resize_debounce_time.on_pause = s_double_to_monotonic_t(PyFloat_AsDouble(PyTuple_GET_ITEM(src, 1))); +} diff --git a/kitty/options/types.py b/kitty/options/types.py index 1471b5142..5dcb4e6b2 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -564,7 +564,7 @@ class Options: pointer_shape_when_grabbed: choices_for_pointer_shape_when_grabbed = 'arrow' remember_window_size: bool = True repaint_delay: int = 10 - resize_debounce_time: float = 0.1 + resize_debounce_time: typing.Tuple[float, float] = (0.1, 0.5) resize_draw_strategy: int = 0 resize_in_steps: bool = False scrollback_fill_enlarged_window: bool = False diff --git a/kitty/options/utils.py b/kitty/options/utils.py index 448609d39..fb535c0b0 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -604,6 +604,13 @@ def resize_draw_strategy(x: str) -> int: return cmap.get(x.lower(), 0) +def resize_debounce_time(x: str) -> Tuple[float, float]: + parts = x.split(maxsplit=1) + if len(parts) == 1: + return positive_float(parts[0]), 0.5 + return positive_float(parts[0]), positive_float(parts[1]) + + def visual_window_select_characters(x: str) -> str: import string valid_characters = string.digits + string.ascii_uppercase diff --git a/kitty/state.h b/kitty/state.h index 454aa90d0..0a8d45d7a 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -70,7 +70,7 @@ typedef struct { bool window_alert_on_bell; bool debug_keyboard; bool allow_hyperlinks; - monotonic_t resize_debounce_time; + struct { monotonic_t on_end, on_pause; } resize_debounce_time; MouseShape pointer_shape_when_grabbed; MouseShape default_pointer_shape; MouseShape pointer_shape_when_dragging;