Files
Francis Lavoie 86c5e13816 Wayland: do not ignore the scroll wheel after reversing direction
A high resolution wheel delivers one physical detent as several VALUE120
events, so pending_scroll_pixels accumulates fractions of a line and carries
the remainder to the next event. Detents do not reliably total 120 units, so
that carry is load bearing: it is what lets an undersized detent still scroll
a line.

It breaks down when the scroll direction changes, because the residual then
has the wrong sign and the first detent of the new direction is spent
cancelling it rather than scrolling. Measured on a Logitech MX Master 3 under
GNOME/Wayland, against a terminal program that had requested mouse events,
47% of detents that reversed direction scrolled nothing, against 2% of
detents continuing in the same direction.

Discard the residual when the direction changes, and leave the carry alone
otherwise, so scrolling in a single direction is unaffected.

The accumulator state also moves from Screen to OSWindow, since scroll events
are delivered to an OS Window rather than to a particular terminal window.
2026-08-02 15:47:31 -04:00

351 lines
14 KiB
C

/*
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include "vt-parser.h"
#include "graphics.h"
#include "monotonic.h"
#include "line-buf.h"
#include "history.h"
typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType;
typedef struct DnDCommand {
char type;
unsigned more;
uint32_t client_id;
size_t payload_sz;
int32_t cell_x, cell_y, pixel_x, pixel_y;
uint32_t operation;
} DnDCommand;
typedef struct {
bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM, mCOLOR_PREFERENCE_NOTIFICATION,
mBRACKETED_PASTE, mFOCUS_TRACKING, mDECSACE, mHANDLE_TERMIOS_SIGNALS, mINBAND_RESIZE_NOTIFICATION,
mPASTE_EVENTS, mVISIBILITY_REPORTS;
MouseTrackingMode mouse_tracking_mode;
MouseTrackingProtocol mouse_tracking_protocol;
} ScreenModes;
typedef struct {
unsigned int x, y;
bool in_left_half_of_cell;
} SelectionBoundary;
typedef enum SelectionExtendModes { EXTEND_CELL, EXTEND_WORD, EXTEND_LINE, EXTEND_LINE_FROM_BEGIN, EXTEND_LINE_FROM_POINT, EXTEND_WORD_AND_LINE_FROM_POINT } SelectionExtendMode;
typedef struct {
index_type x, x_limit;
} XRange;
typedef struct {
int y, y_limit;
XRange first, body, last;
} IterationData;
typedef struct {
SelectionBoundary start, end, input_start, input_current;
unsigned int start_scrolled_by, end_scrolled_by;
bool rectangle_select, adjusting_start, is_hyperlink;
IterationData last_rendered;
int sort_y, sort_x;
struct {
SelectionBoundary start, end;
unsigned int scrolled_by;
} initial_extent;
} Selection;
typedef struct {
Selection *items;
size_t count, capacity, last_rendered_count;
bool in_progress, extension_in_progress;
SelectionExtendMode extend_mode;
} Selections;
#define SAVEPOINTS_SZ 256
typedef struct CharsetState {
uint32_t *zero, *one, *current, current_num;
} CharsetState;
typedef struct {
Cursor cursor;
bool mDECOM, mDECAWM, mDECSCNM;
CharsetState charset;
bool is_valid;
} Savepoint;
typedef struct {
PyObject *overlay_text;
CPUCell *cpu_cells;
GPUCell *gpu_cells;
index_type xstart, ynum, xnum, cursor_x, text_len;
bool is_active;
bool is_dirty;
struct {
CPUCell *cpu_cells;
GPUCell *gpu_cells;
Cursor cursor;
} original_line;
struct {
index_type x, y;
} last_ime_pos;
} OverlayLine;
typedef struct ExtraCursor {
CursorShape shape;
index_type cell;
} ExtraCursor;
typedef struct ExtraCursors {
ExtraCursor *locations;
unsigned count, capacity;
struct { DynamicColor cursor, text; } color;
bool dirty;
} ExtraCursors;
typedef struct {
PyObject_HEAD
unsigned int columns, lines, margin_top, margin_bottom, scrolled_by, pixel_scroll_offset_y;
CellPixelSize cell_size;
OverlayLine overlay_line;
id_type window_id;
Selections selections, url_ranges;
struct {
unsigned int scrolled_by;
index_type lines, columns;
color_type cursor_bg;
CursorRenderInfo cursor;
} last_rendered;
bool is_dirty, scroll_changed, reload_all_gpu_data, sgr_blink_was_used;
Cursor *cursor;
Savepoint main_savepoint, alt_savepoint;
PyObject *callbacks, *test_child;
TextCache *text_cache;
LineBuf *linebuf, *main_linebuf, *alt_linebuf;
GraphicsManager *grman, *main_grman, *alt_grman;
HistoryBuf *historybuf;
unsigned int history_line_added_count;
bool *tabstops, *main_tabstops, *alt_tabstops;
ScreenModes modes, saved_modes;
ColorProfile *color_profile;
monotonic_t start_visual_bell_at;
monotonic_t start_drag_overlay_at; // 0 = inactive
uint8_t drag_overlay_quadrant; // 1=left 2=right 3=top 4=bottom 0=none
uint8_t *write_buf;
size_t write_buf_sz, write_buf_used;
pthread_mutex_t write_buf_lock;
CursorRenderInfo cursor_render_info;
DisableLigature disable_ligatures;
PyObject *marker;
bool has_focus;
uint8_t visibility_state; // 0 = unknown, 1 = potentially visible, 2 = not visible
bool has_activity_since_last_focus;
hyperlink_id_type active_hyperlink_id;
HYPERLINK_POOL_HANDLE hyperlink_pool;
ANSIBuf as_ansi_buf;
char_type last_graphic_char;
uint8_t main_key_encoding_flags[8], alt_key_encoding_flags[8], *key_encoding_flags;
struct {
monotonic_t start, duration;
} ignore_bells;
union {
struct {
unsigned int redraws_prompts_at_all: 1;
unsigned int uses_special_keys_for_cursor_movement: 1;
unsigned int supports_click_events: 1;
unsigned int relative_click_events: 1;
};
unsigned int val;
} prompt_settings;
char display_window_char;
ProgressBarState progress_state;
uint8_t progress_percent; // 0-100
monotonic_t progress_indeterminate_anim_at; // animation start time for indeterminate progress
struct {
char ch;
uint8_t *canvas;
size_t requested_height, width_px, height_px;
} last_rendered_window_char;
struct {
unsigned int scrolled_by;
index_type y;
bool is_set;
} last_visited_prompt;
PyObject *last_reported_cwd;
struct {
hyperlink_id_type id;
index_type x, y;
bool has_detected_url;
} current_hyperlink_under_mouse;
struct {
uint8_t stack[16], count;
} main_pointer_shape_stack, alternate_pointer_shape_stack;
Parser *vt_parser;
struct {
monotonic_t expires_at;
Cursor cursor;
ColorProfile color_profile;
bool inverted, cell_data_updated, cursor_visible;
unsigned int scrolled_by;
LineBuf *linebuf;
GraphicsManager *grman;
Selections selections, url_ranges;
ExtraCursors extra_cursors;
} paused_rendering;
CharsetState charset;
ListOfChars *lc;
monotonic_t parsing_at;
ExtraCursors extra_cursors;
struct {
bool active;
DnDCommand metadata;
} dnd_chunking;
} Screen;
#define pixel_scroll_enabled(screen) (OPT(pixel_scroll) && !screen->paused_rendering.expires_at && screen->linebuf == screen->main_linebuf)
#define render_lines_for_screen(screen) (screen->lines + pixel_scroll_enabled(screen))
void screen_align(Screen*);
void screen_garbage_collect_text_cache(Screen *screen);
void screen_restore_cursor(Screen *);
void screen_save_cursor(Screen *);
void screen_restore_modes(Screen *);
void screen_restore_mode(Screen *, unsigned int);
void screen_save_modes(Screen *);
void screen_save_mode(Screen *, unsigned int);
bool write_escape_code_to_child(Screen *self, unsigned char which, const char *data);
void screen_visibility_changed(Screen *self, bool potentially_visible);
void screen_cursor_position(Screen*, unsigned int, unsigned int);
void screen_cursor_move(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/, bool allow_move_to_previous_line);
void screen_erase_in_line(Screen *, unsigned int, bool);
void screen_erase_in_display(Screen *, unsigned int, bool);
void screen_draw_text(Screen *self, const uint32_t *chars, size_t num_chars);
void screen_ensure_bounds(Screen *self, bool use_margins, bool cursor_was_within_margins);
void screen_toggle_screen_buffer(Screen *self, bool, bool);
void screen_normal_keypad_mode(Screen *self);
void screen_alternate_keypad_mode(Screen *self);
void screen_change_default_color(Screen *self, unsigned int which, uint32_t col);
void screen_alignment_display(Screen *self);
void screen_reverse_index(Screen *self);
void screen_index(Screen *self);
void screen_scroll(Screen *self, unsigned int count);
void screen_reverse_scroll(Screen *self, unsigned int count);
void screen_reverse_scroll_and_fill_from_scrollback(Screen *self, unsigned int count);
void screen_reset(Screen *self);
void screen_soft_reset(Screen *self);
void screen_set_tab_stop(Screen *self);
void screen_tab(Screen *self);
void screen_backtab(Screen *self, unsigned int);
void screen_clear_tab_stop(Screen *self, unsigned int how);
void screen_reset_tab_stops(Screen *self);
void screen_set_mode(Screen *self, unsigned int mode);
void screen_reset_mode(Screen *self, unsigned int mode);
void screen_decsace(Screen *self, unsigned int);
void screen_xtversion(Screen *self, unsigned int);
void screen_insert_characters(Screen *self, unsigned int count);
void screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/);
void screen_set_cursor(Screen *self, unsigned int mode, uint8_t secondary);
void screen_cursor_to_column(Screen *self, unsigned int column);
void screen_cursor_down(Screen *self, unsigned int count/*=1*/);
void screen_cursor_forward(Screen *self, unsigned int count/*=1*/);
void screen_cursor_down1(Screen *self, unsigned int count/*=1*/);
void screen_cursor_up1(Screen *self, unsigned int count/*=1*/);
void screen_cursor_to_line(Screen *screen, unsigned int line);
MouseShape screen_pointer_shape(Screen *self);
void screen_insert_lines(Screen *self, unsigned int count/*=1*/);
void screen_delete_lines(Screen *self, unsigned int count/*=1*/);
void screen_repeat_character(Screen *self, unsigned int count);
void screen_delete_characters(Screen *self, unsigned int count);
void screen_erase_characters(Screen *self, unsigned int count);
void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom);
void screen_push_colors(Screen *, unsigned int);
void screen_pop_colors(Screen *, unsigned int);
void screen_report_color_stack(Screen *);
void screen_handle_kitty_dcs(Screen *, const char *callback_name, PyObject *cmd);
void set_title(Screen *self, PyObject*);
void osc_context(Screen *self, PyObject *ctx);
void desktop_notify(Screen *self, unsigned int, PyObject*);
void set_icon(Screen *self, PyObject*);
void set_dynamic_color(Screen *self, unsigned int code, PyObject*);
void color_control(Screen *self, unsigned int code, PyObject*);
void clipboard_control(Screen *self, int code, PyObject*);
void shell_prompt_marking(Screen *self, char *buf);
void file_transmission(Screen *self, PyObject*);
void set_color_table_color(Screen *self, unsigned int code, PyObject*);
void process_cwd_notification(Screen *self, unsigned int code, const char*, size_t);
void screen_request_capabilities(Screen *, char, const char *);
void report_device_attributes(Screen *self, unsigned int UNUSED mode, char start_modifier);
void select_graphic_rendition(Screen *self, int *params, unsigned int count, bool is_group, Region *r);
void report_device_status(Screen *self, unsigned int which, bool UNUSED);
void report_mode_status(Screen *self, unsigned int which, bool);
void screen_apply_selection(Screen *self, void *address, size_t size);
bool screen_is_selection_dirty(Screen *self);
bool screen_has_selection(Screen*);
bool screen_invert_colors(Screen *self);
void screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE, bool cursor_has_moved);
bool screen_is_cursor_visible(const Screen *self);
unsigned screen_multi_cursor_count(const Screen *self);
bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end);
bool screen_selection_range_for_word(Screen *self, const index_type x, const index_type y, index_type *, index_type *, index_type *start, index_type *end, bool);
void screen_start_selection(Screen *self, index_type x, index_type y, bool, bool, SelectionExtendMode);
typedef struct SelectionUpdate {
bool ended, start_extended_selection, set_as_nearest_extend;
} SelectionUpdate;
void screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half, SelectionUpdate upd);
bool screen_history_scroll(Screen *self, int amt, bool upwards);
void screen_history_scroll_to_absolute(Screen *self, double target_scrolled_by);
bool screen_apply_pixel_scroll(Screen *self, double delta_pixels);
PyObject* as_text_history_buf(HistoryBuf *self, PyObject *args, ANSIBuf *output);
Line* screen_visual_line(Screen *self, index_type y);
void screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y);
void set_active_hyperlink(Screen*, char*, char*);
hyperlink_id_type screen_mark_hyperlink(Screen*, index_type, index_type);
void screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload);
void screen_handle_multicell_command(Screen *self, const MultiCellCommand *cmd, const uint8_t *payload);
void screen_handle_dnd_command(Screen *self, const DnDCommand *cmd, const uint8_t *payload);
bool screen_open_url(Screen* self, const char* callback);
bool screen_set_last_visited_prompt(Screen*, index_type);
bool screen_select_cmd_output(Screen*, index_type);
void screen_dirty_sprite_positions(Screen *self);
void screen_rescale_images(Screen *self);
void screen_report_size(Screen *, unsigned which, unsigned modifier);
void screen_manipulate_title_stack(Screen *, unsigned int op, unsigned int which);
bool screen_is_overlay_active(Screen *self);
void screen_update_overlay_text(Screen *self, const char *utf8_text);
void screen_set_key_encoding_flags(Screen *self, uint32_t val, uint32_t how);
void screen_push_key_encoding_flags(Screen *self, uint32_t val);
void screen_pop_key_encoding_flags(Screen *self, uint32_t num);
uint8_t screen_current_key_encoding_flags(Screen *self);
void screen_modify_other_keys(Screen *self, unsigned, unsigned);
void screen_report_key_encoding_flags(Screen *self);
int screen_detect_url(Screen *screen, unsigned int x, unsigned int y);
int screen_cursor_at_a_shell_prompt(const Screen *);
bool screen_prompt_supports_click_events(const Screen *, bool *is_relative);
bool screen_fake_move_cursor_to_position(Screen *, index_type x, index_type y);
bool screen_send_signal_for_key(Screen *, char key);
bool get_line_edge_colors(Screen *self, color_type *left, color_type *right);
bool get_line_edge_colors_at_row(Screen *self, index_type y, color_type *left, color_type *right, bool *left_is_default, bool *right_is_default);
bool parse_sgr(Screen *screen, const uint8_t *buf, unsigned int num, const char *report_name, bool is_deccara);
bool screen_pause_rendering(Screen *self, bool pause, int for_in_ms);
void screen_check_pause_rendering(Screen *self, monotonic_t now);
void screen_designate_charset(Screen *self, uint32_t which, uint32_t as);
void screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_params);
#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen);
DECLARE_CH_SCREEN_HANDLER(bell)
DECLARE_CH_SCREEN_HANDLER(backspace)
DECLARE_CH_SCREEN_HANDLER(tab)
DECLARE_CH_SCREEN_HANDLER(linefeed)
DECLARE_CH_SCREEN_HANDLER(carriage_return)
#undef DECLARE_CH_SCREEN_HANDLER