diff --git a/docs/changelog.rst b/docs/changelog.rst index 9aa982573..99957ddf9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -153,7 +153,9 @@ Detailed list of changes 0.45.1 [future] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Wayland: Add momentum scrolling for touchpads +- Pixel scrolling for the kitty scrollback buffer controlled via :opt:`pixel_scroll` (:pull:`9330`) + +- Wayland: Add momentum scrolling in the kitty scrollback buffer for touchpads and touchscreens - choose-files kitten: Fix JXL image preview not working (:iss:`9323`) diff --git a/docs/overview.rst b/docs/overview.rst index 4cdf1f09e..be315b8d5 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -206,16 +206,6 @@ displays an interactive :opt:`scrollbar` along the right edge of the window that shows your current position in the scrollback. You can click and drag the scrollbar to quickly navigate through the history. -If you use a high‑precision input device such as a touchpad, you can enable -smooth, per‑pixel scrollback with :opt:`pixel_scroll`. For example, add this to -your :file:`kitty.conf`:: - - pixel_scroll yes - -You can further tune the sensitivity with :opt:`touch_scroll_multiplier`. Note -that this only affects scrolling kitty's own scrollback, not applications -running inside the terminal that handle their own scrolling. - However, |kitty| has an extra, neat feature. Sometimes you need to explore the scrollback buffer in more detail, maybe search for some text or refer to it side-by-side while typing in a follow-up command. |kitty| allows you to do this by pressing diff --git a/kitty/mouse.c b/kitty/mouse.c index 4be87271a..026a28d77 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -1292,8 +1292,8 @@ scroll_event(const GLFWScrollEvent *ev) { break; } if (ev->y_offset != 0.0) { - if (!screen->modes.mouse_tracking_mode && pixel_scroll_enabled_for_screen(screen) && (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES || ev->offset_type == GLFW_SCROLL_OFFEST_V120)) { - double delta_pixels = 0.0; + if (screen->modes.mouse_tracking_mode == NO_TRACKING && pixel_scroll_enabled_for_screen(screen) && (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES || ev->offset_type == GLFW_SCROLL_OFFEST_V120)) { + double delta_pixels; if (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES) { delta_pixels = ev->y_offset * OPT(touch_scroll_multiplier); } else { diff --git a/kitty/options/definition.py b/kitty/options/definition.py index baf3ef143..19d997ba0 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -589,15 +589,14 @@ Wayland. Use negative numbers to change scroll direction. ''' ) -opt('pixel_scroll', 'no', - option_type='to_bool', ctype='bool', - long_text=''' -Enable per-pixel scrolling for high precision input devices (for example -touchpads). When enabled, kitty's own scrollback can move by sub-line increments -instead of only whole lines. This does not affect applications running inside -the terminal (for example full-screen TUIs) that handle scrolling themselves. -''' - ) +opt('pixel_scroll', 'yes', option_type='to_bool', ctype='bool', long_text=''' +Enable per-pixel scrolling, in the kitty scrollback buffer, when using high precision +input devices (for example touchpads). When enabled, kitty's own scrollback will move +by sub-line increments instead of only whole lines. This does not affect applications +running inside the terminal (for example full-screen TUIs) that handle scrolling +themselves. +''') + egr() # }}} diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 284b2ff6b..516b6e408 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -1159,6 +1159,9 @@ class Parser: def paste_actions(self, val: str, ans: dict[str, typing.Any]) -> None: ans['paste_actions'] = paste_actions(val) + def pixel_scroll(self, val: str, ans: dict[str, typing.Any]) -> None: + ans['pixel_scroll'] = to_bool(val) + def placement_strategy(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_placement_strategy: @@ -1389,9 +1392,6 @@ class Parser: def touch_scroll_multiplier(self, val: str, ans: dict[str, typing.Any]) -> None: ans['touch_scroll_multiplier'] = float(val) - def pixel_scroll(self, val: str, ans: dict[str, typing.Any]) -> None: - ans['pixel_scroll'] = to_bool(val) - def transparent_background_colors(self, val: str, ans: dict[str, typing.Any]) -> None: ans['transparent_background_colors'] = transparent_background_colors(val) diff --git a/kitty/options/types.py b/kitty/options/types.py index 525911322..4c632feb5 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -403,6 +403,7 @@ option_names = ( 'notify_on_cmd_finish', 'open_url_with', 'paste_actions', + 'pixel_scroll', 'placement_strategy', 'pointer_shape_when_dragging', 'pointer_shape_when_grabbed', @@ -464,7 +465,6 @@ option_names = ( 'text_composition_strategy', 'text_fg_override_threshold', 'touch_scroll_multiplier', - 'pixel_scroll', 'transparent_background_colors', 'undercurl_style', 'underline_exclusion', @@ -593,6 +593,7 @@ class Options: notify_on_cmd_finish: NotifyOnCmdFinish = NotifyOnCmdFinish(when='never', duration=5.0, action='notify', cmdline=(), clear_on=('focus', 'next')) open_url_with: list[str] = ['default'] paste_actions: frozenset[str] = frozenset({'confirm', 'quote-urls-at-prompt'}) + pixel_scroll: bool = True placement_strategy: choices_for_placement_strategy = 'center' pointer_shape_when_dragging: tuple[str, str] = ('beam', 'crosshair') pointer_shape_when_grabbed: choices_for_pointer_shape_when_grabbed = 'arrow' @@ -652,7 +653,6 @@ class Options: text_composition_strategy: str = 'platform' text_fg_override_threshold: tuple[float, typing.Literal['%', 'ratio']] = (0.0, '%') touch_scroll_multiplier: float = 1.0 - pixel_scroll: bool = False transparent_background_colors: tuple[tuple[kitty.fast_data_types.Color, float], ...] = () undercurl_style: choices_for_undercurl_style = 'thin-sparse' underline_exclusion: tuple[float, typing.Literal['', 'px', 'pt']] = (1.0, '') @@ -1113,4 +1113,4 @@ special_colors = frozenset({ }) -secret_options = ('remote_control_password', 'file_transfer_confirmation_bypass') +secret_options = ('remote_control_password', 'file_transfer_confirmation_bypass') \ No newline at end of file diff --git a/kitty/screen.c b/kitty/screen.c index e86d77b3e..fb1753329 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -89,6 +89,18 @@ init_overlay_line(Screen *self, index_type columns, bool keep_active) { return true; } +static bool +init_blank_line(Screen *self) { + const size_t sz = (sizeof(CPUCell) + sizeof(GPUCell)) * self->columns; + self->blank_line_buffer = PyMem_Realloc(self->blank_line_buffer, sz); + if (!self->blank_line_buffer) return false; + memset(self->blank_line_buffer, 0, sz); + self->blank_line = (Line){.cpu_cells=self->blank_line_buffer, + .gpu_cells=(GPUCell*)((CPUCell*)self->blank_line_buffer + self->columns), + .xnum=self->columns, .text_cache=self->text_cache}; + return true; +} + static void deactivate_overlay_line(Screen *self); static void update_overlay_position(Screen *self); static void render_overlay_line(Screen *self, Line *line, FONTS_DATA_HANDLE fonts_data); @@ -162,15 +174,7 @@ new_screen_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { init_tabstops(self->alt_tabstops, self->columns); self->key_encoding_flags = self->main_key_encoding_flags; if (!init_overlay_line(self, self->columns, false)) { Py_CLEAR(self); return NULL; } - self->blank_line_cpu = PyMem_Calloc(self->columns, sizeof(CPUCell)); - self->blank_line_gpu = PyMem_Calloc(self->columns, sizeof(GPUCell)); - if (!self->blank_line_cpu || !self->blank_line_gpu) { Py_CLEAR(self); return NULL; } - self->blank_line.cpu_cells = self->blank_line_cpu; - self->blank_line.gpu_cells = self->blank_line_gpu; - self->blank_line.xnum = self->columns; - self->blank_line.ynum = 0; - self->blank_line.attrs.val = 0; - self->blank_line.text_cache = self->text_cache; + if (!init_blank_line(self)) { Py_CLEAR(self); return NULL; } self->hyperlink_pool = alloc_hyperlink_pool(); if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); } self->as_ansi_buf.hyperlink_pool = self->hyperlink_pool; @@ -557,19 +561,8 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { which.is_beyond_content = num_content_lines_before > 0 && self->cursor->y >= num_content_lines_before; \ which.num_content_lines = num_content_lines_after; \ } - // Resize overlay line - if (!init_overlay_line(self, columns, true)) return false; - PyMem_Free(self->blank_line_cpu); - PyMem_Free(self->blank_line_gpu); - self->blank_line_cpu = PyMem_Calloc(columns, sizeof(CPUCell)); - self->blank_line_gpu = PyMem_Calloc(columns, sizeof(GPUCell)); - if (!self->blank_line_cpu || !self->blank_line_gpu) return false; - self->blank_line.cpu_cells = self->blank_line_cpu; - self->blank_line.gpu_cells = self->blank_line_gpu; - self->blank_line.xnum = columns; - self->blank_line.ynum = 0; - self->blank_line.attrs.val = 0; - self->blank_line.text_cache = self->text_cache; + // Resize overlay and blank lines + if (!init_overlay_line(self, columns, true) || !init_blank_line(self)) return false; // Resize main linebuf RAII_PyObject(prompt_copy, NULL); @@ -683,6 +676,7 @@ dealloc(Screen* self) { PyMem_Free(self->overlay_line.gpu_cells); PyMem_Free(self->overlay_line.original_line.cpu_cells); PyMem_Free(self->overlay_line.original_line.gpu_cells); + PyMem_Free(self->blank_line_buffer); Py_CLEAR(self->overlay_line.overlay_text); PyMem_Free(self->main_tabstops); Py_CLEAR(self->paused_rendering.linebuf); diff --git a/kitty/screen.h b/kitty/screen.h index d6883e13f..75ad91a99 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -109,9 +109,7 @@ typedef struct { double pixel_scroll_offset_y; CellPixelSize cell_size; OverlayLine overlay_line; - Line blank_line; - CPUCell *blank_line_cpu; - GPUCell *blank_line_gpu; + Line blank_line; void *blank_line_buffer; id_type window_id; Selections selections, url_ranges; struct {