macOS: Add menu items to close the OS window and the current tab

Fixes #3246
This commit is contained in:
Kovid Goyal 2021-01-17 06:49:25 +05:30
parent 4c9bd368c6
commit aa63bf71cf
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 80 additions and 39 deletions

View File

@ -27,7 +27,7 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- 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]

View File

@ -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

View File

@ -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); }

View File

@ -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");

View File

@ -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

View File

@ -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)

View File

@ -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