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 @@ def __init__(
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 @@ def __init__(
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 @@ - (void)new_os_window:(id)sender {
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 @@ + (GlobalMenuTarget *) shared_instance
@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 @@ - (BOOL)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
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 @@ - (BOOL)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
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 @@ - (BOOL)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
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 @@ - (BOOL)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
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 @@ - (BOOL)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
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 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