Clenaup previous PR

Make pixel scrolling the default. Fix memory leak where blank line was
not being deallocated in Screen destructor.
This commit is contained in:
Kovid Goyal 2026-01-08 11:01:34 +05:30
parent 0168d19e57
commit da45f7eeaa
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
8 changed files with 36 additions and 53 deletions

View file

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

View file

@ -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 highprecision input device such as a touchpad, you can enable
smooth, perpixel 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

View file

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

View file

@ -589,15 +589,14 @@
'''
)
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() # }}}

View file

@ -1159,6 +1159,9 @@ def open_url_with(self, val: str, ans: dict[str, typing.Any]) -> None:
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 @@ def text_fg_override_threshold(self, val: str, ans: dict[str, typing.Any]) -> No
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)

View file

@ -403,6 +403,7 @@
'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 @@
'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 @@ def __setattr__(self, key: str, val: typing.Any) -> typing.Any:
})
secret_options = ('remote_control_password', 'file_transfer_confirmation_bypass')
secret_options = ('remote_control_password', 'file_transfer_confirmation_bypass')

View file

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

View file

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