From 9b5f6652185b5376c3e6a85178cb6f41e3608955 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 18 Jan 2024 07:54:15 +0530 Subject: [PATCH] Allow focusing previously active OS windows via nth_os_window Fixes #7009 Fixes #7008 --- docs/changelog.rst | 2 ++ kitty/boss.py | 32 +++++++++++++++++++++++++++++--- kitty/fast_data_types.pyi | 1 + kitty/state.c | 16 +++++++++++++++- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0f8ecdc6a..7a990d4d9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -54,6 +54,8 @@ Detailed list of changes - A new action :ac:`send_key` to simplify mapping key presses to other keys without needing :ac:`send_text` +- Allow focusing previously active OS windows via :ac:`nth_os_window` (:pull:`7009`) + - Wayland: Fix a regression in the previous release that broke copying to clipboard under wl-roots based compositors in some circumstances (:iss:`6890`) diff --git a/kitty/boss.py b/kitty/boss.py index 3eea0d0d5..12f34a117 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -94,6 +94,7 @@ from .fast_data_types import ( is_modifier_key, last_focused_os_window_id, mark_os_window_for_close, + os_window_focus_counters, os_window_font_size, patch_global_colors, redirect_mouse_handling, @@ -1705,12 +1706,37 @@ class Boss: text = '\n'.join(urls) w.paste_text(text) - @ac('win', 'Focus the nth OS window') + @ac('win', ''' + Focus the nth OS window if positive or the previously active OS windows if negative. When the number is larger + than the number of OS windows focus the last OS window. A value of zero will refocus the currently focused OS window, + this is useful if focus is not on any kitty OS window at all, however, it will only work if the window manager + allows applications to grab focus. For example:: + + # focus the previously active OS window + map ctrl+p nth_window -1 + # focus the first OS window + map ctrl+1 nth_window 0 + ''') def nth_os_window(self, num: int = 1) -> None: - if self.os_window_map and num > 0: - ids = list(self.os_window_map.keys()) + if not self.os_window_map: + return + if num == 0: + os_window_id = current_focused_os_window_id() or last_focused_os_window_id() + focus_os_window(os_window_id, True) + elif num > 0: + ids = tuple(self.os_window_map.keys()) os_window_id = ids[min(num, len(ids)) - 1] focus_os_window(os_window_id, True) + elif num < 0: + fc_map = os_window_focus_counters() + s = sorted(fc_map.keys(), key=fc_map.__getitem__, reverse=True) + if not s: + return + try: + os_window_id = s[num-1] + except IndexError: + os_window_id = s[0] + focus_os_window(os_window_id, True) @ac('win', 'Close the currently active OS Window') def close_os_window(self) -> None: diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 4b6c25f2b..c71a59de6 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1557,3 +1557,4 @@ def base64_decode(src: Union[bytes,str]) -> bytes: ... def cocoa_recreate_global_menu() -> None: ... def cocoa_clear_global_shortcuts() -> None: ... def update_pointer_shape(os_window_id: int) -> None: ... +def os_window_focus_counters() -> Dict[int, int]: ... diff --git a/kitty/state.c b/kitty/state.c index 306bb3fa1..caca52610 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -116,7 +116,6 @@ last_focused_os_window_id(void) { return ans; } - static id_type current_focused_os_window_id(void) { for (size_t i = 0; i < global_state.num_os_windows; i++) { @@ -1327,10 +1326,25 @@ KKK(set_active_window) KII(swap_tabs) KK5I(add_borders_rect) +static PyObject* +os_window_focus_counters(PyObject *self UNUSED, PyObject *args UNUSED) { + RAII_PyObject(ans, PyDict_New()); + for (size_t i = 0; i < global_state.num_os_windows; i++) { + OSWindow *w = &global_state.os_windows[i]; + RAII_PyObject(key, PyLong_FromUnsignedLongLong(w->id)); + RAII_PyObject(val, PyLong_FromUnsignedLongLong(w->last_focused_counter)); + if (!key || !val) return NULL; + if (PyDict_SetItem(ans, key, val) != 0) return NULL; + } + Py_INCREF(ans); + return ans; +} + #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef module_methods[] = { + M(os_window_focus_counters, METH_NOARGS), MW(update_pointer_shape, METH_VARARGS), MW(current_os_window, METH_NOARGS), MW(next_window_id, METH_NOARGS),