Fix macOS keyboard shortcut encoding

`glfwGetCocoaKeyEquivalent()` in `glfw/cocoa_window.m` expects the returned characters to be of type `unichar`, which won't work for all unicode characters because it is defined as `unsigned short` according to https://developer.apple.com/documentation/foundation/unichar?language=objc, which is only guaranteed to be at least 16 bits in size. The code calling this function also expects the encoding to be UTF-16.
When I added the various keys in https://github.com/kovidgoyal/kitty/pull/1928, I missed these facts. This means, that `glfwGetCocoaKeyEquivalent()` will behave unexpectedly when called with any of the new-ish keys. Luckily this function is currently only used for determining the macOS shortcut for `new_os_window` but I plan on using it more in the future.
Some of the constants, e.g. `NSBackspaceCharacter` are UTF-16 constants, so we can't just use UTF-8 everywhere.
I fixed the problem by using either UTF-8 characters packed into a `uint32_t` or UTF-16 characters in a `unichar` and then converting them to a UTF-8 encoded char string.

`NSEventModifierFlagNumericPad` isn't guaranteed to fit in a `unichar`, which made this undefined behaviour. It also didn't work. I tried to make it work using `NSEventModifierFlagNumericPad` as a modifier instead, as can be seen in this commit, but couldn't get it to work either because the constants used are native key codes and not unicode characters. Therefore the numpad keys will be removed in the next commit.
This commit is contained in:
Luflosi
2019-12-04 00:04:52 +01:00
parent bcf847fe98
commit 9a3c621eb6
6 changed files with 170 additions and 143 deletions

View File

@@ -2179,8 +2179,7 @@ GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun
requestRenderFrame((_GLFWwindow*)w, callback);
}
GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned short *cocoa_key, int *cocoa_mods) {
*cocoa_key = 0;
GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char **cocoa_key, int *cocoa_mods) {
*cocoa_mods = 0;
if (glfw_mods & GLFW_MOD_SHIFT)
@@ -2194,148 +2193,174 @@ GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned sho
if (glfw_mods & GLFW_MOD_CAPS_LOCK)
*cocoa_mods |= NSEventModifierFlagCapsLock;
uint32_t utf_8_key = 0;
unichar utf_16_key = 0;
START_ALLOW_CASE_RANGE
switch(glfw_key) {
#define K(ch, name) case GLFW_KEY_##name: *cocoa_key = ch; break;
K('!', EXCLAM);
K('"', DOUBLE_QUOTE);
K('#', NUMBER_SIGN);
K('$', DOLLAR);
K('&', AMPERSAND);
K('\'', APOSTROPHE);
K('(', PARENTHESIS_LEFT);
K(')', PARENTHESIS_RIGHT);
K('+', PLUS);
K(',', COMMA);
K('-', MINUS);
K('.', PERIOD);
K('/', SLASH);
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(':', COLON);
K(';', SEMICOLON);
K('<', LESS);
K('=', EQUAL);
K('>', GREATER);
K('@', AT);
K('[', LEFT_BRACKET);
K('\\', BACKSLASH);
K(']', RIGHT_BRACKET);
K('^', CIRCUMFLEX);
K('_', UNDERSCORE);
K('`', GRAVE_ACCENT);
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(PARAGRAPH_UTF_8, PARAGRAPH);
K(MASCULINE_UTF_8, MASCULINE);
K(S_SHARP_UTF_8, S_SHARP);
K(A_GRAVE_LOWER_CASE_UTF_8, A_GRAVE);
K(A_DIAERESIS_LOWER_CASE_UTF_8, A_DIAERESIS);
K(A_RING_LOWER_CASE_UTF_8, A_RING);
K(AE_LOWER_CASE_UTF_8, AE);
K(C_CEDILLA_LOWER_CASE_UTF_8, C_CEDILLA);
K(E_GRAVE_LOWER_CASE_UTF_8, E_GRAVE);
K(E_ACUTE_LOWER_CASE_UTF_8, E_ACUTE);
K(I_GRAVE_LOWER_CASE_UTF_8, I_GRAVE);
K(N_TILDE_LOWER_CASE_UTF_8, N_TILDE);
K(O_GRAVE_LOWER_CASE_UTF_8, O_GRAVE);
K(O_DIAERESIS_LOWER_CASE_UTF_8, O_DIAERESIS);
K(O_SLASH_LOWER_CASE_UTF_8, O_SLASH);
K(U_GRAVE_LOWER_CASE_UTF_8, U_GRAVE);
K(U_DIAERESIS_LOWER_CASE_UTF_8, U_DIAERESIS);
K(CYRILLIC_A_LOWER_CASE_UTF_8, CYRILLIC_A);
K(CYRILLIC_BE_LOWER_CASE_UTF_8, CYRILLIC_BE);
K(CYRILLIC_VE_LOWER_CASE_UTF_8, CYRILLIC_VE);
K(CYRILLIC_GHE_LOWER_CASE_UTF_8, CYRILLIC_GHE);
K(CYRILLIC_DE_LOWER_CASE_UTF_8, CYRILLIC_DE);
K(CYRILLIC_IE_LOWER_CASE_UTF_8, CYRILLIC_IE);
K(CYRILLIC_ZHE_LOWER_CASE_UTF_8, CYRILLIC_ZHE);
K(CYRILLIC_ZE_LOWER_CASE_UTF_8, CYRILLIC_ZE);
K(CYRILLIC_I_LOWER_CASE_UTF_8, CYRILLIC_I);
K(CYRILLIC_SHORT_I_LOWER_CASE_UTF_8, CYRILLIC_SHORT_I);
K(CYRILLIC_KA_LOWER_CASE_UTF_8, CYRILLIC_KA);
K(CYRILLIC_EL_LOWER_CASE_UTF_8, CYRILLIC_EL);
K(CYRILLIC_EM_LOWER_CASE_UTF_8, CYRILLIC_EM);
K(CYRILLIC_EN_LOWER_CASE_UTF_8, CYRILLIC_EN);
K(CYRILLIC_O_LOWER_CASE_UTF_8, CYRILLIC_O);
K(CYRILLIC_PE_LOWER_CASE_UTF_8, CYRILLIC_PE);
K(CYRILLIC_ER_LOWER_CASE_UTF_8, CYRILLIC_ER);
K(CYRILLIC_ES_LOWER_CASE_UTF_8, CYRILLIC_ES);
K(CYRILLIC_TE_LOWER_CASE_UTF_8, CYRILLIC_TE);
K(CYRILLIC_U_LOWER_CASE_UTF_8, CYRILLIC_U);
K(CYRILLIC_EF_LOWER_CASE_UTF_8, CYRILLIC_EF);
K(CYRILLIC_HA_LOWER_CASE_UTF_8, CYRILLIC_HA);
K(CYRILLIC_TSE_LOWER_CASE_UTF_8, CYRILLIC_TSE);
K(CYRILLIC_CHE_LOWER_CASE_UTF_8, CYRILLIC_CHE);
K(CYRILLIC_SHA_LOWER_CASE_UTF_8, CYRILLIC_SHA);
K(CYRILLIC_SHCHA_LOWER_CASE_UTF_8, CYRILLIC_SHCHA);
K(CYRILLIC_HARD_SIGN_LOWER_CASE_UTF_8, CYRILLIC_HARD_SIGN);
K(CYRILLIC_YERU_LOWER_CASE_UTF_8, CYRILLIC_YERU);
K(CYRILLIC_SOFT_SIGN_LOWER_CASE_UTF_8, CYRILLIC_SOFT_SIGN);
K(CYRILLIC_E_LOWER_CASE_UTF_8, CYRILLIC_E);
K(CYRILLIC_YU_LOWER_CASE_UTF_8, CYRILLIC_YU);
K(CYRILLIC_YA_LOWER_CASE_UTF_8, CYRILLIC_YA);
K(CYRILLIC_IO_LOWER_CASE_UTF_8, CYRILLIC_IO);
#define K8(ch, name) case GLFW_KEY_##name: utf_8_key = ch; break;
#define K16(ch, name) case GLFW_KEY_##name: utf_16_key = ch; break;
#define K16_numpad(ch, mods, name) case GLFW_KEY_##name: utf_16_key = ch; *cocoa_mods |= mods; break;
K8('!', EXCLAM);
K8('"', DOUBLE_QUOTE);
K8('#', NUMBER_SIGN);
K8('$', DOLLAR);
K8('&', AMPERSAND);
K8('\'', APOSTROPHE);
K8('(', PARENTHESIS_LEFT);
K8(')', PARENTHESIS_RIGHT);
K8('+', PLUS);
K8(',', COMMA);
K8('-', MINUS);
K8('.', PERIOD);
K8('/', SLASH);
K8('0', 0);
K8('1', 1);
K8('2', 2);
K8('3', 3);
K8('5', 5);
K8('6', 6);
K8('7', 7);
K8('8', 8);
K8('9', 9);
K8(':', COLON);
K8(';', SEMICOLON);
K8('<', LESS);
K8('=', EQUAL);
K8('>', GREATER);
K8('@', AT);
K8('[', LEFT_BRACKET);
K8('\\', BACKSLASH);
K8(']', RIGHT_BRACKET);
K8('^', CIRCUMFLEX);
K8('_', UNDERSCORE);
K8('`', GRAVE_ACCENT);
K8('a', A);
K8('b', B);
K8('c', C);
K8('d', D);
K8('e', E);
K8('f', F);
K8('g', G);
K8('h', H);
K8('i', I);
K8('j', J);
K8('k', K);
K8('l', L);
K8('m', M);
K8('n', N);
K8('o', O);
K8('p', P);
K8('q', Q);
K8('r', R);
K8('s', S);
K8('t', T);
K8('u', U);
K8('v', V);
K8('w', W);
K8('x', X);
K8('y', Y);
K8('z', Z);
K8(PARAGRAPH_UTF_8, PARAGRAPH);
K8(MASCULINE_UTF_8, MASCULINE);
K8(S_SHARP_UTF_8, S_SHARP);
K8(A_GRAVE_LOWER_CASE_UTF_8, A_GRAVE);
K8(A_DIAERESIS_LOWER_CASE_UTF_8, A_DIAERESIS);
K8(A_RING_LOWER_CASE_UTF_8, A_RING);
K8(AE_LOWER_CASE_UTF_8, AE);
K8(C_CEDILLA_LOWER_CASE_UTF_8, C_CEDILLA);
K8(E_GRAVE_LOWER_CASE_UTF_8, E_GRAVE);
K8(E_ACUTE_LOWER_CASE_UTF_8, E_ACUTE);
K8(I_GRAVE_LOWER_CASE_UTF_8, I_GRAVE);
K8(N_TILDE_LOWER_CASE_UTF_8, N_TILDE);
K8(O_GRAVE_LOWER_CASE_UTF_8, O_GRAVE);
K8(O_DIAERESIS_LOWER_CASE_UTF_8, O_DIAERESIS);
K8(O_SLASH_LOWER_CASE_UTF_8, O_SLASH);
K8(U_GRAVE_LOWER_CASE_UTF_8, U_GRAVE);
K8(U_DIAERESIS_LOWER_CASE_UTF_8, U_DIAERESIS);
K8(CYRILLIC_A_LOWER_CASE_UTF_8, CYRILLIC_A);
K8(CYRILLIC_BE_LOWER_CASE_UTF_8, CYRILLIC_BE);
K8(CYRILLIC_VE_LOWER_CASE_UTF_8, CYRILLIC_VE);
K8(CYRILLIC_GHE_LOWER_CASE_UTF_8, CYRILLIC_GHE);
K8(CYRILLIC_DE_LOWER_CASE_UTF_8, CYRILLIC_DE);
K8(CYRILLIC_IE_LOWER_CASE_UTF_8, CYRILLIC_IE);
K8(CYRILLIC_ZHE_LOWER_CASE_UTF_8, CYRILLIC_ZHE);
K8(CYRILLIC_ZE_LOWER_CASE_UTF_8, CYRILLIC_ZE);
K8(CYRILLIC_I_LOWER_CASE_UTF_8, CYRILLIC_I);
K8(CYRILLIC_SHORT_I_LOWER_CASE_UTF_8, CYRILLIC_SHORT_I);
K8(CYRILLIC_KA_LOWER_CASE_UTF_8, CYRILLIC_KA);
K8(CYRILLIC_EL_LOWER_CASE_UTF_8, CYRILLIC_EL);
K8(CYRILLIC_EM_LOWER_CASE_UTF_8, CYRILLIC_EM);
K8(CYRILLIC_EN_LOWER_CASE_UTF_8, CYRILLIC_EN);
K8(CYRILLIC_O_LOWER_CASE_UTF_8, CYRILLIC_O);
K8(CYRILLIC_PE_LOWER_CASE_UTF_8, CYRILLIC_PE);
K8(CYRILLIC_ER_LOWER_CASE_UTF_8, CYRILLIC_ER);
K8(CYRILLIC_ES_LOWER_CASE_UTF_8, CYRILLIC_ES);
K8(CYRILLIC_TE_LOWER_CASE_UTF_8, CYRILLIC_TE);
K8(CYRILLIC_U_LOWER_CASE_UTF_8, CYRILLIC_U);
K8(CYRILLIC_EF_LOWER_CASE_UTF_8, CYRILLIC_EF);
K8(CYRILLIC_HA_LOWER_CASE_UTF_8, CYRILLIC_HA);
K8(CYRILLIC_TSE_LOWER_CASE_UTF_8, CYRILLIC_TSE);
K8(CYRILLIC_CHE_LOWER_CASE_UTF_8, CYRILLIC_CHE);
K8(CYRILLIC_SHA_LOWER_CASE_UTF_8, CYRILLIC_SHA);
K8(CYRILLIC_SHCHA_LOWER_CASE_UTF_8, CYRILLIC_SHCHA);
K8(CYRILLIC_HARD_SIGN_LOWER_CASE_UTF_8, CYRILLIC_HARD_SIGN);
K8(CYRILLIC_YERU_LOWER_CASE_UTF_8, CYRILLIC_YERU);
K8(CYRILLIC_SOFT_SIGN_LOWER_CASE_UTF_8, CYRILLIC_SOFT_SIGN);
K8(CYRILLIC_E_LOWER_CASE_UTF_8, CYRILLIC_E);
K8(CYRILLIC_YU_LOWER_CASE_UTF_8, CYRILLIC_YU);
K8(CYRILLIC_YA_LOWER_CASE_UTF_8, CYRILLIC_YA);
K8(CYRILLIC_IO_LOWER_CASE_UTF_8, CYRILLIC_IO);
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);
K8(0x35, ESCAPE);
K8('\r', ENTER);
K8('\t', TAB);
K16(NSBackspaceCharacter, BACKSPACE);
K16(NSInsertFunctionKey, INSERT);
K16(NSDeleteCharacter, DELETE);
K16(NSLeftArrowFunctionKey, LEFT);
K16(NSRightArrowFunctionKey, RIGHT);
K16(NSUpArrowFunctionKey, UP);
K16(NSDownArrowFunctionKey, DOWN);
K16(NSPageUpFunctionKey, PAGE_UP);
K16(NSPageDownFunctionKey, PAGE_DOWN);
K16(NSHomeFunctionKey, HOME);
K16(NSEndFunctionKey, END);
K16(NSPrintFunctionKey, PRINT_SCREEN);
case GLFW_KEY_F1 ... GLFW_KEY_F24:
*cocoa_key = NSF1FunctionKey + (glfw_key - GLFW_KEY_F1); break;
utf_16_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
utf_16_key = 0x52 + (glfw_key - GLFW_KEY_KP_0); *cocoa_mods |= NSEventModifierFlagNumericPad; break;
K16_numpad(0x41, NSEventModifierFlagNumericPad, KP_DECIMAL);
K16_numpad(0x43, NSEventModifierFlagNumericPad, KP_MULTIPLY);
K16_numpad(0x45, NSEventModifierFlagNumericPad, KP_ADD);
K16_numpad(0x4B, NSEventModifierFlagNumericPad, KP_DIVIDE);
K16_numpad(0x4E, NSEventModifierFlagNumericPad, KP_SUBTRACT);
K16_numpad(0x51, NSEventModifierFlagNumericPad, KP_EQUAL);
#undef K8
#undef K16
#undef K16_numpad
END_ALLOW_CASE_RANGE
}
if (utf_16_key != 0) {
NSString *tmp_cocoa_key = [NSString stringWithCharacters:&utf_16_key length:1];
*cocoa_key = _glfw_strdup([tmp_cocoa_key UTF8String]);
} else {
// 5 chars for one UTF-8 character plus a null byte should be enough
// Use 4 * 4 + 1 chars to allow returning at least four unicode characters in the future, should the need arise
char tmp_cocoa_key[4 * 4 + 1];
int str_pos = 0;
for (int i = 0; i < 4; i++) {
uint8_t byte = (utf_8_key >> 24) & 0xff;
utf_8_key <<= 8;
if (byte != 0) {
tmp_cocoa_key[str_pos] = byte;
str_pos++;
}
}
tmp_cocoa_key[str_pos] = 0;
*cocoa_key = _glfw_strdup(tmp_cocoa_key);
}
}

View File

@@ -166,7 +166,7 @@ def generate_wrappers(glfw_header):
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, int* cocoa_mods)
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char **cocoa_key, int* cocoa_mods)
void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback)
void* glfwGetX11Display(void)
int32_t glfwGetX11Window(GLFWwindow* window)

View File

@@ -97,7 +97,7 @@ find_app_name(void) {
@end
static unichar new_window_key = 0;
static char* new_window_key = NULL;
static NSEventModifierFlags new_window_mods = 0;
static PyObject*
@@ -253,7 +253,7 @@ cocoa_create_global_menu(void) {
[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];
NSString *s = @(new_window_key);
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];
@@ -473,6 +473,8 @@ cleanup() {
dockMenu = nil;
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
notification_activated_callback = NULL;
free(new_window_key);
new_window_key = NULL;
} // autoreleasepool
}

2
kitty/glfw-wrapper.h generated
View File

@@ -2083,7 +2083,7 @@ typedef GLFWapplicationwillfinishlaunchingfun (*glfwSetApplicationWillFinishLaun
glfwSetApplicationWillFinishLaunching_func glfwSetApplicationWillFinishLaunching_impl;
#define glfwSetApplicationWillFinishLaunching glfwSetApplicationWillFinishLaunching_impl
typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, void*, int*);
typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, char**, int*);
glfwGetCocoaKeyEquivalent_func glfwGetCocoaKeyEquivalent_impl;
#define glfwGetCocoaKeyEquivalent glfwGetCocoaKeyEquivalent_impl

View File

@@ -1079,7 +1079,7 @@ set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
#ifdef __APPLE__
void
get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *cocoa_mods) {
get_cocoa_key_equivalent(int key, int mods, char **cocoa_key, int *cocoa_mods) {
glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, cocoa_mods);
}

View File

@@ -225,7 +225,7 @@ 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*);
void get_cocoa_key_equivalent(int, int, char**, int*);
typedef enum {
PREFERENCES_WINDOW = 1,
NEW_OS_WINDOW = 2,