Fix stale is_selected buffer after screen buffer toggle (#9725)

When toggling between alt and main screen buffers, the selection buffer
(is_selected) was not always re-uploaded to the GPU. This caused a size
mismatch because render_lines_for_screen depends on pixel_scroll_enabled,
which depends on linebuf == main_linebuf.

On alt screen, pixel_scroll is disabled so render_lines = screen->lines.
On main screen, pixel_scroll is enabled so render_lines = screen->lines + 1.
After switching from alt to main, the cell data buffer was re-uploaded with
the larger size (is_dirty = true), but the selection buffer was not
(screen_is_selection_dirty could return false if no selections/urls/extra
cursors were active). The extra row of cells then read out-of-bounds from
the selection buffer, getting garbage data that the shader interpreted as
extra cursor shapes, producing blinking cursor-colored artifacts.

Fix by unconditionally setting extra_cursors.dirty = true after screen
toggle, ensuring the selection buffer is always re-uploaded with the
correct size matching the cell data buffer.

Agent-Logs-Url: https://github.com/kovidgoyal/kitty/sessions/daa73124-4795-4389-aea5-bb5593a26d9f

Co-authored-by: kovidgoyal <1308621+kovidgoyal@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-16 03:13:25 +00:00 committed by GitHub
parent a348cabe23
commit 09e10fea85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1622,10 +1622,13 @@ screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_scree
self->is_dirty = true;
grman_mark_layers_dirty(self->grman);
clear_all_selections(self);
if (self->extra_cursors.count) {
self->extra_cursors.count = 0;
self->extra_cursors.dirty = true;
}
self->extra_cursors.count = 0;
// Force re-upload of the selection buffer as the number of render lines
// changes when pixel_scroll_enabled changes (which depends on which
// linebuf is active). Without this, the selection buffer can be smaller
// than the cell data buffer, causing OOB reads that produce cursor
// artifacts (see #9725).
self->extra_cursors.dirty = true;
global_state.check_for_active_animated_images = true;
}