From 2e8d19601bd7a533181a692352c29d0b908a1ad5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Jun 2018 22:18:33 +0530 Subject: [PATCH] macOS: Fix the new OS window keyboard shortcut not working if no kitty window currently has focus. Fixes #524 --- docs/changelog.rst | 3 ++ glfw/cocoa_window.m | 93 ++++++++++++++++++++++++++++++++++++++++++++ glfw/glfw.py | 1 + kitty/boss.py | 7 +++- kitty/cocoa_window.m | 36 ++++++++++++++++- kitty/glfw-wrapper.c | 2 + kitty/glfw-wrapper.h | 4 ++ kitty/glfw.c | 6 +++ kitty/main.py | 11 +++++- kitty/state.h | 3 ++ 10 files changed, 162 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 53d9dca4b..460ff3967 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -44,6 +44,9 @@ Changelog - Wayland: Fix mouse wheel/touchpad scrolling in opposite direction to other apps (:iss:`594`) +- macOS: Fix the new OS window keyboard shortcut (:sc:`new_os_window`) not + working if no kitty window currently has focus. (:iss:`524`) + - Add a config option to set the EDITOR kitty uses (:iss:`580`) - Add an option to @set-window-title to make the title change non-permanent diff --git a/glfw/cocoa_window.m b/glfw/cocoa_window.m index e9b16daf5..d821bfe94 100644 --- a/glfw/cocoa_window.m +++ b/glfw/cocoa_window.m @@ -2088,3 +2088,96 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand window->ns.textInputFilterCallback = callback; return previous; } + +GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned short *cocoa_key, int *cocoa_mods) { + *cocoa_key = 0; + *cocoa_mods = 0; + + if (glfw_mods & GLFW_MOD_SHIFT) + *cocoa_mods |= NSEventModifierFlagShift; + if (glfw_mods & GLFW_MOD_CONTROL) + *cocoa_mods |= NSEventModifierFlagControl; + if (glfw_mods & GLFW_MOD_ALT) + *cocoa_mods |= NSEventModifierFlagOption; + if (glfw_mods & GLFW_MOD_SUPER) + *cocoa_mods |= NSEventModifierFlagCommand; + if (glfw_mods & GLFW_MOD_CAPS_LOCK) + *cocoa_mods |= NSEventModifierFlagCapsLock; + + switch(glfw_key) { +#define K(ch, name) case GLFW_KEY_##name: *cocoa_key = ch; break; + K('a', A); + K('b', B); + K('c', C); + K('d', D); + K('e', E); + K('f', F); + K('g', G); + K('h', H); + K('i', I); + K('j', J); + K('k', K); + K('l', L); + K('m', M); + K('n', N); + K('o', O); + K('p', P); + K('q', Q); + K('r', R); + K('s', S); + K('t', T); + K('u', U); + K('v', V); + K('w', W); + K('x', X); + K('y', Y); + K('z', Z); + K('0', 0); + K('1', 1); + K('2', 2); + K('3', 3); + K('5', 5); + K('6', 6); + K('7', 7); + K('8', 8); + K('9', 9); + K('\'', APOSTROPHE); + K(',', COMMA); + K('.', PERIOD); + K('/', SLASH); + K('-', MINUS); + K('=', EQUAL); + K(';', SEMICOLON); + K('[', LEFT_BRACKET); + K(']', RIGHT_BRACKET); + K('`', GRAVE_ACCENT); + K('\\', BACKSLASH); + + K(0x35, ESCAPE); + K('\r', ENTER); + K('\t', TAB); + K(NSBackspaceCharacter, BACKSPACE); + K(NSInsertFunctionKey, INSERT); + K(NSDeleteCharacter, DELETE); + K(NSLeftArrowFunctionKey, LEFT); + K(NSRightArrowFunctionKey, RIGHT); + K(NSUpArrowFunctionKey, UP); + K(NSDownArrowFunctionKey, DOWN); + K(NSPageUpFunctionKey, PAGE_UP); + K(NSPageDownFunctionKey, PAGE_DOWN); + K(NSHomeFunctionKey, HOME); + K(NSEndFunctionKey, END); + K(NSPrintFunctionKey, PRINT_SCREEN); + case GLFW_KEY_F1 ... GLFW_KEY_F24: + *cocoa_key = NSF1FunctionKey + (glfw_key - GLFW_KEY_F1); break; + case GLFW_KEY_KP_0 ... GLFW_KEY_KP_9: + *cocoa_key = NSEventModifierFlagNumericPad | (0x52 + (glfw_key - GLFW_KEY_KP_0)); break; + K((unichar)(0x41|NSEventModifierFlagNumericPad), KP_DECIMAL); + K((unichar)(0x43|NSEventModifierFlagNumericPad), KP_MULTIPLY); + K((unichar)(0x45|NSEventModifierFlagNumericPad), KP_ADD); + K((unichar)(0x4B|NSEventModifierFlagNumericPad), KP_DIVIDE); + K((unichar)(0x4E|NSEventModifierFlagNumericPad), KP_SUBTRACT); + K((unichar)(0x51|NSEventModifierFlagNumericPad), KP_EQUAL); +#undef K + } +} diff --git a/glfw/glfw.py b/glfw/glfw.py index 304384cd2..a17177c74 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -202,6 +202,7 @@ def generate_wrappers(glfw_header, glfw_native_header): void* glfwGetCocoaWindow(GLFWwindow* window) uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor) GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback) + void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods) void* glfwGetX11Display(void) int32_t glfwGetX11Window(GLFWwindow* window) void glfwSetX11SelectionString(const char* string) diff --git a/kitty/boss.py b/kitty/boss.py index bb9dbe54e..2d882226d 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -75,7 +75,7 @@ class DumpCommands: # {{{ class Boss: - def __init__(self, os_window_id, opts, args, cached_values): + def __init__(self, os_window_id, opts, args, cached_values, new_os_window_trigger): self.window_id_map = WeakValueDictionary() self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)} self.pending_sequences = None @@ -96,6 +96,9 @@ class Boss: set_boss(self) self.opts, self.args = opts, args startup_session = create_session(opts, args) + self.keymap = self.opts.keymap.copy() + if new_os_window_trigger is not None: + self.keymap.pop(new_os_window_trigger, None) self.add_os_window(startup_session, os_window_id=os_window_id) def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None): @@ -411,7 +414,7 @@ class Boss: def dispatch_special_key(self, key, scancode, action, mods): # Handles shortcuts, return True if the key was consumed - key_action = get_shortcut(self.opts.keymap, mods, key, scancode) + key_action = get_shortcut(self.keymap, mods, key, scancode) if key_action is None: sequences = get_shortcut(self.opts.sequence_map, mods, key, scancode) if sequences: diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index bfefdbfd1..4e0a2e882 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -68,6 +68,12 @@ find_app_name(void) { call_boss(edit_config_file, NULL); } +- (void) new_os_window : (id)sender { + (void)sender; + call_boss(new_os_window, NULL); +} + + + (GlobalMenuTarget *) shared_instance { static GlobalMenuTarget *sharedGlobalMenuTarget = nil; @@ -81,6 +87,20 @@ find_app_name(void) { @end +static unichar new_window_key = 0; +static NSEventModifierFlags new_window_mods = 0; + +static PyObject* +cocoa_set_new_window_trigger(PyObject *self UNUSED, PyObject *args) { + int mods, key; + if (!PyArg_ParseTuple(args, "ii", &mods, &key)) return NULL; + int nwm; + get_cocoa_key_equivalent(key, mods, &new_window_key, &nwm); + new_window_mods = nwm; + if (new_window_key) Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + void cocoa_create_global_menu(void) { NSString* app_name = find_app_name(); @@ -97,9 +117,19 @@ cocoa_create_global_menu(void) { action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; [appMenu addItem:[NSMenuItem separatorItem]]; - NSMenuItem* preferences_menu_item = [[NSMenuItem alloc] initWithTitle:@"Preferences..." action:@selector(show_preferences:) keyEquivalent:@","]; + NSMenuItem* preferences_menu_item = [[NSMenuItem alloc] initWithTitle:@"Preferences..." action:@selector(show_preferences:) keyEquivalent:@","], *new_os_window_menu_item = NULL; [preferences_menu_item setTarget:global_menu_target]; [appMenu addItem:preferences_menu_item]; + if (new_window_key) { + NSString *s = [NSString stringWithCharacters:&new_window_key length:1]; + new_os_window_menu_item = [[NSMenuItem alloc] initWithTitle:@"New OS window" action:@selector(new_os_window:) keyEquivalent:s]; + [new_os_window_menu_item setKeyEquivalentModifierMask:new_window_mods]; + [new_os_window_menu_item setTarget:global_menu_target]; + [appMenu addItem:new_os_window_menu_item]; + [s release]; + } + + [appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name] action:@selector(hide:) keyEquivalent:@"h"]; @@ -150,6 +180,9 @@ cocoa_create_global_menu(void) { [NSApp setWindowsMenu:windowMenu]; [windowMenu release]; [preferences_menu_item release]; + if (new_os_window_menu_item) { + [new_os_window_menu_item release]; + } [bar release]; } @@ -239,6 +272,7 @@ cocoa_set_titlebar_color(void *w, color_type titlebar_color) static PyMethodDef module_methods[] = { {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, {"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""}, + {"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 677f49948..80c82b371 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -359,6 +359,8 @@ load_glfw(const char* path) { *(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter"); + *(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent"); + *(void **) (&glfwGetX11Display_impl) = dlsym(handle, "glfwGetX11Display"); *(void **) (&glfwGetX11Window_impl) = dlsym(handle, "glfwGetX11Window"); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 402bf1717..0d1cd175e 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1836,6 +1836,10 @@ typedef GLFWcocoatextinputfilterfun (*glfwSetCocoaTextInputFilter_func)(GLFWwind glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl; #define glfwSetCocoaTextInputFilter glfwSetCocoaTextInputFilter_impl +typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, void*, void*); +glfwGetCocoaKeyEquivalent_func glfwGetCocoaKeyEquivalent_impl; +#define glfwGetCocoaKeyEquivalent glfwGetCocoaKeyEquivalent_impl + typedef void* (*glfwGetX11Display_func)(); glfwGetX11Display_func glfwGetX11Display_impl; #define glfwGetX11Display glfwGetX11Display_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index af15ffbd5..ca10b6ba1 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -799,6 +799,12 @@ set_smallest_allowed_resize(PyObject *self UNUSED, PyObject *args) { Py_RETURN_NONE; } +#ifdef __APPLE__ +void +get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *cocoa_mods) { + glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, cocoa_mods); +} +#endif // Boilerplate {{{ static PyMethodDef module_methods[] = { diff --git a/kitty/main.py b/kitty/main.py index 744836f72..4c70a56d0 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -43,6 +43,15 @@ def init_graphics(debug_keyboard=False): def _run_app(opts, args): + new_os_window_trigger = None + if is_macos: + for k, v in opts.keymap.items(): + if v.func == 'new_os_window': + new_os_window_trigger = k + from .fast_data_types import cocoa_set_new_window_trigger + if not cocoa_set_new_window_trigger(*new_os_window_trigger): + new_os_window_trigger = None + with cached_values_for(run_app.cached_values_name) as cached_values: with startup_notification_handler(extra_callback=run_app.first_window_callback) as pre_show_callback: window_id = create_os_window( @@ -53,7 +62,7 @@ def _run_app(opts, args): if not is_wayland and not is_macos: # no window icons on wayland with open(logo_data_file, 'rb') as f: set_default_window_icon(f.read(), 256, 256) - boss = Boss(window_id, opts, args, cached_values) + boss = Boss(window_id, opts, args, cached_values, new_os_window_trigger) boss.start() try: boss.child_monitor.main_loop() diff --git a/kitty/state.h b/kitty/state.h index 8b4ca9e00..2daf6168e 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -188,3 +188,6 @@ void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsign void set_titlebar_color(OSWindow *w, color_type color); FONTS_DATA_HANDLE load_fonts_data(double, double, double); void send_prerendered_sprites_for_window(OSWindow *w); +#ifdef __APPLE__ +void get_cocoa_key_equivalent(int, int, unsigned short*, int*); +#endif