diff --git a/libobs/obs-cocoa.m b/libobs/obs-cocoa.m index fc93f8eeca4c88..7df53403b434e5 100644 --- a/libobs/obs-cocoa.m +++ b/libobs/obs-cocoa.m @@ -29,6 +29,8 @@ #import +// MARK: macOS Bundle Management + bool is_in_bundle() { NSRunningApplication *app = [NSRunningApplication currentApplication]; @@ -62,6 +64,8 @@ void add_default_module_paths(void) return path; } +// MARK: - macOS Hardware Info Helpers + static void log_processor_name(void) { char *name = NULL; @@ -164,6 +168,8 @@ void log_system_info(void) log_kernel_version(); } +// MARK: - Type Conversion Utilities + static bool dstr_from_cfstring(struct dstr *str, CFStringRef ref) { CFIndex length = CFStringGetLength(ref); @@ -177,6 +183,247 @@ static bool dstr_from_cfstring(struct dstr *str, CFStringRef ref) return true; } +// MARK: - Graphics Thread Wrappers + +void *obs_graphics_thread_autorelease(void *param) +{ + @autoreleasepool { + return obs_graphics_thread(param); + } +} + +bool obs_graphics_thread_loop_autorelease(struct obs_graphics_context *context) +{ + @autoreleasepool { + return obs_graphics_thread_loop(context); + } +} + +// MARK: - macOS Hotkey Management + +typedef struct obs_key_code { + int code; + bool is_valid; +} obs_key_code_t; + +typedef struct macOS_glyph_desc { + UniChar glyph; + char *desc; + bool is_glyph; + bool is_valid; +} macOS_glyph_desc_t; + +typedef struct obs_key_desc { + char *desc; + bool is_valid; +} obs_key_desc_t; + +static int INVALID_KEY = 0xFF; + +/* clang-format off */ +static const obs_key_code_t virtual_keys[OBS_KEY_LAST_VALUE] = { + [OBS_KEY_A] = {.code = kVK_ANSI_A, .is_valid = true}, + [OBS_KEY_B] = {.code = kVK_ANSI_B, .is_valid = true}, + [OBS_KEY_C] = {.code = kVK_ANSI_C, .is_valid = true}, + [OBS_KEY_D] = {.code = kVK_ANSI_D, .is_valid = true}, + [OBS_KEY_E] = {.code = kVK_ANSI_E, .is_valid = true}, + [OBS_KEY_F] = {.code = kVK_ANSI_F, .is_valid = true}, + [OBS_KEY_G] = {.code = kVK_ANSI_G, .is_valid = true}, + [OBS_KEY_H] = {.code = kVK_ANSI_H, .is_valid = true}, + [OBS_KEY_I] = {.code = kVK_ANSI_I, .is_valid = true}, + [OBS_KEY_J] = {.code = kVK_ANSI_J, .is_valid = true}, + [OBS_KEY_K] = {.code = kVK_ANSI_K, .is_valid = true}, + [OBS_KEY_L] = {.code = kVK_ANSI_L, .is_valid = true}, + [OBS_KEY_M] = {.code = kVK_ANSI_M, .is_valid = true}, + [OBS_KEY_N] = {.code = kVK_ANSI_N, .is_valid = true}, + [OBS_KEY_O] = {.code = kVK_ANSI_O, .is_valid = true}, + [OBS_KEY_P] = {.code = kVK_ANSI_P, .is_valid = true}, + [OBS_KEY_Q] = {.code = kVK_ANSI_Q, .is_valid = true}, + [OBS_KEY_R] = {.code = kVK_ANSI_R, .is_valid = true}, + [OBS_KEY_S] = {.code = kVK_ANSI_S, .is_valid = true}, + [OBS_KEY_T] = {.code = kVK_ANSI_T, .is_valid = true}, + [OBS_KEY_U] = {.code = kVK_ANSI_U, .is_valid = true}, + [OBS_KEY_V] = {.code = kVK_ANSI_V, .is_valid = true}, + [OBS_KEY_W] = {.code = kVK_ANSI_W, .is_valid = true}, + [OBS_KEY_X] = {.code = kVK_ANSI_X, .is_valid = true}, + [OBS_KEY_Y] = {.code = kVK_ANSI_Y, .is_valid = true}, + [OBS_KEY_Z] = {.code = kVK_ANSI_Z, .is_valid = true}, + [OBS_KEY_1] = {.code = kVK_ANSI_1, .is_valid = true}, + [OBS_KEY_2] = {.code = kVK_ANSI_2, .is_valid = true}, + [OBS_KEY_3] = {.code = kVK_ANSI_3, .is_valid = true}, + [OBS_KEY_4] = {.code = kVK_ANSI_4, .is_valid = true}, + [OBS_KEY_5] = {.code = kVK_ANSI_5, .is_valid = true}, + [OBS_KEY_6] = {.code = kVK_ANSI_6, .is_valid = true}, + [OBS_KEY_7] = {.code = kVK_ANSI_7, .is_valid = true}, + [OBS_KEY_8] = {.code = kVK_ANSI_8, .is_valid = true}, + [OBS_KEY_9] = {.code = kVK_ANSI_9, .is_valid = true}, + [OBS_KEY_0] = {.code = kVK_ANSI_0, .is_valid = true}, + [OBS_KEY_RETURN] = {.code = kVK_Return, .is_valid = true}, + [OBS_KEY_ESCAPE] = {.code = kVK_Escape, .is_valid = true}, + [OBS_KEY_BACKSPACE] = {.code = kVK_Delete, .is_valid = true}, + [OBS_KEY_TAB] = {.code = kVK_Tab, .is_valid = true}, + [OBS_KEY_SPACE] = {.code = kVK_Space, .is_valid = true}, + [OBS_KEY_MINUS] = {.code = kVK_ANSI_Minus, .is_valid = true}, + [OBS_KEY_EQUAL] = {.code = kVK_ANSI_Equal, .is_valid = true}, + [OBS_KEY_BRACKETLEFT] = {.code = kVK_ANSI_LeftBracket, .is_valid = true}, + [OBS_KEY_BRACKETRIGHT] = {.code = kVK_ANSI_RightBracket, .is_valid = true}, + [OBS_KEY_BACKSLASH] = {.code = kVK_ANSI_Backslash, .is_valid = true}, + [OBS_KEY_SEMICOLON] = {.code = kVK_ANSI_Semicolon, .is_valid = true}, + [OBS_KEY_QUOTE] = {.code = kVK_ANSI_Quote, .is_valid = true}, + [OBS_KEY_DEAD_GRAVE] = {.code = kVK_ANSI_Grave, .is_valid = true}, + [OBS_KEY_COMMA] = {.code = kVK_ANSI_Comma, .is_valid = true}, + [OBS_KEY_PERIOD] = {.code = kVK_ANSI_Period, .is_valid = true}, + [OBS_KEY_SLASH] = {.code = kVK_ANSI_Slash, .is_valid = true}, + [OBS_KEY_CAPSLOCK] = {.code = kVK_CapsLock, .is_valid = true}, + [OBS_KEY_SECTION] = {.code = kVK_ISO_Section, .is_valid = true}, + [OBS_KEY_F1] = {.code = kVK_F1, .is_valid = true}, + [OBS_KEY_F2] = {.code = kVK_F2, .is_valid = true}, + [OBS_KEY_F3] = {.code = kVK_F3, .is_valid = true}, + [OBS_KEY_F4] = {.code = kVK_F4, .is_valid = true}, + [OBS_KEY_F5] = {.code = kVK_F5, .is_valid = true}, + [OBS_KEY_F6] = {.code = kVK_F6, .is_valid = true}, + [OBS_KEY_F7] = {.code = kVK_F7, .is_valid = true}, + [OBS_KEY_F8] = {.code = kVK_F8, .is_valid = true}, + [OBS_KEY_F9] = {.code = kVK_F9, .is_valid = true}, + [OBS_KEY_F10] = {.code = kVK_F10, .is_valid = true}, + [OBS_KEY_F11] = {.code = kVK_F11, .is_valid = true}, + [OBS_KEY_F12] = {.code = kVK_F12, .is_valid = true}, + [OBS_KEY_HELP] = {.code = kVK_Help, .is_valid = true}, + [OBS_KEY_HOME] = {.code = kVK_Home, .is_valid = true}, + [OBS_KEY_PAGEUP] = {.code = kVK_PageUp, .is_valid = true}, + [OBS_KEY_DELETE] = {.code = kVK_ForwardDelete, .is_valid = true}, + [OBS_KEY_END] = {.code = kVK_End, .is_valid = true}, + [OBS_KEY_PAGEDOWN] = {.code = kVK_PageDown, .is_valid = true}, + [OBS_KEY_RIGHT] = {.code = kVK_RightArrow, .is_valid = true}, + [OBS_KEY_LEFT] = {.code = kVK_LeftArrow, .is_valid = true}, + [OBS_KEY_DOWN] = {.code = kVK_DownArrow, .is_valid = true}, + [OBS_KEY_UP] = {.code = kVK_UpArrow, .is_valid = true}, + [OBS_KEY_CLEAR] = {.code = kVK_ANSI_KeypadClear, .is_valid = true}, + [OBS_KEY_NUMSLASH] = {.code = kVK_ANSI_KeypadDivide, .is_valid = true}, + [OBS_KEY_NUMASTERISK] = {.code = kVK_ANSI_KeypadMultiply, .is_valid = true}, + [OBS_KEY_NUMMINUS] = {.code = kVK_ANSI_KeypadMinus, .is_valid = true}, + [OBS_KEY_NUMPLUS] = {.code = kVK_ANSI_KeypadPlus, .is_valid = true}, + [OBS_KEY_ENTER] = {.code = kVK_ANSI_KeypadEnter, .is_valid = true}, + [OBS_KEY_NUM1] = {.code = kVK_ANSI_Keypad1, .is_valid = true}, + [OBS_KEY_NUM2] = {.code = kVK_ANSI_Keypad2, .is_valid = true}, + [OBS_KEY_NUM3] = {.code = kVK_ANSI_Keypad3, .is_valid = true}, + [OBS_KEY_NUM4] = {.code = kVK_ANSI_Keypad4, .is_valid = true}, + [OBS_KEY_NUM5] = {.code = kVK_ANSI_Keypad5, .is_valid = true}, + [OBS_KEY_NUM6] = {.code = kVK_ANSI_Keypad6, .is_valid = true}, + [OBS_KEY_NUM7] = {.code = kVK_ANSI_Keypad7, .is_valid = true}, + [OBS_KEY_NUM8] = {.code = kVK_ANSI_Keypad8, .is_valid = true}, + [OBS_KEY_NUM9] = {.code = kVK_ANSI_Keypad9, .is_valid = true}, + [OBS_KEY_NUM0] = {.code = kVK_ANSI_Keypad0, .is_valid = true}, + [OBS_KEY_NUMPERIOD] = {.code = kVK_ANSI_KeypadDecimal, .is_valid = true}, + [OBS_KEY_NUMEQUAL] = {.code = kVK_ANSI_KeypadEquals, .is_valid = true}, + [OBS_KEY_F13] = {.code = kVK_F13, .is_valid = true}, + [OBS_KEY_F14] = {.code = kVK_F14, .is_valid = true}, + [OBS_KEY_F15] = {.code = kVK_F15, .is_valid = true}, + [OBS_KEY_F16] = {.code = kVK_F16, .is_valid = true}, + [OBS_KEY_F17] = {.code = kVK_F17, .is_valid = true}, + [OBS_KEY_F18] = {.code = kVK_F18, .is_valid = true}, + [OBS_KEY_F19] = {.code = kVK_F19, .is_valid = true}, + [OBS_KEY_F20] = {.code = kVK_F20, .is_valid = true}, + [OBS_KEY_CONTROL] = {.code = kVK_Control, .is_valid = true}, + [OBS_KEY_SHIFT] = {.code = kVK_Shift, .is_valid = true}, + [OBS_KEY_ALT] = {.code = kVK_Option, .is_valid = true}, + [OBS_KEY_META] = {.code = kVK_Command, .is_valid = true}, +}; + +static const obs_key_desc_t key_descriptions[OBS_KEY_LAST_VALUE] = { + [OBS_KEY_SPACE] = {.desc = "Space", .is_valid = true}, + [OBS_KEY_NUMEQUAL] = {.desc = "= (Keypad)", .is_valid = true}, + [OBS_KEY_NUMASTERISK] = {.desc = "* (Keypad)", .is_valid = true}, + [OBS_KEY_NUMPLUS] = {.desc = "+ (Keypad)", .is_valid = true}, + [OBS_KEY_NUMMINUS] = {.desc = "- (Keypad)", .is_valid = true}, + [OBS_KEY_NUMPERIOD] = {.desc = ". (Keypad)", .is_valid = true}, + [OBS_KEY_NUMSLASH] = {.desc = "/ (Keypad)", .is_valid = true}, + [OBS_KEY_NUM0] = {.desc = "0 (Keypad)", .is_valid = true}, + [OBS_KEY_NUM1] = {.desc = "1 (Keypad)", .is_valid = true}, + [OBS_KEY_NUM2] = {.desc = "2 (Keypad)", .is_valid = true}, + [OBS_KEY_NUM3] = {.desc = "3 (Keypad)", .is_valid = true}, + [OBS_KEY_NUM4] = {.desc = "4 (Keypad)", .is_valid = true}, + [OBS_KEY_NUM5] = {.desc = "5 (Keypad)", .is_valid = true}, + [OBS_KEY_NUM6] = {.desc = "6 (Keypad)", .is_valid = true}, + [OBS_KEY_NUM7] = {.desc = "7 (Keypad)", .is_valid = true}, + [OBS_KEY_NUM8] = {.desc = "8 (Keypad)", .is_valid = true}, + [OBS_KEY_NUM9] = {.desc = "9 (Keypad)", .is_valid = true}, + [OBS_KEY_MOUSE1] = {.desc = "Mouse 1", .is_valid = true}, + [OBS_KEY_MOUSE2] = {.desc = "Mouse 2", .is_valid = true}, + [OBS_KEY_MOUSE3] = {.desc = "Mouse 3", .is_valid = true}, + [OBS_KEY_MOUSE4] = {.desc = "Mouse 4", .is_valid = true}, + [OBS_KEY_MOUSE5] = {.desc = "Mouse 5", .is_valid = true}, + [OBS_KEY_MOUSE6] = {.desc = "Mouse 6", .is_valid = true}, + [OBS_KEY_MOUSE7] = {.desc = "Mouse 7", .is_valid = true}, + [OBS_KEY_MOUSE8] = {.desc = "Mouse 8", .is_valid = true}, + [OBS_KEY_MOUSE9] = {.desc = "Mouse 9", .is_valid = true}, + [OBS_KEY_MOUSE10] = {.desc = "Mouse 10", .is_valid = true}, + [OBS_KEY_MOUSE11] = {.desc = "Mouse 11", .is_valid = true}, + [OBS_KEY_MOUSE12] = {.desc = "Mouse 12", .is_valid = true}, + [OBS_KEY_MOUSE13] = {.desc = "Mouse 13", .is_valid = true}, + [OBS_KEY_MOUSE14] = {.desc = "Mouse 14", .is_valid = true}, + [OBS_KEY_MOUSE15] = {.desc = "Mouse 15", .is_valid = true}, + [OBS_KEY_MOUSE16] = {.desc = "Mouse 16", .is_valid = true}, + [OBS_KEY_MOUSE17] = {.desc = "Mouse 17", .is_valid = true}, + [OBS_KEY_MOUSE18] = {.desc = "Mouse 18", .is_valid = true}, + [OBS_KEY_MOUSE19] = {.desc = "Mouse 19", .is_valid = true}, + [OBS_KEY_MOUSE20] = {.desc = "Mouse 20", .is_valid = true}, + [OBS_KEY_MOUSE21] = {.desc = "Mouse 21", .is_valid = true}, + [OBS_KEY_MOUSE22] = {.desc = "Mouse 22", .is_valid = true}, + [OBS_KEY_MOUSE23] = {.desc = "Mouse 23", .is_valid = true}, + [OBS_KEY_MOUSE24] = {.desc = "Mouse 24", .is_valid = true}, + [OBS_KEY_MOUSE25] = {.desc = "Mouse 25", .is_valid = true}, + [OBS_KEY_MOUSE26] = {.desc = "Mouse 26", .is_valid = true}, + [OBS_KEY_MOUSE27] = {.desc = "Mouse 27", .is_valid = true}, + [OBS_KEY_MOUSE28] = {.desc = "Mouse 28", .is_valid = true}, + [OBS_KEY_MOUSE29] = {.desc = "Mouse 29", .is_valid = true}, +}; + +static const macOS_glyph_desc_t key_glyphs[(keyCodeMask >> 8)] = { + [kVK_Return] = {.glyph = 0x21A9, .is_glyph = true, .is_valid = true}, + [kVK_Escape] = {.glyph = 0x238B, .is_glyph = true, .is_valid = true}, + [kVK_Delete] = {.glyph = 0x232B, .is_glyph = true, .is_valid = true}, + [kVK_Tab] = {.glyph = 0x21e5, .is_glyph = true, .is_valid = true}, + [kVK_CapsLock] = {.glyph = 0x21EA, .is_glyph = true, .is_valid = true}, + [kVK_ANSI_KeypadClear] = {.glyph = 0x2327, .is_glyph = true, .is_valid = true}, + [kVK_ANSI_KeypadEnter] = {.glyph = 0x2305, .is_glyph = true, .is_valid = true}, + [kVK_Help] = {.glyph = 0x003F, .is_glyph = true, .is_valid = true}, + [kVK_Home] = {.glyph = 0x2196, .is_glyph = true, .is_valid = true}, + [kVK_PageUp] = {.glyph = 0x21de, .is_glyph = true, .is_valid = true}, + [kVK_ForwardDelete] = {.glyph = 0x2326, .is_glyph = true, .is_valid = true}, + [kVK_End] = {.glyph = 0x2198, .is_glyph = true, .is_valid = true}, + [kVK_PageDown] = {.glyph = 0x21df, .is_glyph = true, .is_valid = true}, + [kVK_Control] = {.glyph = kControlUnicode, .is_glyph = true, .is_valid = true}, + [kVK_Shift] = {.glyph = kShiftUnicode, .is_glyph = true, .is_valid = true}, + [kVK_Option] = {.glyph = kOptionUnicode, .is_glyph = true, .is_valid = true}, + [kVK_Command] = {.glyph = kCommandUnicode, .is_glyph = true, .is_valid = true}, + [kVK_RightControl] = {.glyph = kControlUnicode, .is_glyph = true, .is_valid = true}, + [kVK_RightShift] = {.glyph = kShiftUnicode, .is_glyph = true, .is_valid = true}, + [kVK_RightOption] = {.glyph = kOptionUnicode, .is_glyph = true, .is_valid = true}, + [kVK_F1] = {.desc = "F1", .is_valid = true}, + [kVK_F2] = {.desc = "F2", .is_valid = true}, + [kVK_F3] = {.desc = "F3", .is_valid = true}, + [kVK_F4] = {.desc = "F4", .is_valid = true}, + [kVK_F5] = {.desc = "F5", .is_valid = true}, + [kVK_F6] = {.desc = "F6", .is_valid = true}, + [kVK_F7] = {.desc = "F7", .is_valid = true}, + [kVK_F8] = {.desc = "F8", .is_valid = true}, + [kVK_F9] = {.desc = "F9", .is_valid = true}, + [kVK_F10] = {.desc = "F10", .is_valid = true}, + [kVK_F11] = {.desc = "F11", .is_valid = true}, + [kVK_F12] = {.desc = "F12", .is_valid = true}, + [kVK_F13] = {.desc = "F13", .is_valid = true}, + [kVK_F14] = {.desc = "F14", .is_valid = true}, + [kVK_F15] = {.desc = "F15", .is_valid = true}, + [kVK_F16] = {.desc = "F16", .is_valid = true}, + [kVK_F17] = {.desc = "F17", .is_valid = true}, + [kVK_F18] = {.desc = "F18", .is_valid = true}, + [kVK_F19] = {.desc = "F19", .is_valid = true}, + [kVK_F20] = {.desc = "F20", .is_valid = true} +}; + +/* clang-format on */ + struct obs_hotkeys_platform { volatile long refs; CFTypeRef monitor; @@ -187,702 +434,392 @@ static bool dstr_from_cfstring(struct dstr *str, CFStringRef ref) UCKeyboardLayout *layout; }; -static void hotkeys_retain(struct obs_hotkeys_platform *plat) -{ - os_atomic_inc_long(&plat->refs); -} +// MARK: macOS Hotkey Implementation +#define OBS_COCOA_MODIFIER_SIZE (int) 7 -static inline void free_hotkeys_platform(obs_hotkeys_platform_t *plat); +static char string_control[OBS_COCOA_MODIFIER_SIZE]; +static char string_option[OBS_COCOA_MODIFIER_SIZE]; +static char string_shift[OBS_COCOA_MODIFIER_SIZE]; +static char string_command[OBS_COCOA_MODIFIER_SIZE]; +static dispatch_once_t onceToken; -static void hotkeys_release(struct obs_hotkeys_platform *plat) +static void hotkeys_retain(obs_hotkeys_platform_t *platform) { - if (os_atomic_dec_long(&plat->refs) == -1) - free_hotkeys_platform(plat); + os_atomic_inc_long(&platform->refs); } -#define INVALID_KEY 0xff - -#pragma GCC diagnostic ignored "-Winitializer-overrides" -static const int virtual_keys[] = { - [0 ... OBS_KEY_LAST_VALUE] = INVALID_KEY, - - [OBS_KEY_A] = kVK_ANSI_A, - [OBS_KEY_B] = kVK_ANSI_B, - [OBS_KEY_C] = kVK_ANSI_C, - [OBS_KEY_D] = kVK_ANSI_D, - [OBS_KEY_E] = kVK_ANSI_E, - [OBS_KEY_F] = kVK_ANSI_F, - [OBS_KEY_G] = kVK_ANSI_G, - [OBS_KEY_H] = kVK_ANSI_H, - [OBS_KEY_I] = kVK_ANSI_I, - [OBS_KEY_J] = kVK_ANSI_J, - [OBS_KEY_K] = kVK_ANSI_K, - [OBS_KEY_L] = kVK_ANSI_L, - [OBS_KEY_M] = kVK_ANSI_M, - [OBS_KEY_N] = kVK_ANSI_N, - [OBS_KEY_O] = kVK_ANSI_O, - [OBS_KEY_P] = kVK_ANSI_P, - [OBS_KEY_Q] = kVK_ANSI_Q, - [OBS_KEY_R] = kVK_ANSI_R, - [OBS_KEY_S] = kVK_ANSI_S, - [OBS_KEY_T] = kVK_ANSI_T, - [OBS_KEY_U] = kVK_ANSI_U, - [OBS_KEY_V] = kVK_ANSI_V, - [OBS_KEY_W] = kVK_ANSI_W, - [OBS_KEY_X] = kVK_ANSI_X, - [OBS_KEY_Y] = kVK_ANSI_Y, - [OBS_KEY_Z] = kVK_ANSI_Z, - - [OBS_KEY_1] = kVK_ANSI_1, - [OBS_KEY_2] = kVK_ANSI_2, - [OBS_KEY_3] = kVK_ANSI_3, - [OBS_KEY_4] = kVK_ANSI_4, - [OBS_KEY_5] = kVK_ANSI_5, - [OBS_KEY_6] = kVK_ANSI_6, - [OBS_KEY_7] = kVK_ANSI_7, - [OBS_KEY_8] = kVK_ANSI_8, - [OBS_KEY_9] = kVK_ANSI_9, - [OBS_KEY_0] = kVK_ANSI_0, - - [OBS_KEY_RETURN] = kVK_Return, - [OBS_KEY_ESCAPE] = kVK_Escape, - [OBS_KEY_BACKSPACE] = kVK_Delete, - [OBS_KEY_TAB] = kVK_Tab, - [OBS_KEY_SPACE] = kVK_Space, - [OBS_KEY_MINUS] = kVK_ANSI_Minus, - [OBS_KEY_EQUAL] = kVK_ANSI_Equal, - [OBS_KEY_BRACKETLEFT] = kVK_ANSI_LeftBracket, - [OBS_KEY_BRACKETRIGHT] = kVK_ANSI_RightBracket, - [OBS_KEY_BACKSLASH] = kVK_ANSI_Backslash, - [OBS_KEY_SEMICOLON] = kVK_ANSI_Semicolon, - [OBS_KEY_QUOTE] = kVK_ANSI_Quote, - [OBS_KEY_DEAD_GRAVE] = kVK_ANSI_Grave, - [OBS_KEY_COMMA] = kVK_ANSI_Comma, - [OBS_KEY_PERIOD] = kVK_ANSI_Period, - [OBS_KEY_SLASH] = kVK_ANSI_Slash, - [OBS_KEY_CAPSLOCK] = kVK_CapsLock, - [OBS_KEY_SECTION] = kVK_ISO_Section, - - [OBS_KEY_F1] = kVK_F1, - [OBS_KEY_F2] = kVK_F2, - [OBS_KEY_F3] = kVK_F3, - [OBS_KEY_F4] = kVK_F4, - [OBS_KEY_F5] = kVK_F5, - [OBS_KEY_F6] = kVK_F6, - [OBS_KEY_F7] = kVK_F7, - [OBS_KEY_F8] = kVK_F8, - [OBS_KEY_F9] = kVK_F9, - [OBS_KEY_F10] = kVK_F10, - [OBS_KEY_F11] = kVK_F11, - [OBS_KEY_F12] = kVK_F12, - - [OBS_KEY_HELP] = kVK_Help, - [OBS_KEY_HOME] = kVK_Home, - [OBS_KEY_PAGEUP] = kVK_PageUp, - [OBS_KEY_DELETE] = kVK_ForwardDelete, - [OBS_KEY_END] = kVK_End, - [OBS_KEY_PAGEDOWN] = kVK_PageDown, - - [OBS_KEY_RIGHT] = kVK_RightArrow, - [OBS_KEY_LEFT] = kVK_LeftArrow, - [OBS_KEY_DOWN] = kVK_DownArrow, - [OBS_KEY_UP] = kVK_UpArrow, - - [OBS_KEY_CLEAR] = kVK_ANSI_KeypadClear, - [OBS_KEY_NUMSLASH] = kVK_ANSI_KeypadDivide, - [OBS_KEY_NUMASTERISK] = kVK_ANSI_KeypadMultiply, - [OBS_KEY_NUMMINUS] = kVK_ANSI_KeypadMinus, - [OBS_KEY_NUMPLUS] = kVK_ANSI_KeypadPlus, - [OBS_KEY_ENTER] = kVK_ANSI_KeypadEnter, - - [OBS_KEY_NUM1] = kVK_ANSI_Keypad1, - [OBS_KEY_NUM2] = kVK_ANSI_Keypad2, - [OBS_KEY_NUM3] = kVK_ANSI_Keypad3, - [OBS_KEY_NUM4] = kVK_ANSI_Keypad4, - [OBS_KEY_NUM5] = kVK_ANSI_Keypad5, - [OBS_KEY_NUM6] = kVK_ANSI_Keypad6, - [OBS_KEY_NUM7] = kVK_ANSI_Keypad7, - [OBS_KEY_NUM8] = kVK_ANSI_Keypad8, - [OBS_KEY_NUM9] = kVK_ANSI_Keypad9, - [OBS_KEY_NUM0] = kVK_ANSI_Keypad0, - - [OBS_KEY_NUMPERIOD] = kVK_ANSI_KeypadDecimal, - [OBS_KEY_NUMEQUAL] = kVK_ANSI_KeypadEquals, - - [OBS_KEY_F13] = kVK_F13, - [OBS_KEY_F14] = kVK_F14, - [OBS_KEY_F15] = kVK_F15, - [OBS_KEY_F16] = kVK_F16, - [OBS_KEY_F17] = kVK_F17, - [OBS_KEY_F18] = kVK_F18, - [OBS_KEY_F19] = kVK_F19, - [OBS_KEY_F20] = kVK_F20, - - [OBS_KEY_CONTROL] = kVK_Control, - [OBS_KEY_SHIFT] = kVK_Shift, - [OBS_KEY_ALT] = kVK_Option, - [OBS_KEY_META] = kVK_Command, - [OBS_KEY_CONTROL] = kVK_RightControl, -}; - -int obs_key_to_virtual_key(obs_key_t code) +static void hotkeys_release(obs_hotkeys_platform_t *platform) { - return virtual_keys[code]; -} + if (os_atomic_dec_long(&platform->refs) == -1) { + if (platform->tis) { + CFRelease(platform->tis); + platform->tis = NULL; + } -obs_key_t obs_key_from_virtual_key(int code) -{ - if (code == kVK_RightShift) - return OBS_KEY_SHIFT; - if (code == kVK_RightOption) - return OBS_KEY_ALT; - if (code == kVK_RightCommand) - return OBS_KEY_META; - if (code == kVK_RightControl) - return OBS_KEY_META; - for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { - if (virtual_keys[i] == code) { - return (obs_key_t) i; + if (platform->layout_data) { + CFRelease(platform->layout_data); + platform->layout_data = NULL; + } + + if (platform->monitor) { + [NSEvent removeMonitor:(__bridge id _Nonnull)(platform->monitor)]; + platform->monitor = NULL; + } + + if (platform->local_monitor) { + [NSEvent removeMonitor:(__bridge id _Nonnull)(platform->local_monitor)]; + platform->local_monitor = NULL; } + + bfree(platform); } - return OBS_KEY_NONE; } -static bool localized_key_to_str(obs_key_t key, struct dstr *str) +static bool obs_key_to_localized_string(obs_key_t key, struct dstr *str) { -#define MAP_KEY(k, s) \ - case k: \ - dstr_copy(str, obs_get_hotkey_translation(k, s)); \ - return true - -#define MAP_BUTTON(i) \ - case OBS_KEY_MOUSE##i: \ - dstr_copy(str, obs_get_hotkey_translation(key, "Mouse " #i)); \ - return true - - switch (key) { - MAP_KEY(OBS_KEY_SPACE, "Space"); - MAP_KEY(OBS_KEY_NUMEQUAL, "= (Keypad)"); - MAP_KEY(OBS_KEY_NUMASTERISK, "* (Keypad)"); - MAP_KEY(OBS_KEY_NUMPLUS, "+ (Keypad)"); - MAP_KEY(OBS_KEY_NUMMINUS, "- (Keypad)"); - MAP_KEY(OBS_KEY_NUMPERIOD, ". (Keypad)"); - MAP_KEY(OBS_KEY_NUMSLASH, "/ (Keypad)"); - MAP_KEY(OBS_KEY_NUM0, "0 (Keypad)"); - MAP_KEY(OBS_KEY_NUM1, "1 (Keypad)"); - MAP_KEY(OBS_KEY_NUM2, "2 (Keypad)"); - MAP_KEY(OBS_KEY_NUM3, "3 (Keypad)"); - MAP_KEY(OBS_KEY_NUM4, "4 (Keypad)"); - MAP_KEY(OBS_KEY_NUM5, "5 (Keypad)"); - MAP_KEY(OBS_KEY_NUM6, "6 (Keypad)"); - MAP_KEY(OBS_KEY_NUM7, "7 (Keypad)"); - MAP_KEY(OBS_KEY_NUM8, "8 (Keypad)"); - MAP_KEY(OBS_KEY_NUM9, "9 (Keypad)"); - - MAP_BUTTON(1); - MAP_BUTTON(2); - MAP_BUTTON(3); - MAP_BUTTON(4); - MAP_BUTTON(5); - MAP_BUTTON(6); - MAP_BUTTON(7); - MAP_BUTTON(8); - MAP_BUTTON(9); - MAP_BUTTON(10); - MAP_BUTTON(11); - MAP_BUTTON(12); - MAP_BUTTON(13); - MAP_BUTTON(14); - MAP_BUTTON(15); - MAP_BUTTON(16); - MAP_BUTTON(17); - MAP_BUTTON(18); - MAP_BUTTON(19); - MAP_BUTTON(20); - MAP_BUTTON(21); - MAP_BUTTON(22); - MAP_BUTTON(23); - MAP_BUTTON(24); - MAP_BUTTON(25); - MAP_BUTTON(26); - MAP_BUTTON(27); - MAP_BUTTON(28); - MAP_BUTTON(29); - default: - break; + if (key < OBS_KEY_LAST_VALUE && !key_descriptions[key].is_valid) { + return false; } -#undef MAP_BUTTON -#undef MAP_KEY - return false; + dstr_copy(str, obs_get_hotkey_translation(key, key_descriptions[key].desc)); + return true; } -static bool code_to_str(int code, struct dstr *str) +static bool key_code_to_string(int code, struct dstr *str) { -#define MAP_GLYPH(c, g) \ - case c: \ - dstr_from_wcs(str, (wchar_t[]) {g, 0}); \ - return true -#define MAP_STR(c, s) \ - case c: \ - dstr_copy(str, s); \ - return true - switch (code) { - MAP_GLYPH(kVK_Return, 0x21A9); - MAP_GLYPH(kVK_Escape, 0x238B); - MAP_GLYPH(kVK_Delete, 0x232B); - MAP_GLYPH(kVK_Tab, 0x21e5); - MAP_GLYPH(kVK_CapsLock, 0x21EA); - MAP_GLYPH(kVK_ANSI_KeypadClear, 0x2327); - MAP_GLYPH(kVK_ANSI_KeypadEnter, 0x2305); - MAP_GLYPH(kVK_Help, 0x003F); - MAP_GLYPH(kVK_Home, 0x2196); - MAP_GLYPH(kVK_PageUp, 0x21de); - MAP_GLYPH(kVK_ForwardDelete, 0x2326); - MAP_GLYPH(kVK_End, 0x2198); - MAP_GLYPH(kVK_PageDown, 0x21df); - - MAP_GLYPH(kVK_RightArrow, 0x2192); - MAP_GLYPH(kVK_LeftArrow, 0x2190); - MAP_GLYPH(kVK_DownArrow, 0x2193); - MAP_GLYPH(kVK_UpArrow, 0x2191); - - MAP_STR(kVK_F1, "F1"); - MAP_STR(kVK_F2, "F2"); - MAP_STR(kVK_F3, "F3"); - MAP_STR(kVK_F4, "F4"); - MAP_STR(kVK_F5, "F5"); - MAP_STR(kVK_F6, "F6"); - MAP_STR(kVK_F7, "F7"); - MAP_STR(kVK_F8, "F8"); - MAP_STR(kVK_F9, "F9"); - MAP_STR(kVK_F10, "F10"); - MAP_STR(kVK_F11, "F11"); - MAP_STR(kVK_F12, "F12"); - MAP_STR(kVK_F13, "F13"); - MAP_STR(kVK_F14, "F14"); - MAP_STR(kVK_F15, "F15"); - MAP_STR(kVK_F16, "F16"); - MAP_STR(kVK_F17, "F17"); - MAP_STR(kVK_F18, "F18"); - MAP_STR(kVK_F19, "F19"); - MAP_STR(kVK_F20, "F20"); - MAP_GLYPH(kVK_Control, kControlUnicode); - MAP_GLYPH(kVK_Shift, kShiftUnicode); - MAP_GLYPH(kVK_Option, kOptionUnicode); - MAP_GLYPH(kVK_Command, kCommandUnicode); - MAP_GLYPH(kVK_RightControl, kControlUnicode); - MAP_GLYPH(kVK_RightShift, kShiftUnicode); - MAP_GLYPH(kVK_RightOption, kOptionUnicode); + if (code < INVALID_KEY) { + macOS_glyph_desc_t glyph = key_glyphs[code]; + + if (glyph.is_valid && glyph.is_glyph && glyph.glyph > 0) { + dstr_from_wcs(str, (wchar_t[]) {glyph.glyph, 0}); + } else if (glyph.is_valid && glyph.desc) { + dstr_copy(str, glyph.desc); + } else { + return false; + } } -#undef MAP_STR -#undef MAP_GLYPH - return false; + return true; } -void obs_key_to_str(obs_key_t key, struct dstr *str) +static bool log_layout_name(TISInputSourceRef tis) { - const UniCharCount max_length = 16; - UniChar buffer[16]; - - if (localized_key_to_str(key, str)) - return; - - int code = obs_key_to_virtual_key(key); - if (code_to_str(code, str)) - return; + struct dstr layout_name = {0}; + CFStringRef sid = (CFStringRef) TISGetInputSourceProperty(tis, kTISPropertyInputSourceID); - if (code == INVALID_KEY) { - blog(LOG_ERROR, - "hotkey-cocoa: Got invalid key while " - "translating key '%d' (%s)", - key, obs_key_to_name(key)); - goto err; + if (!sid) { + blog(LOG_ERROR, "hotkeys-cocoa: Unable to get input source ID"); + return false; } - struct obs_hotkeys_platform *plat = NULL; - - if (obs) { - pthread_mutex_lock(&obs->hotkeys.mutex); - plat = obs->hotkeys.platform_context; - hotkeys_retain(plat); - pthread_mutex_unlock(&obs->hotkeys.mutex); + if (!dstr_from_cfstring(&layout_name, sid)) { + blog(LOG_ERROR, "hotkeys-cocoa: Unable to convert input source ID"); + dstr_free(&layout_name); + return false; } - if (!plat) { - blog(LOG_ERROR, - "hotkey-cocoa: Could not get hotkey platform " - "while translating key '%d' (%s)", - key, obs_key_to_name(key)); - goto err; - } + blog(LOG_INFO, "hotkeys-cocoa: Using keyboard layout '%s'", layout_name.array); - UInt32 dead_key_state = 0; - UniCharCount len = 0; + dstr_free(&layout_name); + return true; +} - OSStatus err = UCKeyTranslate(plat->layout, code, kUCKeyActionDown, - 0x104, //caps lock for upper case letters - LMGetKbdType(), kUCKeyTranslateNoDeadKeysBit, &dead_key_state, max_length, &len, - buffer); +// MARK: macOS Hotkey CoreFoundation Callbacks - if (err == noErr && len <= 0 && dead_key_state) { - err = UCKeyTranslate(plat->layout, kVK_Space, kUCKeyActionDown, 0x104, LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, &dead_key_state, max_length, &len, buffer); - } +static void MonitorEventHandlerProc(obs_hotkeys_platform_t *platform, NSEvent *event) +{ + NSEventModifierFlags flags = event.modifierFlags; + platform->is_key_down[OBS_KEY_CAPSLOCK] = !!(flags & NSEventModifierFlagCapsLock); + platform->is_key_down[OBS_KEY_SHIFT] = !!(flags & NSEventModifierFlagShift); + platform->is_key_down[OBS_KEY_ALT] = !!(flags & NSEventModifierFlagOption); + platform->is_key_down[OBS_KEY_META] = !!(flags & NSEventModifierFlagCommand); + platform->is_key_down[OBS_KEY_CONTROL] = !!(flags & NSEventModifierFlagControl); - hotkeys_release(plat); + if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) { + obs_key_t obsKey = obs_key_from_virtual_key(event.keyCode); - if (err != noErr) { - blog(LOG_ERROR, - "hotkey-cocoa: Error while translating key '%d'" - " (0x%x, %s) to string: %d", - key, code, obs_key_to_name(key), err); - goto err; + platform->is_key_down[obsKey] = (event.type == NSEventTypeKeyDown); } +} - if (len == 0) { - blog(LOG_ERROR, - "hotkey-cocoa: Got 0 length string while " - "translating '%d' (0x%x, %s) to string", - key, code, obs_key_to_name(key)); - goto err; - } +static void InputMethodChangedProc(CFNotificationCenterRef center __unused, void *observer, + CFNotificationName name __unused, const void *object __unused, + CFDictionaryRef userInfo __unused) +{ + struct obs_core_hotkeys *hotkeys = observer; + obs_hotkeys_platform_t *platform = hotkeys->platform_context; - CFStringRef string = CFStringCreateWithCharactersNoCopy(NULL, buffer, len, kCFAllocatorNull); - if (!string) { - blog(LOG_ERROR, - "hotkey-cocoa: Could not create CFStringRef " - "while translating '%d' (0x%x, %s) to string", - key, code, obs_key_to_name(key)); - goto err; + pthread_mutex_lock(&hotkeys->mutex); + + if (platform->layout_data) { + CFRelease(platform->layout_data); } - if (!dstr_from_cfstring(str, string)) { - blog(LOG_ERROR, - "hotkey-cocoa: Could not translate CFStringRef " - "to CString while translating '%d' (0x%x, %s)", - key, code, obs_key_to_name(key)); + platform->tis = TISCopyCurrentKeyboardLayoutInputSource(); + platform->layout_data = (CFDataRef) TISGetInputSourceProperty(platform->tis, kTISPropertyUnicodeKeyLayoutData); - goto release; + if (!platform->layout_data) { + blog(LOG_ERROR, "hotkeys-cocoa: Failed to retrieve keyboard layout data"); + hotkeys->platform_context = NULL; + pthread_mutex_unlock(&hotkeys->mutex); + hotkeys_release(platform); + return; } - CFRelease(string); - return; - -release: - CFRelease(string); -err: - dstr_copy(str, obs_key_to_name(key)); + CFRetain(platform->layout_data); + platform->layout = (UCKeyboardLayout *) CFDataGetBytePtr(platform->layout_data); } -#define OBS_COCOA_MODIFIER_SIZE 7 +// MARK: macOS Hotkey API Implementation -static void unichar_to_utf8(const UniChar *c, char *buff) +bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) { - CFStringRef string = CFStringCreateWithCharactersNoCopy(NULL, c, 2, kCFAllocatorNull); - if (!string) { - blog(LOG_ERROR, "hotkey-cocoa: Could not create CFStringRef " - "while populating modifier strings"); - return; - } + CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), hotkeys, InputMethodChangedProc, + kTISNotifySelectedKeyboardInputSourceChanged, NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); - if (!CFStringGetCString(string, buff, OBS_COCOA_MODIFIER_SIZE, kCFStringEncodingUTF8)) - blog(LOG_ERROR, - "hotkey-cocoa: Error while populating " - " modifier string with glyph %d (0x%x)", - c[0], c[0]); + obs_hotkeys_platform_t *platform = bzalloc(sizeof(obs_hotkeys_platform_t)); - CFRelease(string); -} + platform->monitor = + (__bridge CFTypeRef)([NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyUp | NSEventMaskKeyDown + handler:^(NSEvent *_Nonnull event) { + MonitorEventHandlerProc(platform, event); + }]); -static char ctrl_str[OBS_COCOA_MODIFIER_SIZE]; -static char opt_str[OBS_COCOA_MODIFIER_SIZE]; -static char shift_str[OBS_COCOA_MODIFIER_SIZE]; -static char cmd_str[OBS_COCOA_MODIFIER_SIZE]; + platform->local_monitor = (__bridge CFTypeRef)([NSEvent + addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp | NSEventMaskKeyDown + handler:^NSEvent *_Nullable(NSEvent *_Nonnull event) { + MonitorEventHandlerProc(platform, event); -static void init_utf_8_strings(void) -{ - const UniChar ctrl_uni[] = {kControlUnicode, 0}; - const UniChar opt_uni[] = {kOptionUnicode, 0}; - const UniChar shift_uni[] = {kShiftUnicode, 0}; - const UniChar cmd_uni[] = {kCommandUnicode, 0}; - - unichar_to_utf8(ctrl_uni, ctrl_str); - unichar_to_utf8(opt_uni, opt_str); - unichar_to_utf8(shift_uni, shift_str); - unichar_to_utf8(cmd_uni, cmd_str); -} + return event; + }]); -static pthread_once_t strings_token = PTHREAD_ONCE_INIT; + platform->tis = TISCopyCurrentKeyboardLayoutInputSource(); + platform->layout_data = (CFDataRef) TISGetInputSourceProperty(platform->tis, kTISPropertyUnicodeKeyLayoutData); -void obs_key_combination_to_str(obs_key_combination_t key, struct dstr *str) -{ - struct dstr key_str = {0}; - if (key.key != OBS_KEY_NONE) - obs_key_to_str(key.key, &key_str); - - int res = pthread_once(&strings_token, init_utf_8_strings); - if (res) { - blog(LOG_ERROR, - "hotkeys-cocoa: Error while translating " - "modifiers %d (0x%x)", - res, res); - dstr_move(str, &key_str); - return; + if (!platform->layout_data) { + blog(LOG_ERROR, "hotkeys-cocoa: Failed to retrieve keyboard layout data"); + hotkeys_release(platform); + platform = NULL; + return false; } -#define CHECK_MODIFIER(mod, str) ((key.modifiers & mod) ? str : "") - dstr_printf(str, "%s%s%s%s%s", CHECK_MODIFIER(INTERACT_CONTROL_KEY, ctrl_str), - CHECK_MODIFIER(INTERACT_ALT_KEY, opt_str), CHECK_MODIFIER(INTERACT_SHIFT_KEY, shift_str), - CHECK_MODIFIER(INTERACT_COMMAND_KEY, cmd_str), key_str.len ? key_str.array : ""); -#undef CHECK_MODIFIER + CFRetain(platform->layout_data); + platform->layout = (UCKeyboardLayout *) CFDataGetBytePtr(platform->layout_data); - dstr_free(&key_str); -} + obs_hotkeys_platform_t *currentPlatform; -static bool log_layout_name(TISInputSourceRef tis) -{ - struct dstr layout_name = {0}; - CFStringRef sid = (CFStringRef) TISGetInputSourceProperty(tis, kTISPropertyInputSourceID); - if (!sid) { - blog(LOG_ERROR, "hotkeys-cocoa: Failed getting InputSourceID"); - return false; - } + pthread_mutex_lock(&hotkeys->mutex); - if (!dstr_from_cfstring(&layout_name, sid)) { - blog(LOG_ERROR, "hotkeys-cocoa: Could not convert InputSourceID" - " to CString"); - goto fail; + currentPlatform = hotkeys->platform_context; + + if (platform && currentPlatform && platform->layout_data == currentPlatform->layout_data) { + pthread_mutex_unlock(&hotkeys->mutex); + hotkeys_release(platform); + return true; } - blog(LOG_INFO, "hotkeys-cocoa: Using layout '%s'", layout_name.array); + hotkeys->platform_context = platform; - dstr_free(&layout_name); - return true; + log_layout_name(platform->tis); -fail: - dstr_free(&layout_name); - return false; -} + pthread_mutex_unlock(&hotkeys->mutex); -static void handle_monitor_event(obs_hotkeys_platform_t *plat, NSEvent *event) -{ - NSEventModifierFlags flags = event.modifierFlags; - plat->is_key_down[OBS_KEY_CAPSLOCK] = !!(flags & NSEventModifierFlagCapsLock); - plat->is_key_down[OBS_KEY_SHIFT] = !!(flags & NSEventModifierFlagShift); - plat->is_key_down[OBS_KEY_ALT] = !!(flags & NSEventModifierFlagOption); - plat->is_key_down[OBS_KEY_META] = !!(flags & NSEventModifierFlagCommand); - plat->is_key_down[OBS_KEY_CONTROL] = !!(flags & NSEventModifierFlagControl); - if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) { - plat->is_key_down[obs_key_from_virtual_key(event.keyCode)] = (event.type == NSEventTypeKeyDown); + calldata_t parameters = {0}; + signal_handler_signal(hotkeys->signals, "hotkey_layout_change", ¶meters); + + if (currentPlatform) { + hotkeys_release(currentPlatform); } + + bool hasPlatformContext = hotkeys->platform_context != NULL; + + return hasPlatformContext; } -static bool init_hotkeys_platform(obs_hotkeys_platform_t **plat_) +void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) { - if (!plat_) - return false; + CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetDistributedCenter(), hotkeys); - *plat_ = bzalloc(sizeof(obs_hotkeys_platform_t)); - obs_hotkeys_platform_t *plat = *plat_; - if (!plat) { - *plat_ = NULL; - return false; - } + hotkeys_release(hotkeys->platform_context); +} - void (^handler)(NSEvent *) = ^(NSEvent *event) { - handle_monitor_event(plat, event); - }; - plat->monitor = (__bridge CFTypeRef) - [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged - handler:handler]; - - NSEvent *_Nullable (^local_handler)(NSEvent *event) = ^NSEvent *_Nullable(NSEvent *event) - { - handle_monitor_event(plat, event); - - return event; - }; - plat->local_monitor = (__bridge CFTypeRef) - [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged - handler:local_handler]; - - plat->tis = TISCopyCurrentKeyboardLayoutInputSource(); - plat->layout_data = (CFDataRef) TISGetInputSourceProperty(plat->tis, kTISPropertyUnicodeKeyLayoutData); - - if (!plat->layout_data) { - blog(LOG_ERROR, "hotkeys-cocoa: Failed getting LayoutData"); - goto fail; +int obs_key_to_virtual_key(obs_key_t key) +{ + if (virtual_keys[key].is_valid) { + return virtual_keys[key].code; + } else { + return INVALID_KEY; } +} - CFRetain(plat->layout_data); - plat->layout = (UCKeyboardLayout *) CFDataGetBytePtr(plat->layout_data); - - return true; +obs_key_t obs_key_from_virtual_key(int keyCode) +{ + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { + if (virtual_keys[i].is_valid && virtual_keys[i].code == keyCode) { + return (obs_key_t) i; + } + } -fail: - hotkeys_release(plat); - *plat_ = NULL; - return false; + return OBS_KEY_NONE; } -static inline void free_hotkeys_platform(obs_hotkeys_platform_t *plat) +bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *platform, obs_key_t key) { - if (!plat) - return; + if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { + int button = key - 1; - if (plat->monitor) { - [NSEvent removeMonitor:(__bridge id _Nonnull)(plat->monitor)]; - plat->monitor = NULL; + NSUInteger buttons = [NSEvent pressedMouseButtons]; + + if ((buttons & (1 << button)) != 0) { + return true; + } else { + return false; + } } - if (plat->local_monitor) { - [NSEvent removeMonitor:(__bridge id _Nonnull)(plat->local_monitor)]; - plat->local_monitor = NULL; + if (!platform) { + return false; } - if (plat->tis) { - CFRelease(plat->tis); - plat->tis = NULL; + if (key >= OBS_KEY_LAST_VALUE) { + return false; } - if (plat->layout_data) { - CFRelease(plat->layout_data); - plat->layout_data = NULL; + return platform->is_key_down[key]; +} + +static void unichar_to_utf8(const UniChar *character, char *buffer) +{ + CFStringRef string = CFStringCreateWithCharactersNoCopy(NULL, character, 2, kCFAllocatorNull); + + if (!string) { + blog(LOG_ERROR, "hotkey-cocoa: Unable to create CFStringRef with UniChar character"); + return; + } + + if (!CFStringGetCString(string, buffer, OBS_COCOA_MODIFIER_SIZE, kCFStringEncodingUTF8)) { + blog(LOG_ERROR, "hotkey-cocoa: Error while populating buffer with glyph %d (0x%x)", character[0], character[0]); } - bfree(plat); + CFRelease(string); } -static void input_method_changed(CFNotificationCenterRef nc, void *observer, CFStringRef name, const void *object, - CFDictionaryRef user_info) +void obs_key_combination_to_str(obs_key_combination_t key, struct dstr *str) { - UNUSED_PARAMETER(nc); - UNUSED_PARAMETER(name); - UNUSED_PARAMETER(object); - UNUSED_PARAMETER(user_info); + struct dstr keyString = {0}; - struct obs_core_hotkeys *hotkeys = observer; - obs_hotkeys_platform_t *new_plat; + if (key.key != OBS_KEY_NONE) { + obs_key_to_str(key.key, &keyString); + } - if (init_hotkeys_platform(&new_plat)) { - obs_hotkeys_platform_t *plat; + dispatch_once(&onceToken, ^{ + const UniChar controlCharacter[] = {kControlUnicode, 0}; + const UniChar optionCharacter[] = {kOptionUnicode, 0}; + const UniChar shiftCharacter[] = {kShiftUnicode, 0}; + const UniChar commandCharacter[] = {kCommandUnicode, 0}; - pthread_mutex_lock(&hotkeys->mutex); - plat = hotkeys->platform_context; + unichar_to_utf8(controlCharacter, string_control); + unichar_to_utf8(optionCharacter, string_option); + unichar_to_utf8(shiftCharacter, string_shift); + unichar_to_utf8(commandCharacter, string_command); + }); - if (new_plat && plat && new_plat->layout_data == plat->layout_data) { - pthread_mutex_unlock(&hotkeys->mutex); - hotkeys_release(new_plat); - return; - } + const char *modifier_control = (key.modifiers & INTERACT_CONTROL_KEY) ? string_control : ""; + const char *modifier_option = (key.modifiers & INTERACT_ALT_KEY) ? string_option : ""; + const char *modifier_shift = (key.modifiers & INTERACT_SHIFT_KEY) ? string_shift : ""; + const char *modifier_command = (key.modifiers & INTERACT_COMMAND_KEY) ? string_command : ""; + const char *modifier_key = keyString.len ? keyString.array : ""; - hotkeys->platform_context = new_plat; - if (new_plat) - log_layout_name(new_plat->tis); - pthread_mutex_unlock(&hotkeys->mutex); + dstr_printf(str, "%s%s%s%s%s", modifier_control, modifier_option, modifier_shift, modifier_command, modifier_key); - calldata_t params = {0}; - signal_handler_signal(hotkeys->signals, "hotkey_layout_change", ¶ms); - if (plat) - hotkeys_release(plat); - } + dstr_free(&keyString); } -bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) +void obs_key_to_str(obs_key_t key, struct dstr *str) { - CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), hotkeys, input_method_changed, - kTISNotifySelectedKeyboardInputSourceChanged, NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); + const UniCharCount maxLength = 16; + UniChar buffer[16]; - input_method_changed(NULL, hotkeys, NULL, NULL, NULL); - return hotkeys->platform_context != NULL; -} + if (obs_key_to_localized_string(key, str)) { + return; + } -void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) -{ - CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetDistributedCenter(), hotkeys); + int code = obs_key_to_virtual_key(key); + if (key_code_to_string(code, str)) { + return; + } - hotkeys_release(hotkeys->platform_context); -} + if (code == INVALID_KEY) { + const char *keyName = obs_key_to_name(key); + blog(LOG_ERROR, "hotkey-cocoa: Got invalid key while translating '%d' (%s)", key, keyName); + dstr_copy(str, keyName); + return; + } -typedef unsigned long NSUInteger; + struct obs_hotkeys_platform *platform = NULL; -static bool mouse_button_pressed(obs_key_t key, bool *pressed) -{ - int button = 0; - switch (key) { -#define MAP_BUTTON(n) \ - case OBS_KEY_MOUSE##n: \ - button = n - 1; \ - break - MAP_BUTTON(1); - MAP_BUTTON(2); - MAP_BUTTON(3); - MAP_BUTTON(4); - MAP_BUTTON(5); - MAP_BUTTON(6); - MAP_BUTTON(7); - MAP_BUTTON(8); - MAP_BUTTON(9); - MAP_BUTTON(10); - MAP_BUTTON(11); - MAP_BUTTON(12); - MAP_BUTTON(13); - MAP_BUTTON(14); - MAP_BUTTON(15); - MAP_BUTTON(16); - MAP_BUTTON(17); - MAP_BUTTON(18); - MAP_BUTTON(19); - MAP_BUTTON(20); - MAP_BUTTON(21); - MAP_BUTTON(22); - MAP_BUTTON(23); - MAP_BUTTON(24); - MAP_BUTTON(25); - MAP_BUTTON(26); - MAP_BUTTON(27); - MAP_BUTTON(28); - MAP_BUTTON(29); - break; -#undef MAP_BUTTON - - default: - return false; + if (obs) { + pthread_mutex_lock(&obs->hotkeys.mutex); + platform = obs->hotkeys.platform_context; + hotkeys_retain(platform); + pthread_mutex_unlock(&obs->hotkeys.mutex); } - NSUInteger buttons = [NSEvent pressedMouseButtons]; - *pressed = (buttons & (1 << button)) != 0; - return true; -} + if (!platform) { + const char *keyName = obs_key_to_name(key); + blog(LOG_ERROR, "hotkey-cocoa: Could not get hotkey platform while translating '%d' (%s)", key, keyName); + dstr_copy(str, keyName); + return; + } -bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *plat, obs_key_t key) -{ - bool mouse_pressed = false; - if (mouse_button_pressed(key, &mouse_pressed)) - return mouse_pressed; + UInt32 deadKeyState = 0; + UniCharCount length = 0; - if (!plat) - return false; + OSStatus err = UCKeyTranslate(platform->layout, code, kUCKeyActionDown, ((alphaLock >> 8) & 0xFF), LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, &deadKeyState, maxLength, &length, buffer); - if (key >= OBS_KEY_LAST_VALUE) - return false; + if (err == noErr && length <= 0 && deadKeyState) { + err = UCKeyTranslate(platform->layout, kVK_Space, kUCKeyActionDown, ((alphaLock >> 8) & 0xFF), LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, &deadKeyState, maxLength, &length, buffer); + } - return plat->is_key_down[key]; -} + hotkeys_release(platform); -void *obs_graphics_thread_autorelease(void *param) -{ - @autoreleasepool { - return obs_graphics_thread(param); + if (err != noErr) { + const char *keyName = obs_key_to_name(key); + blog(LOG_ERROR, "hotkey-cocoa: Error while translating '%d' (0x%x, %s) to string: %d", key, code, keyName, err); + dstr_copy(str, keyName); + return; } -} -bool obs_graphics_thread_loop_autorelease(struct obs_graphics_context *context) -{ - @autoreleasepool { - return obs_graphics_thread_loop(context); + if (length == 0) { + const char *keyName = obs_key_to_name(key); + blog(LOG_ERROR, "hotkey-cocoa: Got zero length string while translating '%d' (0x%x, %s) to string", key, code, + keyName); + dstr_copy(str, keyName); + return; } + + CFStringRef string = CFStringCreateWithCharactersNoCopy(NULL, buffer, length, kCFAllocatorNull); + + if (!string) { + const char *keyName = obs_key_to_name(key); + blog(LOG_ERROR, "hotkey-cocoa: Could not create CFStringRef while translating '%d' (0x%x, %s) to string", key, + code, keyName); + dstr_copy(str, keyName); + return; + } + + if (!dstr_from_cfstring(str, string)) { + const char *keyName = obs_key_to_name(key); + blog(LOG_ERROR, "hotkey-cocoa: Could not translate CFStringRef to CString while translating '%d' (0x%x, %s)", + key, code, keyName); + CFRelease(string); + dstr_copy(str, keyName); + return; + } + + CFRelease(string); + return; }