mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-09 15:08:13 +02:00
Port multi-click handler to C
This commit is contained in:
@@ -21,7 +21,7 @@ from .keys import (
|
||||
)
|
||||
from .session import create_session
|
||||
from .tabs import SpecialWindow, TabManager
|
||||
from .utils import safe_print, get_primary_selection
|
||||
from .utils import safe_print, get_primary_selection, set_primary_selection
|
||||
from .window import load_shader_programs
|
||||
|
||||
|
||||
@@ -288,6 +288,13 @@ class Boss:
|
||||
text = get_primary_selection()
|
||||
self.paste_to_active_window(text)
|
||||
|
||||
def set_primary_selection(self):
|
||||
w = self.active_window
|
||||
if w is not None and not w.destroyed:
|
||||
text = w.text_for_selection()
|
||||
if text:
|
||||
set_primary_selection(text)
|
||||
|
||||
def next_tab(self):
|
||||
self.tab_manager.next_tab()
|
||||
|
||||
|
||||
@@ -38,8 +38,9 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
handle_move_event(Window *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx) {
|
||||
#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
|
||||
|
||||
HANDLER(handle_move_event) {
|
||||
unsigned int x, y;
|
||||
if (cell_for_pos(w, &x, &y)) {
|
||||
Line *line = screen_visual_line(w->render_data.screen, y);
|
||||
@@ -50,8 +51,46 @@ handle_move_event(Window *w, int UNUSED button, int UNUSED modifiers, unsigned i
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
handle_button_event(Window *w, int button, int modifiers, unsigned int window_idx) {
|
||||
static inline void
|
||||
multi_click(Window *w, unsigned int count) {
|
||||
Screen *screen = w->render_data.screen;
|
||||
index_type start, end;
|
||||
bool found_selection = false;
|
||||
switch(count) {
|
||||
case 2:
|
||||
found_selection = screen_selection_range_for_word(screen, w->mouse_cell_x, w->mouse_cell_y, &start, &end);
|
||||
break;
|
||||
case 3:
|
||||
found_selection = screen_selection_range_for_line(screen, w->mouse_cell_y, &start, &end);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (found_selection) {
|
||||
screen_start_selection(screen, start, w->mouse_cell_y);
|
||||
screen_update_selection(screen, end, w->mouse_cell_y, true);
|
||||
call_boss(set_primary_selection, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
HANDLER(add_click) {
|
||||
ClickQueue *q = &w->click_queue;
|
||||
if (q->length == CLICK_QUEUE_SZ) { memmove(q->clicks, q->clicks + 1, sizeof(Click) * (CLICK_QUEUE_SZ - 1)); q->length--; }
|
||||
double now = monotonic();
|
||||
#define N(n) (q->clicks[q->length - n])
|
||||
N(0).at = now; N(0).button = button; N(0).modifiers = modifiers;
|
||||
q->length++;
|
||||
// Now dispatch the multi-click if any
|
||||
if (q->length > 2 && N(1).at - N(3).at <= 2 * OPT(click_interval)) {
|
||||
multi_click(w, 3);
|
||||
q->length = 0;
|
||||
} else if (q->length > 1 && N(1).at - N(2).at <= OPT(click_interval)) {
|
||||
multi_click(w, 2);
|
||||
}
|
||||
#undef N
|
||||
}
|
||||
|
||||
HANDLER(handle_button_event) {
|
||||
Tab *t = global_state.tabs + global_state.active_tab;
|
||||
bool is_release = !global_state.mouse_button_pressed[button];
|
||||
if (window_idx != t->active_window) {
|
||||
@@ -72,8 +111,8 @@ handle_button_event(Window *w, int button, int modifiers, unsigned int window_id
|
||||
if (is_release) {
|
||||
if (modifiers == (int)OPT(open_url_modifiers)) {
|
||||
// TODO: click_url
|
||||
} else if (!modifiers) {
|
||||
// TODO: handle multi click
|
||||
} else {
|
||||
if (is_release) add_click(w, button, modifiers, window_idx);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -85,8 +124,7 @@ handle_button_event(Window *w, int button, int modifiers, unsigned int window_id
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
handle_event(Window *w, int button, int modifiers, unsigned int window_idx) {
|
||||
HANDLER(handle_event) {
|
||||
switch(button) {
|
||||
case -1:
|
||||
handle_move_event(w, button, modifiers, window_idx);
|
||||
@@ -103,7 +141,8 @@ handle_event(Window *w, int button, int modifiers, unsigned int window_idx) {
|
||||
}
|
||||
}
|
||||
|
||||
void handle_tab_bar_mouse(int button, int UNUSED modifiers) {
|
||||
static inline void
|
||||
handle_tab_bar_mouse(int button, int UNUSED modifiers) {
|
||||
if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.mouse_button_pressed[button]) return;
|
||||
call_boss(activate_tab_at, "d", global_state.mouse_x);
|
||||
}
|
||||
|
||||
@@ -1344,41 +1344,40 @@ text_for_selection(Screen *self) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
selection_range_for_line(Screen *self, PyObject *args) {
|
||||
unsigned int y;
|
||||
if (!PyArg_ParseTuple(args, "I", &y)) return NULL;
|
||||
if (y >= self->lines) { PyErr_SetString(PyExc_ValueError, "y larger than lines"); return NULL; }
|
||||
bool
|
||||
screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end) {
|
||||
if (y >= self->lines) { return false; }
|
||||
Line *line = visual_line_(self, y);
|
||||
index_type xlimit = line->xnum, xstart = 0;
|
||||
while (xlimit > 0 && CHAR_IS_BLANK(line->cells[xlimit - 1].ch)) xlimit--;
|
||||
while (xstart < xlimit && CHAR_IS_BLANK(line->cells[xstart].ch)) xstart++;
|
||||
return Py_BuildValue("II", (unsigned int)xstart, (unsigned int)xlimit);
|
||||
*start = xstart; *end = xlimit;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
has_char(int kind, void *data, Py_ssize_t sz, char_type ch) {
|
||||
for (Py_ssize_t i = 0; i < sz; i++) {
|
||||
if (PyUnicode_READ(kind, data, i) == ch) return true;
|
||||
is_opt_word_char(char_type ch) {
|
||||
for (size_t i = 0; i < OPT(select_by_word_characters_count); i++) {
|
||||
if (OPT(select_by_word_characters[i]) == ch) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
selection_range_for_word(Screen *self, PyObject *args) {
|
||||
unsigned int x, y;
|
||||
PyObject *extra_chars;
|
||||
if (!PyArg_ParseTuple(args, "IIU", &x, &y, &extra_chars)) return NULL;
|
||||
if (PyUnicode_READY(extra_chars) != 0) return NULL;
|
||||
if (y >= self->lines) { PyErr_SetString(PyExc_ValueError, "y larger than lines"); return NULL; }
|
||||
if (x >= self->columns) { PyErr_SetString(PyExc_ValueError, "x larger than columns"); return NULL; }
|
||||
bool
|
||||
screen_selection_range_for_word(Screen *self, index_type x, index_type y, index_type *s, index_type *e) {
|
||||
if (y >= self->lines || x >= self->columns) return false;
|
||||
index_type start, end;
|
||||
Line *line = visual_line_(self, y);
|
||||
#define is_ok(x) (is_word_char((line->cells[x].ch) & CHAR_MASK) || has_char(PyUnicode_KIND(extra_chars), PyUnicode_DATA(extra_chars), PyUnicode_GET_LENGTH(extra_chars), (line->cells[x].ch) & CHAR_MASK))
|
||||
if (!is_ok(x)) Py_BuildValue("II", x, x + 1);
|
||||
unsigned int start = x, end = x;
|
||||
while(start > 0 && is_ok(start - 1)) start--;
|
||||
while(end < self->columns - 1 && is_ok(end + 1)) end++;
|
||||
return Py_BuildValue("II", start, end + 1);
|
||||
#define is_ok(x) (is_word_char((line->cells[x].ch) & CHAR_MASK) || is_opt_word_char(line->cells[x].ch & CHAR_MASK))
|
||||
if (!is_ok(x)) {
|
||||
start = x; end = x + 1;
|
||||
} else {
|
||||
start = x, end = x;
|
||||
while(start > 0 && is_ok(start - 1)) start--;
|
||||
while(end < self->columns - 1 && is_ok(end + 1)) end++;
|
||||
}
|
||||
*s = start; *e = end;
|
||||
return true;
|
||||
#undef is_ok
|
||||
}
|
||||
|
||||
@@ -1433,24 +1432,17 @@ screen_is_selection_dirty(Screen *self) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
start_selection(Screen *self, PyObject *args) {
|
||||
unsigned int x, y;
|
||||
if (!PyArg_ParseTuple(args, "II", &x, &y)) return NULL;
|
||||
void
|
||||
screen_start_selection(Screen *self, index_type x, index_type y) {
|
||||
#define A(attr, val) self->selection.attr = val;
|
||||
A(start_x, x); A(end_x, x); A(start_y, y); A(end_y, y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by); A(in_progress, true);
|
||||
#undef A
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
update_selection(Screen *self, PyObject *args) {
|
||||
unsigned int x, y;
|
||||
int ended;
|
||||
if (!PyArg_ParseTuple(args, "IIp", &x, &y, &ended)) return NULL;
|
||||
void
|
||||
screen_update_selection(Screen *self, index_type x, index_type y, bool ended) {
|
||||
self->selection.end_x = x; self->selection.end_y = y; self->selection.end_scrolled_by = self->scrolled_by;
|
||||
if (ended) self->selection.in_progress = false;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@@ -1533,12 +1525,8 @@ static PyMethodDef methods[] = {
|
||||
MND(mark_as_dirty, METH_NOARGS)
|
||||
MND(resize, METH_VARARGS)
|
||||
MND(set_margins, METH_VARARGS)
|
||||
MND(selection_range_for_line, METH_VARARGS)
|
||||
MND(selection_range_for_word, METH_VARARGS)
|
||||
MND(text_for_selection, METH_NOARGS)
|
||||
MND(is_selection_in_progress, METH_NOARGS)
|
||||
MND(start_selection, METH_VARARGS)
|
||||
MND(update_selection, METH_VARARGS)
|
||||
MND(scroll, METH_VARARGS)
|
||||
MND(toggle_alt_screen, METH_NOARGS)
|
||||
MND(reset_callbacks, METH_NOARGS)
|
||||
|
||||
@@ -64,6 +64,10 @@ bool screen_is_selection_dirty(Screen *self);
|
||||
bool screen_invert_colors(Screen *self);
|
||||
void screen_update_cell_data(Screen *self, void *address, size_t sz);
|
||||
bool screen_is_cursor_visible(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, index_type x, index_type y, index_type *start, index_type *end);
|
||||
void screen_start_selection(Screen *self, index_type x, index_type y);
|
||||
void screen_update_selection(Screen *self, index_type x, index_type y, bool ended);
|
||||
Line* screen_visual_line(Screen *self, index_type y);
|
||||
unsigned long screen_current_char_width(Screen *self);
|
||||
#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen);
|
||||
|
||||
@@ -128,6 +128,14 @@ PYWRAP1(set_options) {
|
||||
S(cursor_opacity, PyFloat_AsDouble);
|
||||
S(mouse_hide_wait, PyFloat_AsDouble);
|
||||
S(open_url_modifiers, PyLong_AsUnsignedLong);
|
||||
S(click_interval, PyFloat_AsDouble);
|
||||
PyObject *chars = PyObject_GetAttrString(args, "select_by_word_characters");
|
||||
if (chars == NULL) return NULL;
|
||||
for (size_t i = 0; i < MIN((size_t)PyUnicode_GET_LENGTH(chars), sizeof(OPT(select_by_word_characters))/sizeof(OPT(select_by_word_characters[0]))); i++) {
|
||||
OPT(select_by_word_characters)[i] = PyUnicode_READ(PyUnicode_KIND(chars), PyUnicode_DATA(chars), i);
|
||||
}
|
||||
OPT(select_by_word_characters_count) = PyUnicode_GET_LENGTH(chars);
|
||||
Py_DECREF(chars);
|
||||
#undef S
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
#define OPT(name) global_state.opts.name
|
||||
|
||||
typedef struct {
|
||||
double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait;
|
||||
double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, cursor_opacity;
|
||||
bool enable_audio_bell;
|
||||
CursorShape cursor_shape;
|
||||
double cursor_opacity;
|
||||
unsigned int open_url_modifiers;
|
||||
char_type select_by_word_characters[256]; size_t select_by_word_characters_count;
|
||||
} Options;
|
||||
|
||||
typedef struct {
|
||||
@@ -27,6 +27,17 @@ typedef struct {
|
||||
unsigned int left, top, right, bottom;
|
||||
} WindowGeometry;
|
||||
|
||||
typedef struct {
|
||||
double at;
|
||||
int button, modifiers;
|
||||
} Click;
|
||||
|
||||
#define CLICK_QUEUE_SZ 3
|
||||
typedef struct {
|
||||
Click clicks[CLICK_QUEUE_SZ];
|
||||
unsigned int length;
|
||||
} ClickQueue;
|
||||
|
||||
typedef struct {
|
||||
unsigned int id;
|
||||
bool visible;
|
||||
@@ -34,6 +45,7 @@ typedef struct {
|
||||
ScreenRenderData render_data;
|
||||
unsigned int mouse_cell_x, mouse_cell_y;
|
||||
WindowGeometry geometry;
|
||||
ClickQueue click_queue;
|
||||
} Window;
|
||||
|
||||
typedef struct {
|
||||
|
||||
Reference in New Issue
Block a user