From 982b5156e1cf54882765429fdb43c325a762d9cf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 Sep 2025 20:04:42 +0530 Subject: [PATCH] macOS: When toggling in the quick access terminal move it to the currently active screen Fixes #9003 --- docs/changelog.rst | 3 +++ glfw/cocoa_window.m | 44 ++++++++++++++++++++++++++++++++++++++- glfw/glfw3.h | 2 +- glfw/internal.h | 2 +- glfw/window.c | 6 +++--- glfw/wl_window.c | 2 +- glfw/x11_window.c | 4 ++-- kitty/boss.py | 2 +- kitty/fast_data_types.pyi | 2 +- kitty/glfw-wrapper.h | 2 +- kitty/glfw.c | 22 +++++++++++--------- 11 files changed, 69 insertions(+), 22 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0c7281c97..b3dc1dcc9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -155,6 +155,9 @@ Detailed list of changes it was last active on, after full screening some application causes the quick access terminal to appear on the old space (:iss:`8740`) +- macOS: When toggling open the quick access terminal move it to the currently + active monitor (the monitor with the mouse pointer on it) (:iss:`9003`) + - macOS: Fix closing an OS Window when another OS Window is minimized causing the minimized window to be un-minimized (:iss:`8913`) diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index 7361725c1..b597953f2 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -1971,6 +1971,40 @@ screen_for_window_center(_GLFWwindow *window) { return NSScreen.mainScreen; } +static NSScreen* +active_screen(void) { + NSPoint mouseLocation = [NSEvent mouseLocation]; + NSArray *screens = [NSScreen screens]; + for (NSScreen *screen in screens) { + if (NSPointInRect(mouseLocation, [screen frame])) { + return screen; + } + } + // As a fallback, return the main screen + return [NSScreen mainScreen]; +} + +static bool +is_same_screen(NSScreen *screenA, NSScreen * screenB) { + if (screenA == screenB) return true; + NSDictionary *deviceDescriptionA = [screenA deviceDescription]; + NSDictionary *deviceDescriptionB = [screenB deviceDescription]; + NSNumber *screenNumberA = deviceDescriptionA[@"NSScreenNumber"]; + NSNumber *screenNumberB = deviceDescriptionB[@"NSScreenNumber"]; + return [screenNumberA isEqualToNumber:screenNumberB]; +} + +static void +move_window_to_screen(_GLFWwindow *window, NSScreen *target) { + NSRect screenFrame = [target visibleFrame]; + NSRect windowFrame = [window->ns.object frame]; + CGFloat newX = NSMidX(screenFrame) - (windowFrame.size.width / 2.0); + CGFloat newY = NSMidY(screenFrame) - (windowFrame.size.height / 2.0); + NSRect newWindowFrame = NSMakeRect(newX, newY, windowFrame.size.width, windowFrame.size.height); + [window->ns.object setFrame:newWindowFrame display:NO animate:NO]; + if (window->ns.layer_shell.is_active) _glfwPlatformSetLayerShellConfig(window, NULL); +} + const GLFWLayerShellConfig* _glfwPlatformGetLayerShellConfig(_GLFWwindow *window) { return &window->ns.layer_shell.config; @@ -2257,10 +2291,18 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window) } } -void _glfwPlatformShowWindow(_GLFWwindow* window) +void _glfwPlatformShowWindow(_GLFWwindow* window, bool move_to_active_screen) { const bool is_background = window->ns.layer_shell.is_active && window->ns.layer_shell.config.type == GLFW_LAYER_SHELL_BACKGROUND; NSWindow *nw = window->ns.object; + if (move_to_active_screen) { + NSScreen *current_screen = screen_for_window_center(window); + NSScreen *target_screen = active_screen(); + if (!is_same_screen(current_screen, target_screen)) { + debug_rendering("Moving OS window %llu to active screen\n", window->id); + move_window_to_screen(window, target_screen); + } + } if (is_background) { [nw orderBack:nil]; } else { diff --git a/glfw/glfw3.h b/glfw/glfw3.h index b3e9518ee..d2a2f45de 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -3579,7 +3579,7 @@ GLFWAPI void glfwMaximizeWindow(GLFWwindow* window); * * @ingroup window */ -GLFWAPI void glfwShowWindow(GLFWwindow* window); +GLFWAPI void glfwShowWindow(GLFWwindow* window, bool move_to_active_screen); /*! @brief Hides the specified window. * diff --git a/glfw/internal.h b/glfw/internal.h index 18710f642..0ea23e7af 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -740,7 +740,7 @@ monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window); void _glfwPlatformIconifyWindow(_GLFWwindow* window); void _glfwPlatformRestoreWindow(_GLFWwindow* window); void _glfwPlatformMaximizeWindow(_GLFWwindow* window); -void _glfwPlatformShowWindow(_GLFWwindow* window); +void _glfwPlatformShowWindow(_GLFWwindow* window, bool move_to_active_screen); void _glfwPlatformHideWindow(_GLFWwindow* window); void _glfwPlatformRequestWindowAttention(_GLFWwindow* window); int _glfwPlatformWindowBell(_GLFWwindow* window); diff --git a/glfw/window.c b/glfw/window.c index 56b4e5447..652034fbe 100644 --- a/glfw/window.c +++ b/glfw/window.c @@ -279,7 +279,7 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, G { if (wndconfig.visible) { - _glfwPlatformShowWindow(window); + _glfwPlatformShowWindow(window, false); #ifndef _GLFW_WAYLAND if (wndconfig.focused) _glfwPlatformFocusWindow(window); @@ -855,7 +855,7 @@ GLFWAPI void glfwMaximizeWindow(GLFWwindow* handle) _glfwPlatformMaximizeWindow(window); } -GLFWAPI void glfwShowWindow(GLFWwindow* handle) +GLFWAPI void glfwShowWindow(GLFWwindow* handle, bool move_to_active_screen) { _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); @@ -865,7 +865,7 @@ GLFWAPI void glfwShowWindow(GLFWwindow* handle) if (window->monitor) return; - _glfwPlatformShowWindow(window); + _glfwPlatformShowWindow(window, move_to_active_screen); #ifndef _GLFW_WAYLAND if (window->focusOnShow) diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 8e3c8fb7c..1251dbe06 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -1756,7 +1756,7 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window) } } -void _glfwPlatformShowWindow(_GLFWwindow* window) +void _glfwPlatformShowWindow(_GLFWwindow* window, bool move_to_active_screen UNUSED) { if (!window->wl.visible) { if (!window->wl.created) { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 471b9d8a8..2b1f1be4a 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -2061,7 +2061,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconf if (window->monitor) { - _glfwPlatformShowWindow(window); + _glfwPlatformShowWindow(window, false); updateWindowMode(window); acquireMonitor(window); } @@ -2458,7 +2458,7 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window) XFlush(_glfw.x11.display); } -void _glfwPlatformShowWindow(_GLFWwindow* window) +void _glfwPlatformShowWindow(_GLFWwindow* window, bool move_to_active_screen UNUSED) { if (_glfwPlatformWindowVisible(window)) return; diff --git a/kitty/boss.py b/kitty/boss.py index 7dee3ecf9..6532c6572 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -957,7 +957,7 @@ class Boss: def quick_access_terminal_invoked(self) -> None: for os_window_id in self.os_window_map: - toggle_os_window_visibility(os_window_id) + toggle_os_window_visibility(os_window_id, move_to_active_screen=True) def handle_remote_cmd(self, cmd: memoryview, window: Window | None = None) -> None: response = self._handle_remote_command(cmd, window) diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index d0d65bada..79dde112b 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1719,7 +1719,7 @@ def get_docs_ref_map() -> bytes: ... def set_clipboard_data_types(ct: int, mime_types: Tuple[str, ...]) -> None: ... def get_clipboard_mime(ct: int, mime: Optional[str], callback: Callable[[bytes], None]) -> None: ... def run_with_activation_token(func: Callable[[str], None]) -> bool: ... -def toggle_os_window_visibility(os_window_id: int, visible: Literal[True, False] = ...) -> bool: ... +def toggle_os_window_visibility(os_window_id: int, visible: bool | Literal[-1] = -1, move_to_active_screen: bool = False) -> bool: ... def parse_cli_from_spec(args: list[str], names_map: dict[str, OptionDict], defval_map: dict[str, Any]) -> tuple[dict[str, tuple[Any, bool]], list[str]]: ... def layer_shell_config_for_os_window(os_window_id: int) -> dict[str, Any] | None: ... def set_layer_shell_config(os_window_id: int, cfg: LayerShellConfig) -> bool: ... diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 428c8497c..f1553d42e 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1974,7 +1974,7 @@ typedef void (*glfwMaximizeWindow_func)(GLFWwindow*); GFW_EXTERN glfwMaximizeWindow_func glfwMaximizeWindow_impl; #define glfwMaximizeWindow glfwMaximizeWindow_impl -typedef void (*glfwShowWindow_func)(GLFWwindow*); +typedef void (*glfwShowWindow_func)(GLFWwindow*, bool); GFW_EXTERN glfwShowWindow_func glfwShowWindow_impl; #define glfwShowWindow glfwShowWindow_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 48a658a16..beaa6911c 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -586,9 +586,9 @@ scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags, int mo static id_type focus_counter = 0; static void -set_os_window_visibility(OSWindow *w, int set_visible) { +set_os_window_visibility(OSWindow *w, int set_visible, bool move_to_active_screen) { if (set_visible) { - glfwShowWindow(w->handle); + glfwShowWindow(w->handle, move_to_active_screen); w->needs_render = true; w->keep_rendering_till_swap = 256; // try this many times request_tick_callback(); @@ -598,7 +598,7 @@ set_os_window_visibility(OSWindow *w, int set_visible) { static void update_os_window_visibility_based_on_focus(id_type timer_id UNUSED, void*d) { OSWindow * osw = os_window_for_id((uintptr_t)d); - if (osw && osw->hide_on_focus_loss && !osw->is_focused) set_os_window_visibility(osw, 0); + if (osw && osw->hide_on_focus_loss && !osw->is_focused) set_os_window_visibility(osw, 0, false); } static void @@ -1458,7 +1458,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (pret == NULL) return NULL; Py_DECREF(pret); if (x != INT_MIN && y != INT_MIN) glfwSetWindowPos(glfw_window, x, y); - if (!global_state.is_apple && !global_state.is_wayland && window_state != WINDOW_HIDDEN) glfwShowWindow(glfw_window); + if (!global_state.is_apple && !global_state.is_wayland && window_state != WINDOW_HIDDEN) glfwShowWindow(glfw_window, false); if (global_state.is_wayland || global_state.is_apple) { float n_xscale, n_yscale; double n_xdpi, n_ydpi; @@ -1551,7 +1551,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { if (window_state != WINDOW_NORMAL) change_state_for_os_window(w, window_state); #ifdef __APPLE__ // macOS: Show the window after it is ready - if (window_state != WINDOW_HIDDEN) glfwShowWindow(glfw_window); + if (window_state != WINDOW_HIDDEN) glfwShowWindow(glfw_window, false); #endif w->redraw_count = 1; debug("OS Window created\n"); @@ -2550,16 +2550,18 @@ is_layer_shell_supported(PyObject *self UNUSED, PyObject *args UNUSED) { } static PyObject* -toggle_os_window_visibility(PyObject *self UNUSED, PyObject *args) { +toggle_os_window_visibility(PyObject *self UNUSED, PyObject *args, PyObject *kw) { unsigned long long wid; - int set_visible = -1; - if (!PyArg_ParseTuple(args, "K|p", &wid, &set_visible)) return NULL; + int set_visible = -1, move_to_active_screen = 0; + static const char* kwlist[] = {"os_window_id", "visible", "move_to_active_screen", NULL}; + if (!PyArg_ParseTupleAndKeywords( + args, kw, "K|pp", (char**)kwlist, &wid, &set_visible, &move_to_active_screen)) return NULL; OSWindow *w = os_window_for_id(wid); if (!w || !w->handle) Py_RETURN_FALSE; bool is_visible = glfwGetWindowAttrib(w->handle, GLFW_VISIBLE) != 0; if (set_visible == -1) set_visible = !is_visible; else if (set_visible == is_visible) Py_RETURN_FALSE; - set_os_window_visibility(w, set_visible); + set_os_window_visibility(w, set_visible, move_to_active_screen); Py_RETURN_TRUE; } @@ -2595,7 +2597,7 @@ grab_keyboard(PyObject *self UNUSED, PyObject *action) { static PyMethodDef module_methods[] = { METHODB(set_custom_cursor, METH_VARARGS), METHODB(is_css_pointer_name_valid, METH_O), - METHODB(toggle_os_window_visibility, METH_VARARGS), + {"toggle_os_window_visibility", (PyCFunction)(void (*) (void))(toggle_os_window_visibility), METH_VARARGS | METH_KEYWORDS, NULL}, METHODB(layer_shell_config_for_os_window, METH_O), METHODB(set_layer_shell_config, METH_VARARGS), METHODB(grab_keyboard, METH_O),