diff --git a/kitty/boss.py b/kitty/boss.py index e7246ad05..838c8694d 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -9,7 +9,8 @@ from .config import MINIMUM_FONT_SIZE from .constants import cell_size, set_boss, viewport_size, wakeup from .fast_data_types import ( GLFW_KEY_DOWN, GLFW_KEY_UP, ChildMonitor, destroy_global_data, - destroy_sprite_map, glfw_post_empty_event, layout_sprite_map + destroy_sprite_map, get_clipboard_string, glfw_post_empty_event, + layout_sprite_map, toggle_fullscreen ) from .fonts.render import prerender, resize_fonts, set_font_family from .keys import get_key_map, get_shortcut @@ -72,8 +73,6 @@ class Boss: cell_size.width, cell_size.height = set_font_family(opts) self.opts, self.args = opts, args self.glfw_window = glfw_window - glfw_window.framebuffer_size_callback = self.on_window_resize - glfw_window.window_focus_callback = self.on_focus initialize_renderer() self.tab_manager = TabManager(opts, args) self.tab_manager.init(startup_session) @@ -111,7 +110,7 @@ class Boss: self.close_window(window) def toggle_fullscreen(self): - self.glfw_window.toggle_fullscreen() + toggle_fullscreen() def start(self): if not getattr(self, 'io_thread_started', False): @@ -119,6 +118,7 @@ class Boss: self.io_thread_started = True def on_window_resize(self, window, w, h): + # WIN: Port this viewport_size.width, viewport_size.height = w, h self.tab_manager.resize() @@ -206,6 +206,7 @@ class Boss: self.dispatch_action(key_action) def on_focus(self, window, focused): + # WIN: Port this self.window_is_focused = focused w = self.active_window if w is not None: @@ -273,7 +274,7 @@ class Boss: w.paste(text) def paste_from_clipboard(self): - text = self.glfw_window.get_clipboard_string() + text = get_clipboard_string() self.paste_to_active_window(text) def paste_from_selection(self): diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index 34161ddf6..7859eb1af 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -158,17 +158,18 @@ cocoa_update_title(PyObject *pytitle) { [title release]; } -PyObject* -cocoa_make_window_resizable(PyObject UNUSED *self, PyObject *window_id) { - NSWindow *window = (NSWindow*)PyLong_AsVoidPtr(window_id); +bool +cocoa_make_window_resizable(void *w) { + NSWindow *window = (NSWindow*)w; @try { [window setStyleMask: [window styleMask] | NSWindowStyleMaskResizable]; } @catch (NSException *e) { return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]); + return false; } - Py_RETURN_NONE; + return true; } @@ -188,7 +189,6 @@ cocoa_get_lang(PyObject UNUSED *self) { static PyMethodDef module_methods[] = { {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, \ - {"cocoa_make_window_resizable", (PyCFunction)cocoa_make_window_resizable, METH_O, ""}, \ {"cocoa_create_global_menu", (PyCFunction)cocoa_create_global_menu, METH_NOARGS, ""}, \ {"cocoa_init", (PyCFunction)cocoa_init, METH_NOARGS, ""}, \ {NULL, NULL, 0, NULL} /* Sentinel */ diff --git a/kitty/data-types.h b/kitty/data-types.h index 2870f814f..75d7eec1c 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -38,7 +38,7 @@ typedef enum MouseTrackingModes { NO_TRACKING, BUTTON_MODE, MOTION_MODE, ANY_MOD typedef enum MouseTrackingProtocols { NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL} MouseTrackingProtocol; typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape; -#define MAX_CHILDREN 256 +#define MAX_CHILDREN 512 #define BLANK_CHAR 0 #define ATTRS_MASK_WITHOUT_WIDTH 0xFFC #define WIDTH_MASK 3 @@ -220,6 +220,7 @@ typedef struct { pthread_t io_thread; } ChildMonitor; + #define clear_sprite_position(cell) (cell).sprite_x = 0; (cell).sprite_y = 0; (cell).sprite_z = 0; #define left_shift_line(line, at, num) \ @@ -275,4 +276,4 @@ void scroll_event(double, double); void set_special_key_combo(int glfw_key, int mods); void on_text_input(unsigned int codepoint, int mods); void on_key_input(int key, int scancode, int action, int mods); -void request_window_attention(); +void request_window_attention(unsigned int); diff --git a/kitty/glfw.c b/kitty/glfw.c index 61c5badde..55b1accf8 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -10,6 +10,7 @@ #if defined(__APPLE__) #define GLFW_EXPOSE_NATIVE_COCOA #include +extern bool cocoa_make_window_resizable(void *w); #endif #if GLFW_VERSION_MAJOR < 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR < 2) @@ -26,155 +27,178 @@ #error "glfw has too many keys, you should increase MAX_KEY_COUNT" #endif -#define CALLBACK(name, fmt, ...) \ - if ((name) != NULL) { \ - PyObject *_pyret = PyObject_CallFunction((name), fmt, __VA_ARGS__); \ - if (_pyret == NULL) PyErr_Print(); \ - else Py_DECREF(_pyret); \ - } - -#define WINDOW_CALLBACK(name, fmt, ...) \ - CALLBACK(the_window->name, "O" fmt, the_window, __VA_ARGS__); - -typedef struct { - int x, y, w, h; - bool is_set; -} GLFWWindowGeometry; - static GLFWcursor *standard_cursor = NULL, *click_cursor = NULL, *arrow_cursor = NULL; -typedef struct { - PyObject_HEAD +#define GLFW_WINDOW(w) ((GLFWwindow*)((w)->handle)) - GLFWwindow *window; - PyObject *framebuffer_size_callback, *window_focus_callback; - GLFWWindowGeometry before_fullscreen; -} WindowWrapper; - -static void update_viewport(GLFWwindow *window) { +static void +update_viewport(OSWindow *window) { int w, h; - glfwGetFramebufferSize(window, &global_state.viewport_width, &global_state.viewport_height); - glfwGetWindowSize(window, &w, &h); - global_state.viewport_x_ratio = (double)global_state.viewport_width / (double)w; - global_state.viewport_y_ratio = (double)global_state.viewport_height / (double)h; + glfwGetFramebufferSize(GLFW_WINDOW(window), &window->viewport_width, &window->viewport_height); + glfwGetWindowSize(GLFW_WINDOW(window), &w, &h); + window->viewport_x_ratio = (double)window->viewport_width / (double)w; + window->viewport_y_ratio = (double)window->viewport_height / (double)h; + window->viewport_size_dirty = true; } // callbacks {{{ -static WindowWrapper* the_window = NULL; + +static inline bool +set_callback_window(GLFWwindow *w) { + global_state.callback_os_window = glfwGetWindowUserPointer(w); + if (global_state.callback_os_window) return true; + for (size_t i = 0; i < global_state.num_os_windows; i++) { + if ((GLFWwindow*)(global_state.os_windows[i].handle) == w) { + global_state.callback_os_window = global_state.os_windows + i; + return true; + } + } + return false; +} + +#define WINDOW_CALLBACK(name, fmt, ...) call_boss(name, "K" fmt, global_state.callback_os_window->window_id, __VA_ARGS__) static void framebuffer_size_callback(GLFWwindow *w, int width, int height) { + if (!set_callback_window(w)) return; if (width > 100 && height > 100) { - update_viewport_size(width, height); - update_viewport(w); - WINDOW_CALLBACK(framebuffer_size_callback, "ii", width, height); + update_viewport(global_state.callback_os_window); + WINDOW_CALLBACK(on_window_resize, "ii", width, height); glfwPostEmptyEvent(); } else fprintf(stderr, "Ignoring resize request for tiny size: %dx%d\n", width, height); + global_state.callback_os_window = NULL; } static void -char_mods_callback(GLFWwindow UNUSED *w, unsigned int codepoint, int mods) { - global_state.cursor_blink_zero_time = monotonic(); +char_mods_callback(GLFWwindow *w, unsigned int codepoint, int mods) { + if (!set_callback_window(w)) return; + global_state.callback_os_window->cursor_blink_zero_time = monotonic(); on_text_input(codepoint, mods); + global_state.callback_os_window = NULL; } static void -key_callback(GLFWwindow UNUSED *w, int key, int scancode, int action, int mods) { - global_state.cursor_blink_zero_time = monotonic(); +key_callback(GLFWwindow *w, int key, int scancode, int action, int mods) { + if (!set_callback_window(w)) return; + global_state.callback_os_window->cursor_blink_zero_time = monotonic(); if (key >= 0 && key <= GLFW_KEY_LAST) { - global_state.is_key_pressed[key] = action == GLFW_RELEASE ? false : true; + global_state.callback_os_window->is_key_pressed[key] = action == GLFW_RELEASE ? false : true; on_key_input(key, scancode, action, mods); } + global_state.callback_os_window = NULL; } static void mouse_button_callback(GLFWwindow *w, int button, int action, int mods) { + if (!set_callback_window(w)) return; if (glfwGetInputMode(w, GLFW_CURSOR) != GLFW_CURSOR_NORMAL) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } double now = monotonic(); - global_state.last_mouse_activity_at = now; - if (button >= 0 && (unsigned int)button < sizeof(global_state.mouse_button_pressed)/sizeof(global_state.mouse_button_pressed[0])) { - global_state.mouse_button_pressed[button] = action == GLFW_PRESS ? true : false; + global_state.callback_os_window->last_mouse_activity_at = now; + if (button >= 0 && (unsigned int)button < sizeof(global_state.callback_os_window->mouse_button_pressed)/sizeof(global_state.callback_os_window->mouse_button_pressed[0])) { + global_state.callback_os_window->mouse_button_pressed[button] = action == GLFW_PRESS ? true : false; mouse_event(button, mods); } + global_state.callback_os_window = NULL; } static void cursor_pos_callback(GLFWwindow *w, double x, double y) { + if (!set_callback_window(w)) return; if (glfwGetInputMode(w, GLFW_CURSOR) != GLFW_CURSOR_NORMAL) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } double now = monotonic(); - global_state.last_mouse_activity_at = now; - global_state.cursor_blink_zero_time = now; - global_state.mouse_x = x * global_state.viewport_x_ratio; - global_state.mouse_y = y * global_state.viewport_y_ratio; + global_state.callback_os_window->last_mouse_activity_at = now; + global_state.callback_os_window->cursor_blink_zero_time = now; + global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio; + global_state.callback_os_window->mouse_y = y * global_state.callback_os_window->viewport_y_ratio; mouse_event(-1, 0); + global_state.callback_os_window = NULL; } static void scroll_callback(GLFWwindow *w, double xoffset, double yoffset) { + if (!set_callback_window(w)) return; if (glfwGetInputMode(w, GLFW_CURSOR) != GLFW_CURSOR_NORMAL) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } double now = monotonic(); - global_state.last_mouse_activity_at = now; + global_state.callback_os_window->last_mouse_activity_at = now; scroll_event(xoffset, yoffset); + global_state.callback_os_window = NULL; } static void -window_focus_callback(GLFWwindow UNUSED *w, int focused) { - global_state.application_focused = focused ? true : false; +window_focus_callback(GLFWwindow *w, int focused) { + if (!set_callback_window(w)) return; + global_state.callback_os_window->is_focused = focused ? true : false; + if (focused) { + global_state.focussed_os_window = global_state.callback_os_window; + } else if (global_state.focussed_os_window == global_state.callback_os_window) global_state.focussed_os_window = NULL; double now = monotonic(); - global_state.last_mouse_activity_at = now; - global_state.cursor_blink_zero_time = now; - WINDOW_CALLBACK(window_focus_callback, "O", focused ? Py_True : Py_False); + global_state.callback_os_window->last_mouse_activity_at = now; + global_state.callback_os_window->cursor_blink_zero_time = now; + WINDOW_CALLBACK(on_focus, "O", focused ? Py_True : Py_False); + global_state.callback_os_window = NULL; } // }}} void set_mouse_cursor(MouseShape type) { - switch(type) { - case HAND: - glfwSetCursor(the_window->window, click_cursor); - break; - case ARROW: - glfwSetCursor(the_window->window, arrow_cursor); - break; - default: - glfwSetCursor(the_window->window, standard_cursor); - break; + if (global_state.callback_os_window) { + GLFWwindow *w = (GLFWwindow*)global_state.callback_os_window->handle; + switch(type) { + case HAND: + glfwSetCursor(w, click_cursor); + break; + case ARROW: + glfwSetCursor(w, arrow_cursor); + break; + default: + glfwSetCursor(w, standard_cursor); + break; + } } } static PyObject* -new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { - WindowWrapper *self; - char *title; +create_new_os_window(PyObject UNUSED *self, PyObject *args) { int width, height; + char *title; if (!PyArg_ParseTuple(args, "iis", &width, &height, &title)) return NULL; - if (the_window != NULL) { PyErr_SetString(PyExc_ValueError, "Only one glfw window is supported"); return NULL; } - self = (WindowWrapper *)type->tp_alloc(type, 0); - if (self != NULL) { - the_window = self; - self->window = glfwCreateWindow(width, height, title, NULL, NULL); - if (self->window == NULL) { Py_CLEAR(self); the_window = NULL; PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; } - update_viewport(self->window); - if (standard_cursor == NULL) { - standard_cursor = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); - click_cursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR); - arrow_cursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - if (standard_cursor == NULL || click_cursor == NULL || arrow_cursor == NULL) { - Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create standard mouse cursors"); return NULL; } - } - set_mouse_cursor(0); - glfwSetFramebufferSizeCallback(self->window, framebuffer_size_callback); - glfwSetCharModsCallback(self->window, char_mods_callback); - glfwSetMouseButtonCallback(self->window, mouse_button_callback); - glfwSetScrollCallback(self->window, scroll_callback); - glfwSetCursorPosCallback(self->window, cursor_pos_callback); - glfwSetKeyCallback(self->window, key_callback); - glfwSetWindowFocusCallback(self->window, window_focus_callback); + if (standard_cursor == NULL) { + standard_cursor = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + click_cursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR); + arrow_cursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + if (standard_cursor == NULL || click_cursor == NULL || arrow_cursor == NULL) { + Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create standard mouse cursors"); return NULL; } } - return (PyObject*)self; + + if (global_state.num_os_windows >= MAX_CHILDREN) { + PyErr_SetString(PyExc_ValueError, "Too many windows"); + return NULL; + } + GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL); + if (glfw_window == NULL) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; } + OSWindow *w = global_state.os_windows + global_state.num_os_windows++; + w->window_id = global_state.window_counter++; + glfwSetWindowUserPointer(glfw_window, w); + w->handle = glfw_window; + glfwSetCursor(glfw_window, standard_cursor); + global_state.callback_os_window->viewport_size_dirty = true; + update_viewport(w); + glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback); + glfwSetCharModsCallback(glfw_window, char_mods_callback); + glfwSetMouseButtonCallback(glfw_window, mouse_button_callback); + glfwSetScrollCallback(glfw_window, scroll_callback); + glfwSetCursorPosCallback(glfw_window, cursor_pos_callback); + glfwSetKeyCallback(glfw_window, key_callback); + glfwSetWindowFocusCallback(glfw_window, window_focus_callback); +#ifdef __APPLE__ + if (OPT(macos_hide_titlebar)) { + if (!cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window))) { PyErr_Print(); } + } +#endif + return PyLong_FromUnsignedLongLong(w->window_id); } - + // Global functions {{{ static void error_callback(int error, const char* description) { @@ -270,131 +294,36 @@ glfw_init_hint_string(PyObject UNUSED *self, PyObject *args) { // }}} -static void -dealloc(WindowWrapper* self) { - the_window = NULL; - Py_CLEAR(self->framebuffer_size_callback); Py_CLEAR(self->window_focus_callback); - if (self->window != NULL) glfwDestroyWindow(self->window); - Py_TYPE(self)->tp_free((PyObject*)self); +static PyObject* +get_clipboard_string(PyObject UNUSED *self) { + OSWindow *w = current_os_window(); + if (w) return Py_BuildValue("s", glfwGetClipboardString(w->handle)); + return Py_BuildValue("s", ""); } static PyObject* -swap_buffers(WindowWrapper *self) { - glfwSwapBuffers(self->window); - Py_RETURN_NONE; -} - -static PyObject* -make_context_current(WindowWrapper *self) { - glfwMakeContextCurrent(self->window); - Py_RETURN_NONE; -} - -static PyObject* -window_id(WindowWrapper *self) { - return PyLong_FromVoidPtr(self->window); -} - -static PyObject* -show(WindowWrapper *self) { - glfwShowWindow(self->window); - Py_RETURN_NONE; -} - -static PyObject* -hide(WindowWrapper *self) { - glfwHideWindow(self->window); - Py_RETURN_NONE; -} - -static PyObject* -should_close(WindowWrapper *self) { - PyObject *ans = glfwWindowShouldClose(self->window) ? Py_True : Py_False; - Py_INCREF(ans); - return ans; -} - -static PyObject* -get_clipboard_string(WindowWrapper *self) { - return Py_BuildValue("s", glfwGetClipboardString(self->window)); -} - -static PyObject* -get_cursor_pos(WindowWrapper *self) { - double x=0, y=0; - glfwGetCursorPos(self->window, &x, &y); - return Py_BuildValue("dd", x, y); -} - -static PyObject* -get_content_scale(WindowWrapper UNUSED *self) { +get_content_scale_for_window(PyObject UNUSED *self) { #ifdef has_content_scale_query + OSWindow *w = global_state.callback_os_window ? global_state.callback_os_window : global_state.os_windows; float xscale, yscale; - glfwGetWindowContentScale(self->window, &xscale, &yscale); + glfwGetWindowContentScale(w->handle, &xscale, &yscale); return Py_BuildValue("ff", xscale, yscale); #else + (void)self; PyErr_SetString(PyExc_NotImplementedError, "glfw version is too old"); return NULL; #endif } static PyObject* -set_should_close(WindowWrapper *self, PyObject *args) { - int c; - if (!PyArg_ParseTuple(args, "p", &c)) return NULL; - glfwSetWindowShouldClose(self->window, c); - Py_RETURN_NONE; -} - -static PyObject* -set_pos(WindowWrapper *self, PyObject *args) { - int x, y; - if (!PyArg_ParseTuple(args, "ii", &x, &y)) return NULL; - glfwSetWindowPos(self->window, x, y); - Py_RETURN_NONE; -} - -static PyObject* -is_key_pressed(WindowWrapper *self, PyObject *args) { - int c; - if (!PyArg_ParseTuple(args, "i", &c)) return NULL; - PyObject *ans = glfwGetKey(self->window, c) == GLFW_PRESS ? Py_True : Py_False; - Py_INCREF(ans); - return ans; -} - -static PyObject* -set_clipboard_string(WindowWrapper *self, PyObject *args) { +set_clipboard_string(PyObject UNUSED *self, PyObject *args) { char *title; if(!PyArg_ParseTuple(args, "s", &title)) return NULL; - glfwSetClipboardString(self->window, title); + OSWindow *w = current_os_window(); + if (w) glfwSetClipboardString(w->handle, title); Py_RETURN_NONE; } -static PyObject* -set_window_icon(WindowWrapper *self, PyObject *args) { - GLFWimage logo; - Py_ssize_t sz; - if(!PyArg_ParseTuple(args, "s#ii", &(logo.pixels), &sz, &(logo.width), &(logo.height))) return NULL; - glfwSetWindowIcon(self->window, 1, &logo); - Py_RETURN_NONE; -} - - -static PyObject* -get_framebuffer_size(WindowWrapper *self) { - int w, h; - glfwGetFramebufferSize(self->window, &w, &h); - return Py_BuildValue("ii", w, h); -} - -static PyObject* -get_window_size(WindowWrapper *self) { - int w, h; - glfwGetWindowSize(self->window, &w, &h); - return Py_BuildValue("ii", w, h); -} - static GLFWmonitor* current_monitor(GLFWwindow *window) { // Find the monitor that has the maximum overlap with this window @@ -431,51 +360,40 @@ current_monitor(GLFWwindow *window) { } static PyObject* -current_monitor_dpi(WindowWrapper *self) { - GLFWmonitor *m = current_monitor(self->window); - if (m == NULL) return NULL; - return get_physical_dpi(m); -} - -static PyObject* -toggle_fullscreen(WindowWrapper *self) { +toggle_fullscreen(PyObject UNUSED *self) { GLFWmonitor *monitor; - if ((monitor = glfwGetWindowMonitor(self->window)) == NULL) { + OSWindow *w = current_os_window(); + if (!w) Py_RETURN_NONE; + if ((monitor = glfwGetWindowMonitor(w->handle)) == NULL) { // make fullscreen - monitor = current_monitor(self->window); + monitor = current_monitor(w->handle); if (monitor == NULL) return NULL; const GLFWvidmode* mode = glfwGetVideoMode(monitor); - self->before_fullscreen.is_set = true; - glfwGetWindowSize(self->window, &self->before_fullscreen.w, &self->before_fullscreen.h); - glfwGetWindowPos(self->window, &self->before_fullscreen.x, &self->before_fullscreen.y); - glfwSetWindowMonitor(self->window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); + w->before_fullscreen.is_set = true; + glfwGetWindowSize(w->handle, &w->before_fullscreen.w, &w->before_fullscreen.h); + glfwGetWindowPos(w->handle, &w->before_fullscreen.x, &w->before_fullscreen.y); + glfwSetWindowMonitor(w->handle, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); Py_RETURN_TRUE; } else { // make windowed const GLFWvidmode* mode = glfwGetVideoMode(monitor); - if (self->before_fullscreen.is_set) glfwSetWindowMonitor(self->window, NULL, self->before_fullscreen.x, self->before_fullscreen.y, self->before_fullscreen.w, self->before_fullscreen.h, mode->refreshRate); - else glfwSetWindowMonitor(self->window, NULL, 0, 0, 600, 400, mode->refreshRate); + if (w->before_fullscreen.is_set) glfwSetWindowMonitor(w->handle, NULL, w->before_fullscreen.x, w->before_fullscreen.y, w->before_fullscreen.w, w->before_fullscreen.h, mode->refreshRate); + else glfwSetWindowMonitor(w->handle, NULL, 0, 0, 600, 400, mode->refreshRate); Py_RETURN_FALSE; } } void -request_window_attention() { +request_window_attention(unsigned int kitty_window_id) { + OSWindow *w = os_window_for_kitty_window(kitty_window_id); + if (w) { #ifdef has_request_attention - glfwRequestWindowAttention(the_window->window); + glfwRequestWindowAttention(w->handle); #endif - glfwPostEmptyEvent(); + glfwPostEmptyEvent(); + } } -#ifdef __APPLE__ -static PyObject* -cocoa_window_id(WindowWrapper *self) { - void *wid = glfwGetCocoaWindow(self->window); - if (wid == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get native window handle"); return NULL; } - return PyLong_FromVoidPtr(wid); -} -#endif - static PyObject* primary_monitor_size(PyObject UNUSED *self) { GLFWmonitor* monitor = glfwGetPrimaryMonitor(); @@ -497,65 +415,24 @@ primary_monitor_content_scale(PyObject UNUSED *self) { } // Boilerplate {{{ -#define MND(name, args) {#name, (PyCFunction)name, args, ""} - -static PyMethodDef methods[] = { - MND(swap_buffers, METH_NOARGS), - MND(get_clipboard_string, METH_NOARGS), - MND(get_cursor_pos, METH_NOARGS), - MND(get_content_scale, METH_NOARGS), - MND(should_close, METH_NOARGS), - MND(get_framebuffer_size, METH_NOARGS), - MND(get_window_size, METH_NOARGS), - MND(toggle_fullscreen, METH_NOARGS), - MND(current_monitor_dpi, METH_NOARGS), -#ifdef __APPLE__ - MND(cocoa_window_id, METH_NOARGS), -#endif - MND(set_should_close, METH_VARARGS), - MND(is_key_pressed, METH_VARARGS), - MND(set_pos, METH_VARARGS), - MND(set_clipboard_string, METH_VARARGS), - MND(make_context_current, METH_NOARGS), - MND(window_id, METH_NOARGS), - MND(show, METH_NOARGS), - MND(hide, METH_NOARGS), - {"set_icon", (PyCFunction)set_window_icon, METH_VARARGS, ""}, - {NULL} /* Sentinel */ -}; - -static PyMemberDef members[] = { -#define CBE(name) {#name, T_OBJECT_EX, offsetof(WindowWrapper, name), 0, #name} - CBE(framebuffer_size_callback), - CBE(window_focus_callback), - {NULL} -#undef CBE -}; - -PyTypeObject WindowWrapper_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "fast_data_types.GLFWWindow", - .tp_basicsize = sizeof(WindowWrapper), - .tp_dealloc = (destructor)dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = "A GLFW window", - .tp_methods = methods, - .tp_members = members, - .tp_new = new, -}; static PyMethodDef module_methods[] = { - {"glfw_init", (PyCFunction)glfw_init, METH_NOARGS, ""}, \ - {"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""}, \ - {"glfw_window_hint", (PyCFunction)glfw_window_hint, METH_VARARGS, ""}, \ - {"glfw_swap_interval", (PyCFunction)glfw_swap_interval, METH_VARARGS, ""}, \ - {"glfw_wait_events", (PyCFunction)glfw_wait_events, METH_VARARGS, ""}, \ - {"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, \ - {"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, \ - {"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, \ - {"glfw_init_hint_string", (PyCFunction)glfw_init_hint_string, METH_VARARGS, ""}, \ - {"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""}, \ - {"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""}, \ + METHODB(create_new_os_window, METH_VARARGS), + METHODB(get_clipboard_string, METH_NOARGS), + METHODB(get_content_scale_for_window, METH_NOARGS), + METHODB(set_clipboard_string, METH_VARARGS), + METHODB(toggle_fullscreen, METH_NOARGS), + {"glfw_init", (PyCFunction)glfw_init, METH_NOARGS, ""}, + {"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""}, + {"glfw_window_hint", (PyCFunction)glfw_window_hint, METH_VARARGS, ""}, + {"glfw_swap_interval", (PyCFunction)glfw_swap_interval, METH_VARARGS, ""}, + {"glfw_wait_events", (PyCFunction)glfw_wait_events, METH_VARARGS, ""}, + {"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, + {"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, + {"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, + {"glfw_init_hint_string", (PyCFunction)glfw_init_hint_string, METH_VARARGS, ""}, + {"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""}, + {"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -563,9 +440,6 @@ static PyMethodDef module_methods[] = { bool init_glfw(PyObject *m) { if (PyModule_AddFunctions(m, module_methods) != 0) return false; - if (PyType_Ready(&WindowWrapper_Type) < 0) return false; - if (PyModule_AddObject(m, "GLFWWindow", (PyObject *)&WindowWrapper_Type) != 0) return 0; - Py_INCREF(&WindowWrapper_Type); #define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false; #ifdef GLFW_X11_WM_CLASS_NAME ADDC(GLFW_X11_WM_CLASS_NAME) diff --git a/kitty/keys.c b/kitty/keys.c index ec8362bb4..9798faa57 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -34,7 +34,7 @@ set_special_key_combo(int glfw_key, int mods) { static inline Window* active_window() { - Tab *t = global_state.tabs + global_state.active_tab; + Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; Window *w = t->windows + t->active_window; if (w->render_data.screen) return w; return NULL; diff --git a/kitty/main.py b/kitty/main.py index c624c03e5..35fae74b9 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -192,13 +192,10 @@ def run_app(opts, args): viewport_size.height = 400 window = GLFWWindow(viewport_size.width, viewport_size.height, args.cls) startup_ctx = init_startup_notification(window) - window.make_context_current() if isosx: - from .fast_data_types import cocoa_make_window_resizable, cocoa_create_global_menu, cocoa_init + from .fast_data_types import cocoa_create_global_menu, cocoa_init cocoa_init() cocoa_create_global_menu() - if opts.macos_hide_titlebar: - cocoa_make_window_resizable(window.cocoa_window_id()) elif not iswayland: # no window icons on wayland with open(logo_data_file, 'rb') as f: window.set_icon(f.read(), 256, 256) diff --git a/kitty/mouse.c b/kitty/mouse.c index d0ebd179b..4d7d19f72 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -86,15 +86,15 @@ encode_mouse_event(Window *w, int button, MouseAction action, int mods) { static inline bool contains_mouse(Window *w) { WindowGeometry *g = &w->geometry; - double x = global_state.mouse_x, y = global_state.mouse_y; + double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y; return (w->visible && g->left <= x && x <= g->right && g->top <= y && y <= g->bottom); } static inline bool cell_for_pos(Window *w, unsigned int *x, unsigned int *y) { WindowGeometry *g = &w->geometry; - unsigned int qx = (unsigned int)((double)(global_state.mouse_x - g->left) / global_state.cell_width); - unsigned int qy = (unsigned int)((double)(global_state.mouse_y - g->top) / global_state.cell_height); + unsigned int qx = (unsigned int)((double)(global_state.callback_os_window->mouse_x - g->left) / global_state.cell_width); + unsigned int qy = (unsigned int)((double)(global_state.callback_os_window->mouse_y - g->top) / global_state.cell_height); bool ret = false; Screen *screen = w->render_data.screen; if (screen && qx <= screen->columns && qy <= screen->lines) { @@ -119,9 +119,9 @@ update_drag(bool from_button, Window *w, bool is_release) { bool -drag_scroll(Window *w) { +drag_scroll(Window *w, OSWindow *frame) { unsigned int margin = global_state.cell_height / 2; - double x = global_state.mouse_x, y = global_state.mouse_y; + double x = frame->mouse_x, y = frame->mouse_y; if (y < w->geometry.top || y > w->geometry.bottom) return false; if (x < w->geometry.left || x > w->geometry.right) return false; bool upwards = y <= (w->geometry.top + margin); @@ -130,7 +130,7 @@ drag_scroll(Window *w) { if (screen->linebuf == screen->main_linebuf) { screen_history_scroll(screen, SCROLL_LINE, upwards); update_drag(false, w, false); - global_state.last_mouse_activity_at = monotonic(); + frame->last_mouse_activity_at = monotonic(); if (mouse_cursor_shape != ARROW) { mouse_cursor_shape = ARROW; set_mouse_cursor(mouse_cursor_shape); @@ -165,7 +165,7 @@ detect_url(Window *w, Screen *screen, unsigned int x, unsigned int y) { HANDLER(handle_move_event) { unsigned int x = 0, y = 0; if (OPT(focus_follows_mouse)) { - Tab *t = global_state.tabs + global_state.active_tab; + Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; if (window_idx != t->active_window) { call_boss(switch_focus_to, "I", window_idx); } @@ -178,7 +178,7 @@ HANDLER(handle_move_event) { bool handle_in_kitty = ( (screen->modes.mouse_tracking_mode == ANY_MODE || (screen->modes.mouse_tracking_mode == MOTION_MODE && button >= 0)) && - !(global_state.is_key_pressed[GLFW_KEY_LEFT_SHIFT] || global_state.is_key_pressed[GLFW_KEY_RIGHT_SHIFT]) + !(global_state.callback_os_window->is_key_pressed[GLFW_KEY_LEFT_SHIFT] || global_state.callback_os_window->is_key_pressed[GLFW_KEY_RIGHT_SHIFT]) ) ? false : true; if (handle_in_kitty) { if (screen->selection.in_progress && button == GLFW_MOUSE_BUTTON_LEFT) { @@ -249,8 +249,8 @@ open_url(Window *w) { } HANDLER(handle_button_event) { - Tab *t = global_state.tabs + global_state.active_tab; - bool is_release = !global_state.mouse_button_pressed[button]; + Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; + bool is_release = !global_state.callback_os_window->mouse_button_pressed[button]; if (window_idx != t->active_window) { call_boss(switch_focus_to, "I", window_idx); } @@ -287,7 +287,7 @@ HANDLER(handle_button_event) { HANDLER(handle_event) { switch(button) { case -1: - for (int i = 0; i < GLFW_MOUSE_BUTTON_5; i++) { if (global_state.mouse_button_pressed[i]) { button = i; break; } } + for (int i = 0; i < GLFW_MOUSE_BUTTON_5; i++) { if (global_state.callback_os_window->mouse_button_pressed[i]) { button = i; break; } } handle_move_event(w, button, modifiers, window_idx); break; case GLFW_MOUSE_BUTTON_LEFT: @@ -304,15 +304,15 @@ HANDLER(handle_event) { 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); + if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.callback_os_window->mouse_button_pressed[button]) return; + call_boss(activate_tab_at, "d", global_state.callback_os_window->mouse_x); } static inline Window* window_for_event(unsigned int *window_idx, bool *in_tab_bar) { - *in_tab_bar = global_state.num_tabs > 1 && global_state.mouse_y >= global_state.viewport_height - global_state.cell_height; + *in_tab_bar = global_state.callback_os_window->num_tabs > 1 && global_state.callback_os_window->mouse_y >= global_state.callback_os_window->viewport_height - global_state.cell_height; if (!*in_tab_bar) { - Tab *t = global_state.tabs + global_state.active_tab; + Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; for (unsigned int i = 0; i < t->num_windows; i++) { if (contains_mouse(t->windows + i) && t->windows[i].render_data.screen) { *window_idx = i; return t->windows + i; diff --git a/kitty/screen.c b/kitty/screen.c index 1e11242e1..26ca2454a 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1007,7 +1007,7 @@ screen_invert_colors(Screen *self) { } void -screen_bell(Screen UNUSED *self) { +screen_bell(Screen *self) { if (global_state.opts.enable_audio_bell) { int fd = open("/dev/tty", O_WRONLY | O_CLOEXEC | O_NOCTTY); if (fd > 0) { @@ -1023,7 +1023,7 @@ screen_bell(Screen UNUSED *self) { if (global_state.opts.visual_bell_duration > 0) { self->start_visual_bell_at = monotonic(); } - request_window_attention(); + request_window_attention(self->window_id); } void diff --git a/kitty/state.c b/kitty/state.c index 6267df27a..4d08756c8 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -31,6 +31,27 @@ static const Window EMPTY_WINDOW = {0}; Tab *tab = global_state.tabs + t; #define END_WITH_TAB break; }} +OSWindow* +current_os_window() { + if (global_state.callback_os_window) return global_state.callback_os_window; + if (global_state.focussed_os_window) return global_state.focussed_os_window; + return global_state.os_windows; +} + +OSWindow* +os_window_for_kitty_window(unsigned int kitty_window_id) { + for (size_t i = 0; i < global_state.num_os_windows; i++) { + OSWindow *w = global_state.os_windows + i; + for (size_t t = 0; t < w->num_tabs; t++) { + Tab *tab = w->tabs + t; + for (size_t c = 0; c < tab->num_windows; c++) { + if (tab->windows[c].id == kitty_window_id) return w; + } + } + } + return NULL; +} + static inline void add_tab(unsigned int id) { ensure_can_add(global_state.tabs, global_state.num_tabs, "Too many children (add_tab)"); @@ -163,6 +184,7 @@ PYWRAP1(set_options) { S(repaint_delay, repaint_delay); S(input_delay, repaint_delay); S(macos_option_as_alt, PyObject_IsTrue); + S(macos_hide_titlebar, PyObject_IsTrue); PyObject *chars = PyObject_GetAttrString(args, "select_by_word_characters"); if (chars == NULL) return NULL; diff --git a/kitty/state.h b/kitty/state.h index 16ff3c5f9..435a00845 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -19,7 +19,7 @@ typedef struct { color_type url_color; double repaint_delay, input_delay; bool focus_follows_mouse; - bool macos_option_as_alt; + bool macos_option_as_alt, macos_hide_titlebar; int adjust_line_height_px; float adjust_line_height_frac; } Options; @@ -64,23 +64,41 @@ typedef struct { #define MAX_KEY_COUNT 512 typedef struct { - Options opts; + int x, y, w, h; + bool is_set; +} OSWindowGeometry; + +typedef struct { + void *handle; + unsigned long long window_id; + OSWindowGeometry before_fullscreen; + int viewport_width, viewport_height; + double viewport_x_ratio, viewport_y_ratio; Tab tabs[MAX_CHILDREN]; unsigned int active_tab, num_tabs; ScreenRenderData tab_bar_render_data; - bool application_focused; + bool is_focused; double cursor_blink_zero_time, last_mouse_activity_at; - double logical_dpi_x, logical_dpi_y; - float font_sz_in_pts; double mouse_x, mouse_y; bool mouse_button_pressed[20]; - int viewport_width, viewport_height; - double viewport_x_ratio, viewport_y_ratio; - unsigned int cell_width, cell_height; - PyObject *application_title; - PyObject *boss; + PyObject *window_title; bool is_key_pressed[MAX_KEY_COUNT]; + bool viewport_size_dirty; +} OSWindow; + + +typedef struct { + Options opts; + + double logical_dpi_x, logical_dpi_y; + unsigned long long window_counter; + float font_sz_in_pts; + unsigned int cell_width, cell_height; + PyObject *boss; + OSWindow os_windows[MAX_CHILDREN]; + size_t num_os_windows; + OSWindow *callback_os_window, *focussed_os_window; } GlobalState; extern GlobalState global_state; @@ -98,7 +116,9 @@ typedef struct { else Py_DECREF(cret_); \ } -bool drag_scroll(Window *); +OSWindow* os_window_for_kitty_window(unsigned int); +OSWindow* current_os_window(); +bool drag_scroll(Window *, OSWindow*); void draw_borders(); void draw_cells(ssize_t, ssize_t, float, float, float, float, Screen *, CursorRenderInfo *); void draw_cursor(CursorRenderInfo *); diff --git a/kitty/window.py b/kitty/window.py index e9044a60e..68b6e92b0 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -15,7 +15,7 @@ from .constants import ( ) from .fast_data_types import ( BRACKETED_PASTE_END, BRACKETED_PASTE_START, CELL_BACKGROUND_PROGRAM, - CELL_FOREGROUND_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, + CELL_FOREGROUND_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, set_clipboard_string, CURSOR_PROGRAM, GRAPHICS_PROGRAM, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen, compile_program, create_cell_vao, create_graphics_vao, glfw_post_empty_event, init_cell_program, init_cursor_program, remove_vao, @@ -275,7 +275,7 @@ class Window: def copy_to_clipboard(self): text = self.text_for_selection() if text: - get_boss().glfw_window.set_clipboard_string(text) + set_clipboard_string(text) def pass_selection_to_program(self, *args): text = self.text_for_selection()