Implement the mouse tracking protocol

This commit is contained in:
Kovid Goyal 2016-12-02 14:23:38 +05:30
parent 991d01bb68
commit d3fd0646fb
7 changed files with 227 additions and 83 deletions

View File

@ -263,11 +263,14 @@ class CharGrid:
self.current_cursor = Cursor(c.x, c.y, c.hidden, c.shape, c.color, c.blink) self.current_cursor = Cursor(c.x, c.y, c.hidden, c.shape, c.color, c.blink)
def cell_for_pos(self, x, y): def cell_for_pos(self, x, y):
return int(x // cell_size.width), int(y // cell_size.height) x, y = int(x // cell_size.width), int(y // cell_size.height)
if 0 <= x < self.screen.columns and 0 <= y < self.screen.lines:
return x, y
return None, None
def update_drag(self, is_press, x, y): def update_drag(self, is_press, x, y):
x, y = self.cell_for_pos(x, y) x, y = self.cell_for_pos(x, y)
if 0 <= x < self.screen.columns and 0 <= y < self.screen.lines: if x is not None:
ps = None ps = None
with self.buffer_lock: with self.buffer_lock:
if is_press: if is_press:
@ -287,24 +290,26 @@ class CharGrid:
def has_url_at(self, x, y): def has_url_at(self, x, y):
x, y = self.cell_for_pos(x, y) x, y = self.cell_for_pos(x, y)
l = self.screen_line(y) if x is not None:
if l is not None: l = self.screen_line(y)
text = l.as_base_text() if l is not None:
for m in self.url_pat.finditer(text): text = l.as_base_text()
if m.start() <= x < m.end(): for m in self.url_pat.finditer(text):
return True if m.start() <= x < m.end():
return True
return False return False
def click_url(self, x, y): def click_url(self, x, y):
x, y = self.cell_for_pos(x, y) x, y = self.cell_for_pos(x, y)
l = self.screen_line(y) if x is not None:
if l is not None: l = self.screen_line(y)
text = l.as_base_text() if l is not None:
for m in self.url_pat.finditer(text): text = l.as_base_text()
if m.start() <= x < m.end(): for m in self.url_pat.finditer(text):
url = ''.join(l[i] for i in range(*m.span())).rstrip('.') if m.start() <= x < m.end():
if url: url = ''.join(l[i] for i in range(*m.span())).rstrip('.')
open_url(url, self.opts.open_url_with) if url:
open_url(url, self.opts.open_url_with)
def screen_line(self, y): def screen_line(self, y):
' Return the Line object corresponding to the yth line on the rendered screen ' ' Return the Line object corresponding to the yth line on the rendered screen '
@ -318,36 +323,37 @@ class CharGrid:
def multi_click(self, count, x, y): def multi_click(self, count, x, y):
x, y = self.cell_for_pos(x, y) x, y = self.cell_for_pos(x, y)
line = self.screen_line(y) if x is not None:
if line is not None and 0 <= x < self.screen.columns and count in (2, 3): line = self.screen_line(y)
s = self.current_selection if line is not None and count in (2, 3):
s.start_scrolled_by = s.end_scrolled_by = self.scrolled_by s = self.current_selection
s.start_y = s.end_y = y s.start_scrolled_by = s.end_scrolled_by = self.scrolled_by
s.in_progress = False s.start_y = s.end_y = y
if count == 3: s.in_progress = False
for i in range(self.screen.columns): if count == 3:
if line[i] != ' ': for i in range(self.screen.columns):
s.start_x = i if line[i] != ' ':
break s.start_x = i
else: break
s.start_x = 0 else:
for i in range(self.screen.columns): s.start_x = 0
c = self.screen.columns - 1 - i for i in range(self.screen.columns):
if line[c] != ' ': c = self.screen.columns - 1 - i
s.end_x = c if line[c] != ' ':
break s.end_x = c
else: break
s.end_x = self.screen.columns - 1 else:
elif count == 2: s.end_x = self.screen.columns - 1
i = x elif count == 2:
pat = re.compile(r'\w') i = x
while i >= 0 and pat.match(line[i]) is not None: pat = re.compile(r'\w')
i -= 1 while i >= 0 and pat.match(line[i]) is not None:
s.start_x = i if i == x else i + 1 i -= 1
i = x s.start_x = i if i == x else i + 1
while i < self.screen.columns and pat.match(line[i]) is not None: i = x
i += 1 while i < self.screen.columns and pat.match(line[i]) is not None:
s.end_x = i if i == x else i - 1 i += 1
s.end_x = i if i == x else i - 1
def text_for_selection(self, sel=None): def text_for_selection(self, sel=None):
start, end = (sel or self.current_selection).limits(self.scrolled_by) start, end = (sel or self.current_selection).limits(self.scrolled_by)

View File

@ -73,7 +73,13 @@ PyInit_fast_data_types(void) {
PyModule_AddIntMacro(m, DECOM); PyModule_AddIntMacro(m, DECOM);
PyModule_AddIntMacro(m, IRM); PyModule_AddIntMacro(m, IRM);
PyModule_AddIntMacro(m, DATA_CELL_SIZE); PyModule_AddIntMacro(m, DATA_CELL_SIZE);
PyModule_AddIntMacro(m, ANY_MODE);
PyModule_AddIntMacro(m, MOTION_MODE);
PyModule_AddIntMacro(m, BUTTON_MODE);
PyModule_AddIntMacro(m, SGR_PROTOCOL);
PyModule_AddIntMacro(m, NORMAL_PROTOCOL);
PyModule_AddIntMacro(m, URXVT_PROTOCOL);
PyModule_AddIntMacro(m, UTF8_PROTOCOL);
} }
return m; return m;

View File

@ -23,6 +23,13 @@ typedef uint32_t combining_type;
typedef unsigned int index_type; typedef unsigned int index_type;
#define ERROR_PREFIX "[PARSE ERROR]" #define ERROR_PREFIX "[PARSE ERROR]"
#define ANY_MODE 3
#define MOTION_MODE 2
#define BUTTON_MODE 1
#define NORMAL_PROTOCOL 0
#define UTF8_PROTOCOL 1
#define SGR_PROTOCOL 2
#define URXVT_PROTOCOL 3
#define CELL_SIZE (sizeof(char_type) + sizeof(color_type) + sizeof(decoration_type) + sizeof(combining_type)) #define CELL_SIZE (sizeof(char_type) + sizeof(color_type) + sizeof(decoration_type) + sizeof(combining_type))
// The data cell size must be a multiple of 3 // The data cell size must be a multiple of 3
@ -227,8 +234,8 @@ PyTypeObject ChangeTracker_Type;
typedef struct { typedef struct {
bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM,
mBRACKETED_PASTE, mFOCUS_TRACKING, mMOUSE_BUTTON_TRACKING, mBRACKETED_PASTE, mFOCUS_TRACKING;
mMOUSE_MOTION_TRACKING, mMOUSE_SGR_MODE, mMOUSE_MOVE_TRACKING; unsigned long mouse_tracking_mode, mouse_tracking_protocol;
} ScreenModes; } ScreenModes;
PyTypeObject ScreenModes_Type; PyTypeObject ScreenModes_Type;

View File

@ -63,7 +63,9 @@
#define MOUSE_MOTION_TRACKING (1002 << 5) #define MOUSE_MOTION_TRACKING (1002 << 5)
#define MOUSE_MOVE_TRACKING (1003 << 5) #define MOUSE_MOVE_TRACKING (1003 << 5)
#define FOCUS_TRACKING (1004 << 5) #define FOCUS_TRACKING (1004 << 5)
#define MOUSE_UTF8_MODE (1005 << 5)
#define MOUSE_SGR_MODE (1006 << 5) #define MOUSE_SGR_MODE (1006 << 5)
#define MOUSE_URXVT_MODE (1015 << 5)
// Alternate screen buffer // Alternate screen buffer
#define ALTERNATE_SCREEN (1049 << 5) #define ALTERNATE_SCREEN (1049 << 5)

62
kitty/mouse.py Normal file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from .fast_data_types import (
GLFW_MOUSE_BUTTON_2, GLFW_MOUSE_BUTTON_3, GLFW_MOD_ALT, GLFW_MOD_CONTROL,
GLFW_MOD_SHIFT, GLFW_MOUSE_BUTTON_4, GLFW_MOUSE_BUTTON_5, SGR_PROTOCOL,
GLFW_MOUSE_BUTTON_1, URXVT_PROTOCOL, UTF8_PROTOCOL
)
PRESS, RELEASE, DRAG, MOVE = range(4)
SHIFT_INDICATOR = 1 << 2
ALT_INDICATOR = 1 << 3
CONTROL_INDICATOR = 1 << 4
MOTION_INDICATOR = 1 << 5
EXTRA_BUTTON_INDICATOR = 1 << 6
cb_map = {
GLFW_MOUSE_BUTTON_1: 0,
GLFW_MOUSE_BUTTON_2: 0b1,
GLFW_MOUSE_BUTTON_3: 0b10,
GLFW_MOUSE_BUTTON_4: EXTRA_BUTTON_INDICATOR,
GLFW_MOUSE_BUTTON_5: EXTRA_BUTTON_INDICATOR | 0b1
}
def encode_mouse_event(tracking_mode, tracking_protocol, button, action, mods, x, y):
x, y = x + 1, y + 1 # One based indexing
cb = 0
if action is MOVE:
if tracking_protocol != SGR_PROTOCOL:
cb = 0b11
else:
cb = cb_map.get(button)
if cb is None:
return
if action in (DRAG, MOVE):
cb |= MOTION_INDICATOR
elif action is RELEASE:
if tracking_protocol != SGR_PROTOCOL:
cb = 0b11
if mods & GLFW_MOD_SHIFT:
cb |= SHIFT_INDICATOR
if mods & GLFW_MOD_ALT:
cb |= ALT_INDICATOR
if mods & GLFW_MOD_CONTROL:
cb |= CONTROL_INDICATOR
ans = None
if tracking_protocol == SGR_PROTOCOL:
ans = '\033[<%d;%d;%d%s' % (cb, x, y, 'm' if action is RELEASE else 'M')
ans = ans.encode('ascii')
elif tracking_protocol == URXVT_PROTOCOL:
ans = '\033[%d;%d;%dM' % (cb + 32, x, y)
ans = ans.encode('ascii')
elif tracking_protocol == UTF8_PROTOCOL:
ans = bytearray([0o33, ord('['), cb + 32])
ans.extend(chr(x + 32).encode('utf-8') + chr(y + 32).encode('utf-8'))
ans = bytes(ans)
else:
if x <= 223 and y <= 223:
ans = bytearray([0o33, ord('['), cb + 32, x + 32, y + 32])
return ans

View File

@ -344,17 +344,23 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
case name: \ case name: \
self->modes.m##name = val; break; self->modes.m##name = val; break;
#define MOUSE_MODE(name, attr, value) \
case name: \
self->modes.attr = val ? value : 0; break;
bool private; bool private;
switch(mode) { switch(mode) {
SIMPLE_MODE(LNM) SIMPLE_MODE(LNM)
SIMPLE_MODE(IRM) SIMPLE_MODE(IRM)
SIMPLE_MODE(DECARM) SIMPLE_MODE(DECARM)
SIMPLE_MODE(BRACKETED_PASTE) SIMPLE_MODE(BRACKETED_PASTE)
SIMPLE_MODE(MOUSE_BUTTON_TRACKING)
SIMPLE_MODE(MOUSE_MOVE_TRACKING)
SIMPLE_MODE(MOUSE_MOTION_TRACKING)
SIMPLE_MODE(MOUSE_SGR_MODE)
SIMPLE_MODE(FOCUS_TRACKING) SIMPLE_MODE(FOCUS_TRACKING)
MOUSE_MODE(MOUSE_BUTTON_TRACKING, mouse_tracking_mode, BUTTON_MODE)
MOUSE_MODE(MOUSE_MOTION_TRACKING, mouse_tracking_mode, MOTION_MODE)
MOUSE_MODE(MOUSE_MOVE_TRACKING, mouse_tracking_mode, ANY_MODE)
MOUSE_MODE(MOUSE_UTF8_MODE, mouse_tracking_protocol, UTF8_PROTOCOL)
MOUSE_MODE(MOUSE_SGR_MODE, mouse_tracking_protocol, SGR_PROTOCOL)
MOUSE_MODE(MOUSE_URXVT_MODE, mouse_tracking_protocol, URXVT_PROTOCOL)
case DECCKM: case DECCKM:
case DECSCLM: case DECSCLM:
@ -403,6 +409,7 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
fprintf(stderr, "%s %s %u %s\n", ERROR_PREFIX, "Unsupported screen mode: ", mode, private ? "(private)" : ""); fprintf(stderr, "%s %s %u %s\n", ERROR_PREFIX, "Unsupported screen mode: ", mode, private ? "(private)" : "");
} }
#undef SIMPLE_MODE #undef SIMPLE_MODE
#undef MOUSE_MODE
} }
void screen_set_mode(Screen *self, unsigned int mode) { void screen_set_mode(Screen *self, unsigned int mode) {
@ -993,12 +1000,17 @@ WRAP1B(erase_in_display, 0)
MODE_GETTER(in_bracketed_paste_mode, BRACKETED_PASTE) MODE_GETTER(in_bracketed_paste_mode, BRACKETED_PASTE)
MODE_GETTER(focus_tracking_enabled, FOCUS_TRACKING) MODE_GETTER(focus_tracking_enabled, FOCUS_TRACKING)
MODE_GETTER(mouse_button_tracking_enabled, MOUSE_BUTTON_TRACKING)
MODE_GETTER(mouse_motion_tracking_enabled, MOUSE_MOTION_TRACKING)
MODE_GETTER(mouse_move_tracking_enabled, MOUSE_MOVE_TRACKING)
MODE_GETTER(mouse_in_sgr_mode, MOUSE_SGR_MODE)
MODE_GETTER(auto_repeat_enabled, DECARM) MODE_GETTER(auto_repeat_enabled, DECARM)
static PyObject*
mouse_tracking_mode(Screen *self) {
return PyLong_FromUnsignedLong(self->modes.mouse_tracking_mode);
}
static PyObject*
mouse_tracking_protocol(Screen *self) {
return PyLong_FromUnsignedLong(self->modes.mouse_tracking_protocol);
}
static PyObject* static PyObject*
cursor_up(Screen *self, PyObject *args) { cursor_up(Screen *self, PyObject *args) {
@ -1100,7 +1112,8 @@ static PyObject* mark_as_dirty(Screen *self) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyObject* current_char_width(Screen *self) { static PyObject*
current_char_width(Screen *self) {
#define current_char_width_doc "The width of the character under the cursor" #define current_char_width_doc "The width of the character under the cursor"
unsigned long ans = 1; unsigned long ans = 1;
if (self->cursor->x < self->columns - 1 && self->cursor->y < self->lines) { if (self->cursor->x < self->columns - 1 && self->cursor->y < self->lines) {
@ -1109,6 +1122,13 @@ static PyObject* current_char_width(Screen *self) {
return PyLong_FromUnsignedLong(ans); return PyLong_FromUnsignedLong(ans);
} }
static PyObject*
is_main_linebuf(Screen *self) {
PyObject *ans = (self->linebuf == self->main_linebuf) ? Py_True : Py_False;
Py_INCREF(ans);
return ans;
}
WRAP2(cursor_position, 1, 1) WRAP2(cursor_position, 1, 1)
#define COUNT_WRAP(name) WRAP1(name, 1) #define COUNT_WRAP(name) WRAP1(name, 1)
@ -1132,6 +1152,7 @@ static PyMethodDef methods[] = {
MND(reset_mode, METH_VARARGS) MND(reset_mode, METH_VARARGS)
MND(reset, METH_NOARGS) MND(reset, METH_NOARGS)
MND(reset_dirty, METH_NOARGS) MND(reset_dirty, METH_NOARGS)
MND(is_main_linebuf, METH_NOARGS)
MND(consolidate_changes, METH_NOARGS) MND(consolidate_changes, METH_NOARGS)
MND(cursor_back, METH_VARARGS) MND(cursor_back, METH_VARARGS)
MND(erase_in_line, METH_VARARGS) MND(erase_in_line, METH_VARARGS)
@ -1144,6 +1165,8 @@ static PyMethodDef methods[] = {
MND(change_scrollback_size, METH_VARARGS) MND(change_scrollback_size, METH_VARARGS)
MND(erase_characters, METH_VARARGS) MND(erase_characters, METH_VARARGS)
MND(cursor_up, METH_VARARGS) MND(cursor_up, METH_VARARGS)
MND(mouse_tracking_mode, METH_NOARGS)
MND(mouse_tracking_protocol, METH_NOARGS)
MND(cursor_up1, METH_VARARGS) MND(cursor_up1, METH_VARARGS)
MND(cursor_down, METH_VARARGS) MND(cursor_down, METH_VARARGS)
MND(cursor_down1, METH_VARARGS) MND(cursor_down1, METH_VARARGS)
@ -1165,10 +1188,6 @@ static PyMethodDef methods[] = {
MND(in_bracketed_paste_mode, METH_NOARGS) MND(in_bracketed_paste_mode, METH_NOARGS)
MND(auto_repeat_enabled, METH_NOARGS) MND(auto_repeat_enabled, METH_NOARGS)
MND(focus_tracking_enabled, METH_NOARGS) MND(focus_tracking_enabled, METH_NOARGS)
MND(mouse_button_tracking_enabled, METH_NOARGS)
MND(mouse_motion_tracking_enabled, METH_NOARGS)
MND(mouse_move_tracking_enabled, METH_NOARGS)
MND(mouse_in_sgr_mode, METH_NOARGS)
{"update_cell_data", (PyCFunction)screen_update_cell_data, METH_VARARGS, ""}, {"update_cell_data", (PyCFunction)screen_update_cell_data, METH_VARARGS, ""},
{"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""}, {"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},

View File

@ -4,7 +4,7 @@
import os import os
import weakref import weakref
from collections import deque from collections import deque, defaultdict
from functools import partial from functools import partial
from time import monotonic from time import monotonic
@ -13,9 +13,12 @@ from .constants import wakeup, tab_manager, appname, WindowGeometry
from .fast_data_types import ( from .fast_data_types import (
BRACKETED_PASTE_START, BRACKETED_PASTE_END, Screen, read_bytes_dump, BRACKETED_PASTE_START, BRACKETED_PASTE_END, Screen, read_bytes_dump,
read_bytes, GLFW_MOD_SHIFT, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, read_bytes, GLFW_MOD_SHIFT, GLFW_MOUSE_BUTTON_1, GLFW_PRESS,
GLFW_MOUSE_BUTTON_MIDDLE, GLFW_RELEASE, GLFW_KEY_LEFT_SHIFT, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_RELEASE, glfw_post_empty_event,
GLFW_KEY_RIGHT_SHIFT, glfw_post_empty_event GLFW_MOUSE_BUTTON_5, ANY_MODE, MOTION_MODE, GLFW_KEY_LEFT_SHIFT,
GLFW_KEY_RIGHT_SHIFT, GLFW_KEY_UP, GLFW_KEY_DOWN, GLFW_MOUSE_BUTTON_4
) )
from .keys import key_map
from .mouse import encode_mouse_event, PRESS, RELEASE, MOVE, DRAG
from .terminfo import get_capabilities from .terminfo import get_capabilities
from .utils import sanitize_title, get_primary_selection, parse_color_set from .utils import sanitize_title, get_primary_selection, parse_color_set
@ -24,6 +27,7 @@ class Window:
def __init__(self, tab, child, opts, args): def __init__(self, tab, child, opts, args):
self.tabref = weakref.ref(tab) self.tabref = weakref.ref(tab)
self.mouse_button_pressed = defaultdict(lambda: False)
self.destroyed = False self.destroyed = False
self.click_queue = deque(maxlen=3) self.click_queue = deque(maxlen=3)
self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0) self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0)
@ -149,11 +153,13 @@ class Window:
glfw_post_empty_event() glfw_post_empty_event()
def on_mouse_button(self, window, button, action, mods): def on_mouse_button(self, window, button, action, mods):
handle_event = mods == GLFW_MOD_SHIFT or not self.screen.mouse_button_tracking_enabled() self.mouse_button_pressed[button] = action == GLFW_PRESS
if handle_event: mode = self.screen.mouse_tracking_mode()
send_event = mods != GLFW_MOD_SHIFT and mode > 0
x, y = window.get_cursor_pos()
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top)
if not send_event:
if button == GLFW_MOUSE_BUTTON_1: if button == GLFW_MOUSE_BUTTON_1:
x, y = window.get_cursor_pos()
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top)
self.char_grid.update_drag(action == GLFW_PRESS, x, y) self.char_grid.update_drag(action == GLFW_PRESS, x, y)
if action == GLFW_RELEASE: if action == GLFW_RELEASE:
if mods == self.char_grid.opts.open_url_modifiers: if mods == self.char_grid.opts.open_url_modifiers:
@ -164,27 +170,62 @@ class Window:
if action == GLFW_RELEASE: if action == GLFW_RELEASE:
self.paste_from_selection() self.paste_from_selection()
else: else:
x, y = window.get_cursor_pos() if action == GLFW_RELEASE and button == GLFW_MOUSE_BUTTON_1 and mods == self.char_grid.opts.open_url_modifiers:
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top) self.char_grid.click_url(x, y)
x, y = self.char_grid.cell_for_pos(x, y) x, y = self.char_grid.cell_for_pos(x, y)
if x is not None:
ev = encode_mouse_event(mode, self.screen.mouse_tracking_protocol(),
button, PRESS if action == GLFW_PRESS else RELEASE, mods, x, y)
if ev:
self.write_to_child(ev)
def on_mouse_move(self, window, x, y): def on_mouse_move(self, window, x, y):
button = None
for b in range(0, GLFW_MOUSE_BUTTON_5 + 1):
if self.mouse_button_pressed[b]:
button = b
break
action = MOVE if button is None else DRAG
mode = self.screen.mouse_tracking_mode()
send_event = (mode == ANY_MODE or (mode == MOTION_MODE and button is not None)) and not (
window.is_key_pressed(GLFW_KEY_LEFT_SHIFT) or window.is_key_pressed(GLFW_KEY_RIGHT_SHIFT))
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top) x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top)
if self.char_grid.current_selection.in_progress:
self.char_grid.update_drag(None, x, y)
tm = tab_manager() tm = tab_manager()
tm.queue_ui_action(tab_manager().change_mouse_cursor, self.char_grid.has_url_at(x, y)) tm.queue_ui_action(tab_manager().change_mouse_cursor, self.char_grid.has_url_at(x, y))
if send_event:
x, y = self.char_grid.cell_for_pos(x, y)
if x is not None:
ev = encode_mouse_event(mode, self.screen.mouse_tracking_protocol(),
button, action, 0, x, y)
if ev:
self.write_to_child(ev)
else:
if self.char_grid.current_selection.in_progress:
self.char_grid.update_drag(None, x, y)
def on_mouse_scroll(self, window, x, y): def on_mouse_scroll(self, window, x, y):
handle_event = ( s = int(round(y * self.opts.wheel_scroll_multiplier))
window.is_key_pressed(GLFW_KEY_LEFT_SHIFT) or if abs(s) < 0:
window.is_key_pressed(GLFW_KEY_RIGHT_SHIFT) or return
not self.screen.mouse_button_tracking_enabled()) upwards = s > 0
if handle_event: if self.screen.is_main_linebuf():
s = int(round(y * self.opts.wheel_scroll_multiplier)) self.char_grid.scroll(abs(s), upwards)
if abs(s) > 0: glfw_post_empty_event()
self.char_grid.scroll(abs(s), s > 0) else:
glfw_post_empty_event() mode = self.screen.mouse_tracking_mode()
send_event = mode > 0
if send_event:
x, y = window.get_cursor_pos()
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top)
x, y = self.char_grid.cell_for_pos(x, y)
if x is not None:
ev = encode_mouse_event(mode, self.screen.mouse_tracking_protocol(),
GLFW_MOUSE_BUTTON_4 if upwards else GLFW_MOUSE_BUTTON_5, PRESS, 0, x, y)
if ev:
self.write_to_child(ev)
else:
k = key_map[GLFW_KEY_UP if upwards else GLFW_KEY_DOWN]
self.write_to_child(k * abs(s))
# actions {{{ # actions {{{
@ -239,7 +280,7 @@ class Window:
glfw_post_empty_event() glfw_post_empty_event()
# }}} # }}}
def dump_commands(self, *a): def dump_commands(self, *a): # {{{
if a: if a:
if a[0] == 'draw': if a[0] == 'draw':
if a[1] is None: if a[1] is None:
@ -253,3 +294,4 @@ class Window:
print('draw', ''.join(self.draw_dump_buf)) print('draw', ''.join(self.draw_dump_buf))
self.draw_dump_buf = [] self.draw_dump_buf = []
print(*a) print(*a)
# }}}