diff --git a/kitty/boss.py b/kitty/boss.py index 6efa02cb1..cb9bd238a 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -82,8 +82,8 @@ class Boss: load_shader_programs() self.tab_manager = TabManager(opts, args) self.tab_manager.init(startup_session) + self.activate_tab_at = self.tab_manager.activate_tab_at layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper) - self.glfw_window.set_click_cursor(False) @property def current_tab_bar_height(self): diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index cfefd0776..3622599c6 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -31,7 +31,6 @@ extern int pthread_setname_np(const char *name); #define EXTRA_FDS 2 -extern GlobalState global_state; static void (*parse_func)(Screen*, PyObject*); typedef struct { diff --git a/kitty/constants.py b/kitty/constants.py index ea8ac18ba..16db315f9 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -11,7 +11,7 @@ from collections import namedtuple, defaultdict from .fast_data_types import ( GLFW_KEY_LEFT_SHIFT, GLFW_KEY_RIGHT_SHIFT, GLFW_KEY_LEFT_ALT, GLFW_KEY_RIGHT_ALT, GLFW_KEY_LEFT_CONTROL, GLFW_KEY_RIGHT_CONTROL, - GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER) + GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER, set_boss as set_c_boss) appname = 'kitty' version = (0, 3, 0) @@ -57,6 +57,7 @@ def get_boss(): def set_boss(m): get_boss.boss = m + set_c_boss(m) def wakeup(): diff --git a/kitty/glfw.c b/kitty/glfw.c index 39f782424..3a4570ced 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -16,7 +16,6 @@ #error "glfw >= 3.2 required" #endif -extern GlobalState global_state; #define MAX_WINDOWS 256 #define CALLBACK(name, fmt, ...) \ @@ -63,12 +62,29 @@ key_callback(GLFWwindow UNUSED *w, int key, int scancode, int action, int mods) WINDOW_CALLBACK(key_callback, "iiii", key, scancode, action, mods); } +extern void mouse_event(int, int); + static void mouse_button_callback(GLFWwindow *w, int button, int action, int mods) { if (!global_state.mouse_visible) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); global_state.mouse_visible = true; } double now = monotonic(); global_state.last_mouse_activity_at = now; - WINDOW_CALLBACK(mouse_button_callback, "iii", button, action, mods); + if (button >= 0 && (unsigned int)button < sizeof(global_state.mouse_button_pressed)/sizeof(global_state.mouse_button_pressed)) { + global_state.mouse_button_pressed[button] = action == GLFW_PRESS ? true : false; + mouse_event(button, mods); + } + /* WINDOW_CALLBACK(mouse_button_callback, "iii", button, action, mods); */ +} + +static void +cursor_pos_callback(GLFWwindow *w, double x, double y) { + if (!global_state.mouse_visible) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); global_state.mouse_visible = true; } + double now = monotonic(); + global_state.last_mouse_activity_at = now; + global_state.cursor_blink_zero_time = now; + global_state.mouse_x = x; global_state.mouse_y = y; + mouse_event(-1, 0); + /* WINDOW_CALLBACK(cursor_pos_callback, "dd", x, y); */ } static void @@ -79,21 +95,6 @@ scroll_callback(GLFWwindow *w, double xoffset, double yoffset) { WINDOW_CALLBACK(scroll_callback, "dd", xoffset, yoffset); } -static void -cursor_pos_callback(GLFWwindow *w, double x, double y) { - if (!global_state.mouse_visible) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); global_state.mouse_visible = true; } - double now = monotonic(); - global_state.last_mouse_activity_at = now; - global_state.cursor_blink_zero_time = now; - WINDOW_CALLBACK(cursor_pos_callback, "dd", x, y); -} - -void -hide_mouse_cursor() { - glfwSetInputMode(the_window->window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); - global_state.mouse_visible = false; -} - static void window_focus_callback(GLFWwindow UNUSED *w, int focused) { global_state.application_focused = focused ? true : false; @@ -104,6 +105,11 @@ window_focus_callback(GLFWwindow UNUSED *w, int focused) { } // }}} +void +set_click_cursor(bool yes) { + glfwSetCursor(the_window->window, yes ? the_window->click_cursor : the_window->standard_cursor); +} + static PyObject* new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { WindowWrapper *self; @@ -121,6 +127,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self->standard_cursor = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); self->click_cursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR); if (self->standard_cursor == NULL || self->click_cursor == NULL) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create standard mouse cursors"); return NULL; } + set_click_cursor(false); glfwSetFramebufferSizeCallback(self->window, framebuffer_size_callback); glfwSetCharModsCallback(self->window, char_mods_callback); glfwSetKeyCallback(self->window, key_callback); @@ -305,14 +312,6 @@ is_key_pressed(WindowWrapper *self, PyObject *args) { return ans; } -static PyObject* -set_click_cursor(WindowWrapper *self, PyObject *args) { - int c; - if (!PyArg_ParseTuple(args, "p", &c)) return NULL; - glfwSetCursor(self->window, c ? self->click_cursor : self->standard_cursor); - Py_RETURN_NONE; -} - static PyObject* set_clipboard_string(WindowWrapper *self, PyObject *args) { char *title; @@ -424,7 +423,6 @@ static PyMethodDef methods[] = { MND(set_should_close, METH_VARARGS), MND(set_input_mode, METH_VARARGS), MND(is_key_pressed, METH_VARARGS), - MND(set_click_cursor, METH_VARARGS), MND(set_clipboard_string, METH_VARARGS), MND(make_context_current, METH_NOARGS), MND(window_id, METH_NOARGS), diff --git a/kitty/mouse.c b/kitty/mouse.c new file mode 100644 index 000000000..c8d1a6ba2 --- /dev/null +++ b/kitty/mouse.c @@ -0,0 +1,87 @@ +/* + * mouse.c + * Copyright (C) 2017 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "state.h" +#include + +extern void set_click_cursor(bool yes); +static bool has_click_cursor = false; + +static inline bool +contains_mouse(Window *w) { + WindowGeometry *g = &w->geometry; + double x = global_state.mouse_x, y = global_state.mouse_y; + return (w->visible && g->left <= x && x <= g->right && g->top <= y && y <= g->bottom) ? true : false; +} + +static inline bool +cell_for_pos(Window *w, unsigned int *x, unsigned int *y) { + unsigned int qx = (unsigned int)((double)global_state.mouse_x / global_state.cell_width); + unsigned int qy = (unsigned int)((double)global_state.mouse_x / global_state.cell_width); + bool ret = false; + Screen *screen = w->render_data.screen; + if (screen && qx <= screen->columns && qy <= screen->lines) { + *x = qx; *y = qy; ret = true; + } + return ret; +} + +void +handle_move_event(Window *w, int UNUSED button, int UNUSED modifiers) { + unsigned int x, y; + if (cell_for_pos(w, &x, &y)) { + if (x != w->mouse_cell_x || y != w->mouse_cell_y) { + w->mouse_cell_x = x; w->mouse_cell_y = y; + } + } +} + +void +handle_event(Window *w, int button, int modifiers) { + switch(button) { + case -1: + handle_move_event(w, button, modifiers); + break; + case GLFW_MOUSE_BUTTON_LEFT: + case GLFW_MOUSE_BUTTON_RIGHT: + case GLFW_MOUSE_BUTTON_MIDDLE: + case GLFW_MOUSE_BUTTON_4: + case GLFW_MOUSE_BUTTON_5: + break; + default: + break; + } +} + +void handle_tab_bar_mouse(int button, int UNUSED modifiers) { + if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.mouse_button_pressed[button]) return; + PyObject *ret = PyObject_CallMethod(global_state.boss, "activate_tab_at", "d", global_state.mouse_x); + if (ret == NULL) { PyErr_Print(); } + else Py_DECREF(ret); +} + +void +mouse_event(int button, int modifiers) { + bool old_has_click_cursor = has_click_cursor; + bool in_tab_bar = global_state.num_tabs > 1 && global_state.mouse_y >= global_state.viewport_height - global_state.cell_height ? true : false; + has_click_cursor = false; + if (in_tab_bar) { + has_click_cursor = true; + handle_tab_bar_mouse(button, modifiers); + } else { + Tab *t = global_state.tabs + global_state.active_tab; + for (size_t i = 0; i < t->num_windows; i++) { + if (contains_mouse(t->windows + i)) { + handle_event(t->windows + i, button, modifiers); + break; + } + } + } + if (has_click_cursor != old_has_click_cursor) { + set_click_cursor(has_click_cursor); + } +} diff --git a/kitty/screen.c b/kitty/screen.c index 6a8829abe..cfb3a2bea 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -23,7 +23,6 @@ #include "modes.h" #include "wcwidth9.h" -extern GlobalState global_state; static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true}; static Selection EMPTY_SELECTION = {0}; diff --git a/kitty/shaders.c b/kitty/shaders.c index 345ba4d90..9ed3a56cd 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -518,6 +518,8 @@ static void layout_sprite_map(unsigned int cell_width, unsigned int cell_height, PyObject *render_cell) { sprite_map.cell_width = MAX(1, cell_width); sprite_map.cell_height = MAX(1, cell_height); + global_state.cell_width = sprite_map.cell_width; + global_state.cell_height = sprite_map.cell_height; if (sprite_map.max_texture_size == 0) { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(sprite_map.max_texture_size)); glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(sprite_map.max_array_texture_layers)); diff --git a/kitty/state.c b/kitty/state.c index a1f315969..2101ba54c 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -142,17 +142,21 @@ PYWRAP1(set_tab_bar_render_data) { PYWRAP1(set_window_render_data) { #define A(name) &(d.name) +#define B(name) &(g.name) unsigned int window_idx, tab_id; - ScreenRenderData d = {0}; - PA("IIiffffO", &tab_id, &window_idx, A(vao_idx), A(xstart), A(ystart), A(dx), A(dy), A(screen)); + static ScreenRenderData d = {0}; + static WindowGeometry g = {0}; + PA("IIiffffOIIII", &tab_id, &window_idx, A(vao_idx), A(xstart), A(ystart), A(dx), A(dy), A(screen), B(left), B(top), B(right), B(bottom)); WITH_TAB(tab_id); Py_CLEAR(tab->windows[window_idx].render_data.screen); tab->windows[window_idx].render_data = d; + tab->windows[window_idx].geometry = g; Py_INCREF(tab->windows[window_idx].render_data.screen); END_WITH_TAB; Py_RETURN_NONE; #undef A +#undef B } PYWRAP1(update_window_visibility) { @@ -170,8 +174,16 @@ PYWRAP1(set_logical_dpi) { Py_RETURN_NONE; } +PYWRAP1(set_boss) { + Py_CLEAR(global_state.boss); + global_state.boss = args; + Py_INCREF(global_state.boss); + Py_RETURN_NONE; +} + PYWRAP0(destroy_global_data) { Py_CLEAR(global_state.tab_bar_render_data.screen); + Py_CLEAR(global_state.boss); Py_RETURN_NONE; } @@ -211,6 +223,7 @@ static PyMethodDef module_methods[] = { MW(set_tab_bar_render_data, METH_VARARGS), MW(set_window_render_data, METH_VARARGS), MW(update_window_visibility, METH_VARARGS), + MW(set_boss, METH_O), MW(destroy_global_data, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ @@ -224,6 +237,7 @@ init_state(PyObject *module) { global_state.cursor_blink_zero_time = now; global_state.last_mouse_activity_at = now; global_state.mouse_visible = true; + global_state.cell_width = 1; global_state.cell_height = 1; if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } diff --git a/kitty/state.h b/kitty/state.h index 331d7d249..584beaf5a 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -22,11 +22,17 @@ typedef struct { Screen *screen; } ScreenRenderData; +typedef struct { + unsigned int left, top, right, bottom; +} WindowGeometry; + typedef struct { unsigned int id; bool visible; PyObject *title; ScreenRenderData render_data; + unsigned int mouse_cell_x, mouse_cell_y; + WindowGeometry geometry; } Window; typedef struct { @@ -43,9 +49,14 @@ typedef struct { bool application_focused, mouse_visible; double cursor_blink_zero_time, last_mouse_activity_at; double logical_dpi_x, logical_dpi_y; + double mouse_x, mouse_y; + bool mouse_button_pressed[20]; int viewport_width, viewport_height; + unsigned int cell_width, cell_height; PyObject *application_title; + PyObject *boss; } GlobalState; +extern GlobalState global_state; #define EXTERNAL_FUNC(name, ret, ...) typedef ret (*name##_func)(__VA_ARGS__); extern name##_func name #define EXTERNAL_FUNC0(name, ret) typedef ret (*name##_func)(); extern name##_func name diff --git a/kitty/window.py b/kitty/window.py index da5a97ea1..2bcc61c9c 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -121,8 +121,8 @@ class Window: boss.child_monitor.resize_pty(self.id, *current_pty_size) else: sg = self.update_position(new_geometry) - set_window_render_data(self.tab_id, window_idx, self.vao_id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen) - self.geometry = new_geometry + self.geometry = g = new_geometry + set_window_render_data(self.tab_id, window_idx, self.vao_id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen, *g[:4]) def contains(self, x, y): g = self.geometry