diff --git a/docs/changelog.rst b/docs/changelog.rst index b42a55432..69638b953 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -154,6 +154,10 @@ Detailed list of changes - Automatic color scheme switching: Fix title bar and scroll bar colors not being updated (:iss:`9167`) +- macOS: Fix cycle through OS windows only swapping between the two most recent + OS Windows. Also add a cycle through OS Windows backwards action. + (:iss:`9215`) + 0.44.0 [2025-11-03] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/boss.py b/kitty/boss.py index 3611fe9eb..b275946d6 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1318,7 +1318,11 @@ class Boss: @ac('misc', 'Cycle through OS windows on macOS') def macos_cycle_through_os_windows(self) -> None: - macos_cycle_through_os_windows() + macos_cycle_through_os_windows(False) + + @ac('misc', 'Cycle through OS windows backwards on macOS') + def macos_cycle_through_os_windows_backwards(self) -> None: + macos_cycle_through_os_windows(True) @ac('misc', 'Hide macOS kitty application') def hide_macos_app(self) -> None: diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index a13511914..4ed473a19 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -1241,6 +1241,7 @@ process_cocoa_pending_actions(void) { if (cocoa_pending_actions[RELOAD_CONFIG]) { call_boss(load_config_file, NULL); } if (cocoa_pending_actions[TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY]) { call_boss(toggle_macos_secure_keyboard_entry, NULL); } if (cocoa_pending_actions[MACOS_CYCLE_THROUGH_OS_WINDOWS]) { call_boss(macos_cycle_through_os_windows, NULL); } + if (cocoa_pending_actions[MACOS_CYCLE_THROUGH_OS_WINDOWS_BACKWARDS]) { call_boss(macos_cycle_through_os_windows_backwards, NULL); } if (cocoa_pending_actions[TOGGLE_FULLSCREEN]) { call_boss(toggle_fullscreen, NULL); } if (cocoa_pending_actions[OPEN_KITTY_WEBSITE]) { call_boss(open_kitty_website, NULL); } if (cocoa_pending_actions[HIDE]) { call_boss(hide_macos_app, NULL); } diff --git a/kitty/cocoa_window.h b/kitty/cocoa_window.h index a723c5a53..b9d916380 100644 --- a/kitty/cocoa_window.h +++ b/kitty/cocoa_window.h @@ -31,6 +31,7 @@ typedef enum { RELOAD_CONFIG, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY, MACOS_CYCLE_THROUGH_OS_WINDOWS, + MACOS_CYCLE_THROUGH_OS_WINDOWS_BACKWARDS, TOGGLE_FULLSCREEN, OPEN_KITTY_WEBSITE, HIDE, @@ -51,7 +52,7 @@ void cocoa_system_beep(const char*); void cocoa_set_activation_policy(bool); bool cocoa_alt_option_key_pressed(unsigned long); void cocoa_toggle_secure_keyboard_entry(void); -void cocoa_cycle_through_os_windows(void); +void cocoa_cycle_through_os_windows(bool); void cocoa_hide(void); void cocoa_clear_global_shortcuts(void); void cocoa_hide_others(void); diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index 9d20468bc..7f3d947c0 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -259,6 +259,7 @@ PENDING(clear_last_command, CLEAR_LAST_COMMAND) PENDING(reload_config, RELOAD_CONFIG) PENDING(toggle_macos_secure_keyboard_entry, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY) PENDING(macos_cycle_through_os_windows, MACOS_CYCLE_THROUGH_OS_WINDOWS) +PENDING(macos_cycle_through_os_windows_backwards, MACOS_CYCLE_THROUGH_OS_WINDOWS_BACKWARDS) PENDING(toggle_fullscreen, TOGGLE_FULLSCREEN) PENDING(open_kitty_website, OPEN_KITTY_WEBSITE) PENDING(hide_macos_app, HIDE) @@ -320,7 +321,7 @@ typedef struct { GlobalShortcut clear_terminal_and_scrollback, clear_screen, clear_scrollback, clear_last_command; GlobalShortcut toggle_macos_secure_keyboard_entry, toggle_fullscreen, open_kitty_website; GlobalShortcut hide_macos_app, hide_macos_other_apps, minimize_macos_window, quit; - GlobalShortcut macos_cycle_through_os_windows; + GlobalShortcut macos_cycle_through_os_windows, macos_cycle_through_os_windows_backwards; } GlobalShortcuts; static GlobalShortcuts global_shortcuts; @@ -338,7 +339,8 @@ cocoa_set_global_shortcut(PyObject *self UNUSED, PyObject *args) { else Q(clear_terminal_and_scrollback); else Q(clear_scrollback); else Q(clear_screen); else Q(clear_last_command); else Q(reload_config); else Q(toggle_macos_secure_keyboard_entry); else Q(toggle_fullscreen); else Q(open_kitty_website); else Q(hide_macos_app); else Q(hide_macos_other_apps); - else Q(minimize_macos_window); else Q(quit); else Q(macos_cycle_through_os_windows); + else Q(minimize_macos_window); else Q(quit); + else Q(macos_cycle_through_os_windows); else Q(macos_cycle_through_os_windows_backwards); #undef Q if (gs == NULL) { PyErr_SetString(PyExc_KeyError, "Unknown shortcut name"); return NULL; } int cocoa_mods; @@ -808,6 +810,7 @@ cocoa_create_global_menu(void) { keyEquivalent:@""]; [windowMenu addItem:[NSMenuItem separatorItem]]; MENU_ITEM(windowMenu, @"Cycle Through OS Windows", macos_cycle_through_os_windows); + MENU_ITEM(windowMenu, @"Cycle Through OS Windows backwards", macos_cycle_through_os_windows_backwards); [windowMenu addItem:[NSMenuItem separatorItem]]; [windowMenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) @@ -925,20 +928,24 @@ cocoa_toggle_secure_keyboard_entry(void) { } void -cocoa_cycle_through_os_windows(void) { - NSArray *allWindows = [NSApp orderedWindows]; +cocoa_cycle_through_os_windows(bool backwards) { + NSArray *allWindows = [NSApp windows]; if (allWindows.count < 2) return; NSMutableArray *filteredWindows = [NSMutableArray array]; - for (NSWindow *window in allWindows) { + for (NSWindow *window in allWindows) { NSRect windowFrame = [window frame]; // Exclude zero size windows which are likely zombie windows from the Tahoe bug - if (windowFrame.size.width > 0 && windowFrame.size.height > 0) [filteredWindows addObject:window]; + if (windowFrame.size.width > 0 && windowFrame.size.height > 0 && !window.isMiniaturized && window.isVisible) [filteredWindows addObject:window]; } if (filteredWindows.count < 2) return; NSWindow *keyWindow = [NSApp keyWindow]; NSUInteger index = [filteredWindows indexOfObject:keyWindow]; - if (index < 0) return; - NSUInteger nextIndex = (index + 1) % filteredWindows.count; + NSUInteger nextIndex = 0; + if (index != NSNotFound) { + if (backwards) { + nextIndex = (index == 0) ? [filteredWindows count] - 1 : index - 1; + } else nextIndex = (index + 1) % filteredWindows.count; + } NSWindow *nextWindow = filteredWindows[nextIndex]; [nextWindow makeKeyAndOrderFront:nil]; } diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index f2f5fa7c4..54064b70e 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -948,7 +948,7 @@ def toggle_secure_input() -> None: pass -def macos_cycle_through_os_windows() -> None: +def macos_cycle_through_os_windows(backwards: bool) -> None: pass diff --git a/kitty/glfw.c b/kitty/glfw.c index b62d4ae47..b641633d9 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1922,9 +1922,11 @@ toggle_secure_input(PYNOARG) { } static PyObject* -macos_cycle_through_os_windows(PYNOARG) { +macos_cycle_through_os_windows(PyObject *self UNUSED, PyObject *backwards) { #ifdef __APPLE__ - cocoa_cycle_through_os_windows(); + cocoa_cycle_through_os_windows(PyObject_IsTrue(backwards)); +#else + (void)backwards; #endif Py_RETURN_NONE; } @@ -2616,7 +2618,7 @@ static PyMethodDef module_methods[] = { METHODB(set_clipboard_data_types, METH_VARARGS), METHODB(get_clipboard_mime, METH_VARARGS), METHODB(toggle_secure_input, METH_NOARGS), - METHODB(macos_cycle_through_os_windows, METH_NOARGS), + METHODB(macos_cycle_through_os_windows, METH_O), METHODB(get_content_scale_for_window, METH_NOARGS), METHODB(ring_bell, METH_VARARGS), METHODB(toggle_fullscreen, METH_VARARGS), diff --git a/kitty/main.py b/kitty/main.py index f862659d2..15564a099 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -193,7 +193,7 @@ def set_cocoa_global_shortcuts(opts: Options) -> dict[str, SingleKey]: for ac in ('new_os_window', 'close_os_window', 'close_tab', 'edit_config_file', 'previous_tab', 'next_tab', 'new_tab', 'new_window', 'close_window', 'toggle_macos_secure_keyboard_entry', - 'toggle_fullscreen', 'macos_cycle_through_os_windows', + 'toggle_fullscreen', 'macos_cycle_through_os_windows', 'macos_cycle_through_os_windows_backwards', 'hide_macos_app', 'hide_macos_other_apps', 'minimize_macos_window', 'quit'): val = get_macos_shortcut_for(func_map, ac) if val is not None: diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 0544a413b..ef7e17af2 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -4429,6 +4429,7 @@ map('Toggle macOS secure keyboard entry', ) map('macOS Cycle through OS Windows', 'macos_cycle_through_os_windows cmd+` macos_cycle_through_os_windows', only='macos') +map('macOS Cycle through OS Windows backwards', 'macos_cycle_through_os_windows_backwards cmd+shift+` macos_cycle_through_os_windows_backwards', only='macos') map('Unicode input', 'input_unicode_character kitty_mod+u kitten unicode_input', diff --git a/kitty/options/types.py b/kitty/options/types.py index cd9d8b8b6..c15a34e08 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -1006,6 +1006,7 @@ if is_macos: defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=102), definition='toggle_fullscreen')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=115), definition='toggle_macos_secure_keyboard_entry')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=96), definition='macos_cycle_through_os_windows')) + defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=96), definition='macos_cycle_through_os_windows_backwards')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=32), definition='kitten unicode_input')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=44), definition='edit_config_file')) defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=114), definition='clear_terminal reset active'))