From aa63bf71cf34310a5c9db3ceae1cbe5929aa0bfd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 17 Jan 2021 06:49:25 +0530 Subject: [PATCH] macOS: Add menu items to close the OS window and the current tab Fixes #3246 --- docs/changelog.rst | 2 +- kitty/boss.py | 6 ++-- kitty/child-monitor.c | 2 ++ kitty/cocoa_window.m | 63 +++++++++++++++++++++++++++++---------- kitty/fast_data_types.pyi | 2 +- kitty/main.py | 40 ++++++++++++++----------- kitty/state.h | 4 ++- 7 files changed, 80 insertions(+), 39 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index a5e391a7b..92ac2d8bd 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -27,7 +27,7 @@ To update |kitty|, :doc:`follow the instructions `. - Panel kitten: Allow setting WM_CLASS (:iss:`3233`) -- macOS: Add a menu item to close the OS window (:pull:`3240`) +- macOS: Add menu items to close the OS window and the current tab (:pull:`3240`, :iss:`3246`) 0.19.3 [2020-12-19] diff --git a/kitty/boss.py b/kitty/boss.py index 7206a3d11..f44dcfe43 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -141,7 +141,7 @@ class Boss: opts: Options, args: CLIOptions, cached_values: Dict[str, Any], - new_os_window_trigger: Optional[SingleKey] + global_shortcuts: Dict[str, SingleKey] ): set_layout_options(opts) self.clipboard_buffers: Dict[str, str] = {} @@ -168,8 +168,8 @@ class Boss: set_boss(self) self.opts, self.args = opts, args self.keymap = self.opts.keymap.copy() - if new_os_window_trigger is not None: - self.keymap.pop(new_os_window_trigger, None) + for sc in global_shortcuts.values(): + self.keymap.pop(sc, None) if is_macos: from .fast_data_types import ( cocoa_set_notification_activated_callback diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index af4f698fc..30439e284 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -968,6 +968,8 @@ process_global_state(void *data) { if (cocoa_pending_actions) { if (cocoa_pending_actions & PREFERENCES_WINDOW) { call_boss(edit_config_file, NULL); } if (cocoa_pending_actions & NEW_OS_WINDOW) { call_boss(new_os_window, NULL); } + if (cocoa_pending_actions & CLOSE_OS_WINDOW) { call_boss(close_os_window, NULL); } + if (cocoa_pending_actions & CLOSE_TAB) { call_boss(close_tab, NULL); } if (cocoa_pending_actions_wd) { if (cocoa_pending_actions & NEW_OS_WINDOW_WITH_WD) { call_boss(new_os_window_with_wd, "s", cocoa_pending_actions_wd); } if (cocoa_pending_actions & NEW_TAB_WITH_WD) { call_boss(new_tab_with_wd, "s", cocoa_pending_actions_wd); } diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index c41b15d0f..682197ebf 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -87,6 +87,16 @@ find_app_name(void) { set_cocoa_pending_action(NEW_OS_WINDOW, NULL); } +- (void) close_os_window:(id)sender { + (void)sender; + set_cocoa_pending_action(CLOSE_OS_WINDOW, NULL); +} + +- (void)close_tab:(id)sender { + (void)sender; + set_cocoa_pending_action(CLOSE_TAB, NULL); +} + - (void)open_kitty_website_url:(id)sender { (void)sender; [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://sw.kovidgoyal.net/kitty/"]]; @@ -106,18 +116,30 @@ find_app_name(void) { @end -static char new_window_key[32] = {0}; -static NSEventModifierFlags new_window_mods = 0; +typedef struct { + char key[32]; + NSEventModifierFlags mods; +} GlobalShortcut; +typedef struct { + GlobalShortcut new_os_window, close_os_window, close_tab; +} GlobalShortcuts; +static GlobalShortcuts global_shortcuts; static PyObject* -cocoa_set_new_window_trigger(PyObject *self UNUSED, PyObject *args) { +cocoa_set_global_shortcut(PyObject *self UNUSED, PyObject *args) { int mods; unsigned int key; - if (!PyArg_ParseTuple(args, "iI", &mods, &key)) return NULL; - int nwm; - get_cocoa_key_equivalent(key, mods, new_window_key, sizeof(new_window_key), &nwm); - new_window_mods = nwm; - if (new_window_key[0]) Py_RETURN_TRUE; + const char *name; + if (!PyArg_ParseTuple(args, "siI", &name, &mods, &key)) return NULL; + GlobalShortcut *gs = NULL; + if (strcmp(name, "new_os_window") == 0) gs = &global_shortcuts.new_os_window; + else if (strcmp(name, "close_os_window") == 0) gs = &global_shortcuts.close_os_window; + else if (strcmp(name, "close_tab") == 0) gs = &global_shortcuts.close_tab; + if (gs == NULL) { PyErr_SetString(PyExc_KeyError, "Unknown shortcut name"); return NULL; } + int cocoa_mods; + get_cocoa_key_equivalent(key, mods, gs->key, 32, &cocoa_mods); + gs->mods = cocoa_mods; + if (gs->key[0]) Py_RETURN_TRUE; Py_RETURN_FALSE; } @@ -358,8 +380,8 @@ cocoa_create_global_menu(void) { NSMenuItem* new_os_window_menu_item = [appMenu addItemWithTitle:@"New OS window" action:@selector(new_os_window:) - keyEquivalent:@(new_window_key)]; - [new_os_window_menu_item setKeyEquivalentModifierMask:new_window_mods]; + keyEquivalent:@(global_shortcuts.new_os_window.key)]; + [new_os_window_menu_item setKeyEquivalentModifierMask:global_shortcuts.new_os_window.mods]; [new_os_window_menu_item setTarget:global_menu_target]; @@ -396,10 +418,6 @@ cocoa_create_global_menu(void) { NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; [windowMenuItem setSubmenu:windowMenu]; - [windowMenu addItemWithTitle:@"Close" - action:@selector(performClose:) - keyEquivalent:@"w"]; - [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; @@ -411,6 +429,20 @@ cocoa_create_global_menu(void) { action:@selector(arrangeInFront:) keyEquivalent:@""]; + [windowMenu addItem:[NSMenuItem separatorItem]]; + NSMenuItem* close_tab_item = + [windowMenu addItemWithTitle:@"Close Tab" + action:@selector(close_tab:) + keyEquivalent:@(global_shortcuts.close_tab.key)]; + [close_tab_item setKeyEquivalentModifierMask:global_shortcuts.close_tab.mods]; + [close_tab_item setTarget:global_menu_target]; + NSMenuItem* close_os_window_menu_item = + [windowMenu addItemWithTitle:@"Close OS Window" + action:@selector(close_os_window:) + keyEquivalent:@(global_shortcuts.close_os_window.key)]; + [close_os_window_menu_item setKeyEquivalentModifierMask:global_shortcuts.close_os_window.mods]; + [close_os_window_menu_item setTarget:global_menu_target]; + [windowMenu addItem:[NSMenuItem separatorItem]]; [[windowMenu addItemWithTitle:@"Enter Full Screen" action:@selector(toggleFullScreen:) @@ -627,7 +659,7 @@ cocoa_system_beep(void) { static PyMethodDef module_methods[] = { {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, - {"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""}, + {"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""}, {"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""}, {"cocoa_set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ @@ -635,6 +667,7 @@ static PyMethodDef module_methods[] = { bool init_cocoa(PyObject *module) { + memset(&global_shortcuts, 0, sizeof(global_shortcuts)); if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (Py_AtExit(cleanup) != 0) { PyErr_SetString(PyExc_RuntimeError, "Failed to register the cocoa_window at exit handler"); diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index b2ebdabaf..f4592e93c 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -640,7 +640,7 @@ def cocoa_set_notification_activated_callback(identifier: Callable[[str], None]) pass -def cocoa_set_new_window_trigger(mods: int, key: int) -> bool: +def cocoa_set_global_shortcut(name: str, mods: int, key: int) -> bool: pass diff --git a/kitty/main.py b/kitty/main.py index efd2081a4..1863f32be 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -7,7 +7,7 @@ import os import shutil import sys from contextlib import contextmanager, suppress -from typing import Generator, List, Mapping, Optional, Sequence +from typing import Dict, Generator, List, Mapping, Optional, Sequence from .borders import load_borders_program from .boss import Boss @@ -104,26 +104,30 @@ def init_glfw(opts: OptionsStub, debug_keyboard: bool = False) -> str: return glfw_module -def get_new_os_window_trigger(opts: OptionsStub) -> Optional[SingleKey]: - new_os_window_trigger = None - if is_macos: - new_os_window_shortcuts = [] - for k, v in opts.keymap.items(): - if v.func == 'new_os_window': - new_os_window_shortcuts.append(k) - if new_os_window_shortcuts: - from .fast_data_types import cocoa_set_new_window_trigger +def get_macos_shortcut_for(opts: OptionsStub, function: str = 'new_os_window') -> Optional[SingleKey]: + ans = None + candidates = [] + for k, v in opts.keymap.items(): + if v.func == function: + candidates.append(k) + if candidates: + from .fast_data_types import cocoa_set_global_shortcut - # Reverse list so that later defined keyboard shortcuts take priority over earlier defined ones - for candidate in reversed(new_os_window_shortcuts): - if cocoa_set_new_window_trigger(candidate[0], candidate[2]): - new_os_window_trigger = candidate - break - return new_os_window_trigger + # Reverse list so that later defined keyboard shortcuts take priority over earlier defined ones + for candidate in reversed(candidates): + if cocoa_set_global_shortcut(function, candidate[0], candidate[2]): + ans = candidate + break + return ans def _run_app(opts: OptionsStub, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) -> None: - new_os_window_trigger = get_new_os_window_trigger(opts) + global_shortcuts: Dict[str, SingleKey] = {} + if is_macos: + for ac in ('new_os_window', 'close_os_window', 'close_tab'): + val = get_macos_shortcut_for(opts, ac) + if val is not None: + global_shortcuts[ac] = val if is_macos and opts.macos_custom_beam_cursor: set_custom_ibeam_cursor() if not is_wayland() and not is_macos: # no window icons on wayland @@ -137,7 +141,7 @@ def _run_app(opts: OptionsStub, args: CLIOptions, bad_lines: Sequence[BadLine] = pre_show_callback, args.title or appname, args.name or args.cls or appname, args.cls or appname, load_all_shaders) - boss = Boss(opts, args, cached_values, new_os_window_trigger) + boss = Boss(opts, args, cached_values, global_shortcuts) boss.start(window_id) if bad_lines: boss.show_bad_config_lines(bad_lines) diff --git a/kitty/state.h b/kitty/state.h index ab5c22010..750585e6f 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -263,7 +263,9 @@ typedef enum { PREFERENCES_WINDOW = 1, NEW_OS_WINDOW = 2, NEW_OS_WINDOW_WITH_WD = 4, - NEW_TAB_WITH_WD = 8 + NEW_TAB_WITH_WD = 8, + CLOSE_OS_WINDOW = 16, + CLOSE_TAB = 32 } CocoaPendingAction; void set_cocoa_pending_action(CocoaPendingAction action, const char*); #endif