macOS: Allow to customize Hide, Minimize, and Quit global menu shortcuts

All previously hard-coded shortcuts under macOS can be configured now.
This commit is contained in:
pagedown 2022-12-20 21:08:26 +08:00
parent aa9679e56b
commit 1023084eb9
No known key found for this signature in database
GPG Key ID: E921CF18AC8FF6EB
10 changed files with 157 additions and 25 deletions

View File

@ -71,6 +71,9 @@ Detailed list of changes
- ZSH Integration: Bind :kbd:`alt+left` and :kbd:`alt+right` to move by word if not already bound. This mimics the default bindings in Terminal.app (:iss:`5793`)
- macOS: Allow to customize :sc:`Hide <hide_macos_app>`, :sc:`Hide Others <hide_macos_other_apps>`, :sc:`Minimize <minimize_macos_window>`, and :sc:`Quit <quit>` global menu shortcuts. Note that :opt:`clear_all_shortcuts` will remove these shortcuts now (:iss:`948`)
0.26.5 [2022-11-07]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -33,18 +33,21 @@ from .constants import (
)
from .fast_data_types import (
CLOSE_BEING_CONFIRMED, GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT,
GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, IMPERATIVE_CLOSE_REQUESTED,
NO_CLOSE_REQUESTED, ChildMonitor, Color, EllipticCurveKey, KeyEvent, SingleKey,
add_timer, apply_options_update, background_opacity_of, change_background_opacity,
change_os_window_state, cocoa_set_menubar_title, create_os_window,
current_application_quit_request, current_focused_os_window_id, current_os_window,
destroy_global_data, focus_os_window, get_boss, get_options, get_os_window_size,
global_font_size, last_focused_os_window_id, mark_os_window_for_close,
os_window_font_size, patch_global_colors, redirect_mouse_handling, ring_bell,
GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS,
IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, ChildMonitor, Color,
EllipticCurveKey, KeyEvent, SingleKey, add_timer, apply_options_update,
background_opacity_of, change_background_opacity, change_os_window_state,
cocoa_hide_app, cocoa_hide_other_apps, cocoa_minimize_os_window,
cocoa_set_menubar_title, create_os_window,
current_application_quit_request, current_focused_os_window_id,
current_os_window, destroy_global_data, focus_os_window, get_boss,
get_options, get_os_window_size, global_font_size,
last_focused_os_window_id, mark_os_window_for_close, os_window_font_size,
patch_global_colors, redirect_mouse_handling, ring_bell,
run_with_activation_token, safe_pipe, send_data_to_peer,
set_application_quit_request, set_background_image, set_boss, set_in_sequence_mode,
set_options, set_os_window_size, set_os_window_title, thread_write,
toggle_fullscreen, toggle_maximized, toggle_secure_input,
set_application_quit_request, set_background_image, set_boss,
set_in_sequence_mode, set_options, set_os_window_size, set_os_window_title,
thread_write, toggle_fullscreen, toggle_maximized, toggle_secure_input,
)
from .key_encoding import get_name_to_functional_number_map
from .keys import get_shortcut, shortcut_matches
@ -926,6 +929,20 @@ class Boss:
def toggle_macos_secure_keyboard_entry(self) -> None:
toggle_secure_input()
@ac('misc', 'Hide macOS kitty application')
def hide_macos_app(self) -> None:
cocoa_hide_app()
@ac('misc', 'Hide macOS other applications')
def hide_macos_other_apps(self) -> None:
cocoa_hide_other_apps()
@ac('misc', 'Minimize macOS window')
def minimize_macos_window(self) -> None:
osw_id = current_os_window()
if osw_id is not None:
cocoa_minimize_os_window(osw_id)
def start(self, first_os_window_id: int, startup_sessions: Iterable[Session]) -> None:
if not getattr(self, 'io_thread_started', False):
self.child_monitor.start()

View File

@ -1108,6 +1108,10 @@ process_cocoa_pending_actions(void) {
if (cocoa_pending_actions[TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY]) { call_boss(toggle_macos_secure_keyboard_entry, 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); }
if (cocoa_pending_actions[HIDE_OTHERS]) { call_boss(hide_macos_other_apps, NULL); }
if (cocoa_pending_actions[MINIMIZE]) { call_boss(minimize_macos_window, NULL); }
if (cocoa_pending_actions[QUIT]) { call_boss(quit, NULL); }
if (cocoa_pending_actions_data.wd) {
if (cocoa_pending_actions[NEW_OS_WINDOW_WITH_WD]) { call_boss(new_os_window_with_wd, "sO", cocoa_pending_actions_data.wd, Py_True); }
if (cocoa_pending_actions[NEW_TAB_WITH_WD]) { call_boss(new_tab_with_wd, "sO", cocoa_pending_actions_data.wd, Py_True); }

View File

@ -236,12 +236,30 @@ PENDING(reload_config, RELOAD_CONFIG)
PENDING(toggle_macos_secure_keyboard_entry, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY)
PENDING(toggle_fullscreen, TOGGLE_FULLSCREEN)
PENDING(open_kitty_website, OPEN_KITTY_WEBSITE)
PENDING(hide_macos_app, HIDE)
PENDING(hide_macos_other_apps, HIDE_OTHERS)
PENDING(minimize_macos_window, MINIMIZE)
PENDING(quit, QUIT)
- (BOOL)validateMenuItem:(NSMenuItem *)item {
if (item.action == @selector(toggle_macos_secure_keyboard_entry:)) {
item.state = [SecureKeyboardEntryController sharedInstance].isDesired ? NSControlStateValueOn : NSControlStateValueOff;
} else if (item.action == @selector(toggle_fullscreen:)) {
item.title = ([NSApp currentSystemPresentationOptions] & NSApplicationPresentationFullScreen) ? @"Exit Full Screen" : @"Enter Full Screen";
if (![NSApp keyWindow]) return NO;
} else if (item.action == @selector(minimize_macos_window:)) {
NSWindow *window = [NSApp keyWindow];
if (!window || window.miniaturized || [NSApp currentSystemPresentationOptions] & NSApplicationPresentationFullScreen) return NO;
} else if (item.action == @selector(close_os_window:) ||
item.action == @selector(close_tab:) ||
item.action == @selector(close_window:) ||
item.action == @selector(reset_terminal:) ||
item.action == @selector(clear_terminal_and_scrollback:) ||
item.action == @selector(previous_tab:) ||
item.action == @selector(next_tab:) ||
item.action == @selector(detach_tab:))
{
if (![NSApp keyWindow]) return NO;
}
return YES;
}
@ -272,6 +290,7 @@ typedef struct {
GlobalShortcut new_os_window, close_os_window, close_tab, edit_config_file, reload_config;
GlobalShortcut previous_tab, next_tab, new_tab, new_window, close_window, reset_terminal, clear_terminal_and_scrollback;
GlobalShortcut toggle_macos_secure_keyboard_entry, toggle_fullscreen, open_kitty_website;
GlobalShortcut hide_macos_app, hide_macos_other_apps, minimize_macos_window, quit;
} GlobalShortcuts;
static GlobalShortcuts global_shortcuts;
@ -287,6 +306,7 @@ cocoa_set_global_shortcut(PyObject *self UNUSED, PyObject *args) {
else Q(new_tab); else Q(next_tab); else Q(previous_tab);
else Q(new_window); else Q(close_window); else Q(reset_terminal); else Q(clear_terminal_and_scrollback); 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);
#undef Q
if (gs == NULL) { PyErr_SetString(PyExc_KeyError, "Unknown shortcut name"); return NULL; }
int cocoa_mods;
@ -574,13 +594,8 @@ cocoa_create_global_menu(void) {
[servicesMenu release];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name]
action:@selector(hide:)
keyEquivalent:@"h"];
[[appMenu addItemWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"]
setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
MENU_ITEM(appMenu, ([NSString stringWithFormat:@"Hide %@", app_name]), hide_macos_app);
MENU_ITEM(appMenu, @"Hide Others", hide_macos_other_apps);
[appMenu addItemWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
@ -589,9 +604,7 @@ cocoa_create_global_menu(void) {
MENU_ITEM(appMenu, @"Secure Keyboard Entry", toggle_macos_secure_keyboard_entry);
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name]
action:@selector(terminate:)
keyEquivalent:@"q"];
MENU_ITEM(appMenu, ([NSString stringWithFormat:@"Quit %@", app_name]), quit);
[appMenu release];
NSMenuItem* shellMenuItem =
@ -619,9 +632,7 @@ cocoa_create_global_menu(void) {
NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
[windowMenuItem setSubmenu:windowMenu];
[windowMenu addItemWithTitle:@"Minimize"
action:@selector(performMiniaturize:)
keyEquivalent:@"m"];
MENU_ITEM(windowMenu, @"Minimize", minimize_macos_window);
[windowMenu addItemWithTitle:@"Zoom"
action:@selector(performZoom:)
keyEquivalent:@""];
@ -723,6 +734,22 @@ cocoa_toggle_secure_keyboard_entry(void) {
[[NSUserDefaults standardUserDefaults] setBool:k.isDesired forKey:@"SecureKeyboardEntry"];
}
void
cocoa_hide(void) {
[[NSApplication sharedApplication] performSelectorOnMainThread:@selector(hide:) withObject:nil waitUntilDone:NO];
}
void
cocoa_hide_others(void) {
[[NSApplication sharedApplication] performSelectorOnMainThread:@selector(hideOtherApplications:) withObject:nil waitUntilDone:NO];
}
void
cocoa_minimize(void *w) {
NSWindow *window = (NSWindow*)w;
if (window && !window.miniaturized) [window performSelectorOnMainThread:@selector(performMiniaturize:) withObject:nil waitUntilDone:NO];
}
void
cocoa_focus_window(void *w) {
NSWindow *window = (NSWindow*)w;

View File

@ -741,6 +741,18 @@ def cocoa_set_dock_icon(icon_path: str) -> None:
pass
def cocoa_hide_app() -> None:
pass
def cocoa_hide_other_apps() -> None:
pass
def cocoa_minimize_os_window(os_window_id: Optional[int] = None) -> None:
pass
def locale_is_valid(name: str) -> bool:
pass

View File

@ -25,6 +25,9 @@ extern void cocoa_set_titlebar_appearance(void *w, unsigned int theme);
extern void cocoa_set_titlebar_color(void *w, color_type color);
extern bool cocoa_alt_option_key_pressed(unsigned long);
extern void cocoa_toggle_secure_keyboard_entry(void);
extern void cocoa_hide(void);
extern void cocoa_hide_others(void);
extern void cocoa_minimize(void *w);
extern void cocoa_set_uncaught_exception_handler(void);
extern void cocoa_update_menu_bar_title(PyObject*);
extern size_t cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz);
@ -1272,6 +1275,22 @@ toggle_secure_input(PYNOARG) {
Py_RETURN_NONE;
}
static PyObject*
cocoa_hide_app(PYNOARG) {
#ifdef __APPLE__
cocoa_hide();
#endif
Py_RETURN_NONE;
}
static PyObject*
cocoa_hide_other_apps(PYNOARG) {
#ifdef __APPLE__
cocoa_hide_others();
#endif
Py_RETURN_NONE;
}
static void
ring_audio_bell(void) {
static monotonic_t last_bell_at = -1;
@ -1349,6 +1368,24 @@ toggle_maximized(PyObject UNUSED *self, PyObject *args) {
Py_RETURN_FALSE;
}
static PyObject*
cocoa_minimize_os_window(PyObject UNUSED *self, PyObject *args) {
id_type os_window_id = 0;
if (!PyArg_ParseTuple(args, "|K", &os_window_id)) return NULL;
#ifdef __APPLE__
OSWindow *w = os_window_id ? find_os_window(os_window_id) : current_os_window();
if (!w || !w->handle) Py_RETURN_NONE;
if (!glfwGetCocoaWindow) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetCocoaWindow"); return NULL; }
void *window = glfwGetCocoaWindow(w->handle);
if (!window) Py_RETURN_NONE;
cocoa_minimize(window);
#else
PyErr_SetString(PyExc_RuntimeError, "cocoa_minimize_os_window() is only supported on macOS");
return NULL;
#endif
Py_RETURN_NONE;
}
static PyObject*
change_os_window_state(PyObject *self UNUSED, PyObject *args) {
char *state;
@ -1718,6 +1755,9 @@ static PyMethodDef module_methods[] = {
METHODB(dbus_send_notification, METH_VARARGS),
#endif
METHODB(cocoa_window_id, METH_O),
METHODB(cocoa_hide_app, METH_NOARGS),
METHODB(cocoa_hide_other_apps, METH_NOARGS),
METHODB(cocoa_minimize_os_window, METH_VARARGS),
{"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""},
{"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""},
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""},

View File

@ -181,7 +181,8 @@ def _run_app(opts: Options, args: CLIOptions, prewarm: PrewarmProcess, bad_lines
func_map[parts].append(k)
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'):
'next_tab', 'new_tab', 'new_window', 'close_window', 'toggle_macos_secure_keyboard_entry', 'toggle_fullscreen',
'hide_macos_app', 'hide_macos_other_apps', 'minimize_macos_window', 'quit'):
val = get_macos_shortcut_for(func_map, ac)
if val is not None:
global_shortcuts[ac] = val

View File

@ -4002,5 +4002,25 @@ map('Open kitty Website',
f'open_kitty_website shift+cmd+/ open_url {website_url()}',
only='macos',
)
map('Hide macOS kitty application',
'hide_macos_app cmd+h hide_macos_app',
only='macos',
)
map('Hide macOS other applications',
'hide_macos_other_apps opt+cmd+h hide_macos_other_apps',
only='macos',
)
map('Minimize macOS window',
'minimize_macos_window cmd+m minimize_macos_window',
only='macos',
)
map('Quit kitty',
'quit cmd+q quit',
only='macos',
)
egr() # }}}
egr() # }}}

View File

@ -937,6 +937,10 @@ if is_macos:
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=12, key=44), definition='load_config_file')) # noqa
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=44), definition='debug_config')) # noqa
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=9, key=47), definition='open_url https://sw.kovidgoyal.net/kitty/')) # noqa
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=104), definition='hide_macos_app')) # noqa
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=104), definition='hide_macos_other_apps')) # noqa
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=109), definition='minimize_macos_window')) # noqa
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=113), definition='quit')) # noqa
defaults.mouse_map = [
# click_url_or_select
MouseMapping(repeat_count=-2, definition='mouse_handle_click selection link prompt'), # noqa

View File

@ -320,6 +320,10 @@ typedef enum {
TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY,
TOGGLE_FULLSCREEN,
OPEN_KITTY_WEBSITE,
HIDE,
HIDE_OTHERS,
MINIMIZE,
QUIT,
NUM_COCOA_PENDING_ACTIONS
} CocoaPendingAction;