diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 5eb3042f7..ea5250e1a 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -187,6 +187,7 @@ elseif(APPLE) elseif(UNIX) set(libobs_PLATFORM_SOURCES obs-nix.c + obs-nix-x11.c util/threading-posix.c util/pipe-posix.c util/platform-nix.c) diff --git a/libobs/obs-nix-x11.c b/libobs/obs-nix-x11.c new file mode 100644 index 000000000..29aa3c7f8 --- /dev/null +++ b/libobs/obs-nix-x11.c @@ -0,0 +1,1270 @@ +/****************************************************************************** + Copyright (C) 2013 by Hugh Bailey + Copyright (C) 2014 by Zachary Lund + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "obs-internal.h" +#include "obs-nix-x11.h" + +#include +#if USE_XINPUT +#include +#endif +#include +#include +#include +#include +#include + +void obs_nix_x11_log_info(void) +{ + Display *dpy = XOpenDisplay(NULL); + if (!dpy) { + blog(LOG_INFO, "Unable to open X display"); + return; + } + + int protocol_version = ProtocolVersion(dpy); + int protocol_revision = ProtocolRevision(dpy); + int vendor_release = VendorRelease(dpy); + const char *vendor_name = ServerVendor(dpy); + + if (strstr(vendor_name, "X.Org")) { + blog(LOG_INFO, + "Window System: X%d.%d, Vendor: %s, Version: %d" + ".%d.%d", + protocol_version, protocol_revision, vendor_name, + vendor_release / 10000000, (vendor_release / 100000) % 100, + (vendor_release / 1000) % 100); + } else { + blog(LOG_INFO, + "Window System: X%d.%d - vendor string: %s - " + "vendor release: %d", + protocol_version, protocol_revision, vendor_name, + vendor_release); + } +} + +/* So here's how linux works with key mapping: + * + * First, there's a global key symbol enum (xcb_keysym_t) which has unique + * values for all possible symbols keys can have (e.g., '1' and '!' are + * different values). + * + * Then there's a key code (xcb_keycode_t), which is basically an index to the + * actual key itself on the keyboard (e.g., '1' and '!' will share the same + * value). + * + * xcb_keysym_t values should be given to libobs, and libobs will translate it + * to an obs_key_t, and although xcb_keysym_t can differ ('!' vs '1'), it will + * get the obs_key_t value that represents the actual key pressed; in other + * words it will be based on the key code rather than the key symbol. The same + * applies to checking key press states. + */ + +struct keycode_list { + DARRAY(xcb_keycode_t) list; +}; + +struct obs_hotkeys_platform { + Display *display; + xcb_keysym_t base_keysyms[OBS_KEY_LAST_VALUE]; + struct keycode_list keycodes[OBS_KEY_LAST_VALUE]; + xcb_keycode_t min_keycode; + xcb_keycode_t super_l_code; + xcb_keycode_t super_r_code; + + /* stores a copy of the keysym map for keycodes */ + xcb_keysym_t *keysyms; + int num_keysyms; + int syms_per_code; + +#if USE_XINPUT + bool pressed[XINPUT_MOUSE_LEN]; + bool update[XINPUT_MOUSE_LEN]; + bool button_pressed[XINPUT_MOUSE_LEN]; +#endif +}; + +#define MOUSE_1 (1 << 16) +#define MOUSE_2 (2 << 16) +#define MOUSE_3 (3 << 16) +#define MOUSE_4 (4 << 16) +#define MOUSE_5 (5 << 16) + +static int get_keysym(obs_key_t key) +{ + switch (key) { + case OBS_KEY_RETURN: + return XK_Return; + case OBS_KEY_ESCAPE: + return XK_Escape; + case OBS_KEY_TAB: + return XK_Tab; + case OBS_KEY_BACKSPACE: + return XK_BackSpace; + case OBS_KEY_INSERT: + return XK_Insert; + case OBS_KEY_DELETE: + return XK_Delete; + case OBS_KEY_PAUSE: + return XK_Pause; + case OBS_KEY_PRINT: + return XK_Print; + case OBS_KEY_HOME: + return XK_Home; + case OBS_KEY_END: + return XK_End; + case OBS_KEY_LEFT: + return XK_Left; + case OBS_KEY_UP: + return XK_Up; + case OBS_KEY_RIGHT: + return XK_Right; + case OBS_KEY_DOWN: + return XK_Down; + case OBS_KEY_PAGEUP: + return XK_Prior; + case OBS_KEY_PAGEDOWN: + return XK_Next; + + case OBS_KEY_SHIFT: + return XK_Shift_L; + case OBS_KEY_CONTROL: + return XK_Control_L; + case OBS_KEY_ALT: + return XK_Alt_L; + case OBS_KEY_CAPSLOCK: + return XK_Caps_Lock; + case OBS_KEY_NUMLOCK: + return XK_Num_Lock; + case OBS_KEY_SCROLLLOCK: + return XK_Scroll_Lock; + + case OBS_KEY_F1: + return XK_F1; + case OBS_KEY_F2: + return XK_F2; + case OBS_KEY_F3: + return XK_F3; + case OBS_KEY_F4: + return XK_F4; + case OBS_KEY_F5: + return XK_F5; + case OBS_KEY_F6: + return XK_F6; + case OBS_KEY_F7: + return XK_F7; + case OBS_KEY_F8: + return XK_F8; + case OBS_KEY_F9: + return XK_F9; + case OBS_KEY_F10: + return XK_F10; + case OBS_KEY_F11: + return XK_F11; + case OBS_KEY_F12: + return XK_F12; + case OBS_KEY_F13: + return XK_F13; + case OBS_KEY_F14: + return XK_F14; + case OBS_KEY_F15: + return XK_F15; + case OBS_KEY_F16: + return XK_F16; + case OBS_KEY_F17: + return XK_F17; + case OBS_KEY_F18: + return XK_F18; + case OBS_KEY_F19: + return XK_F19; + case OBS_KEY_F20: + return XK_F20; + case OBS_KEY_F21: + return XK_F21; + case OBS_KEY_F22: + return XK_F22; + case OBS_KEY_F23: + return XK_F23; + case OBS_KEY_F24: + return XK_F24; + case OBS_KEY_F25: + return XK_F25; + case OBS_KEY_F26: + return XK_F26; + case OBS_KEY_F27: + return XK_F27; + case OBS_KEY_F28: + return XK_F28; + case OBS_KEY_F29: + return XK_F29; + case OBS_KEY_F30: + return XK_F30; + case OBS_KEY_F31: + return XK_F31; + case OBS_KEY_F32: + return XK_F32; + case OBS_KEY_F33: + return XK_F33; + case OBS_KEY_F34: + return XK_F34; + case OBS_KEY_F35: + return XK_F35; + + case OBS_KEY_MENU: + return XK_Menu; + case OBS_KEY_HYPER_L: + return XK_Hyper_L; + case OBS_KEY_HYPER_R: + return XK_Hyper_R; + case OBS_KEY_HELP: + return XK_Help; + case OBS_KEY_CANCEL: + return XK_Cancel; + case OBS_KEY_FIND: + return XK_Find; + case OBS_KEY_REDO: + return XK_Redo; + case OBS_KEY_UNDO: + return XK_Undo; + case OBS_KEY_SPACE: + return XK_space; + + case OBS_KEY_COPY: + return XF86XK_Copy; + case OBS_KEY_CUT: + return XF86XK_Cut; + case OBS_KEY_OPEN: + return XF86XK_Open; + case OBS_KEY_PASTE: + return XF86XK_Paste; + case OBS_KEY_FRONT: + return SunXK_Front; + case OBS_KEY_PROPS: + return SunXK_Props; + + case OBS_KEY_EXCLAM: + return XK_exclam; + case OBS_KEY_QUOTEDBL: + return XK_quotedbl; + case OBS_KEY_NUMBERSIGN: + return XK_numbersign; + case OBS_KEY_DOLLAR: + return XK_dollar; + case OBS_KEY_PERCENT: + return XK_percent; + case OBS_KEY_AMPERSAND: + return XK_ampersand; + case OBS_KEY_APOSTROPHE: + return XK_apostrophe; + case OBS_KEY_PARENLEFT: + return XK_parenleft; + case OBS_KEY_PARENRIGHT: + return XK_parenright; + case OBS_KEY_ASTERISK: + return XK_asterisk; + case OBS_KEY_PLUS: + return XK_plus; + case OBS_KEY_COMMA: + return XK_comma; + case OBS_KEY_MINUS: + return XK_minus; + case OBS_KEY_PERIOD: + return XK_period; + case OBS_KEY_SLASH: + return XK_slash; + case OBS_KEY_0: + return XK_0; + case OBS_KEY_1: + return XK_1; + case OBS_KEY_2: + return XK_2; + case OBS_KEY_3: + return XK_3; + case OBS_KEY_4: + return XK_4; + case OBS_KEY_5: + return XK_5; + case OBS_KEY_6: + return XK_6; + case OBS_KEY_7: + return XK_7; + case OBS_KEY_8: + return XK_8; + case OBS_KEY_9: + return XK_9; + case OBS_KEY_NUMEQUAL: + return XK_KP_Equal; + case OBS_KEY_NUMASTERISK: + return XK_KP_Multiply; + case OBS_KEY_NUMPLUS: + return XK_KP_Add; + case OBS_KEY_NUMCOMMA: + return XK_KP_Separator; + case OBS_KEY_NUMMINUS: + return XK_KP_Subtract; + case OBS_KEY_NUMPERIOD: + return XK_KP_Decimal; + case OBS_KEY_NUMSLASH: + return XK_KP_Divide; + case OBS_KEY_NUM0: + return XK_KP_0; + case OBS_KEY_NUM1: + return XK_KP_1; + case OBS_KEY_NUM2: + return XK_KP_2; + case OBS_KEY_NUM3: + return XK_KP_3; + case OBS_KEY_NUM4: + return XK_KP_4; + case OBS_KEY_NUM5: + return XK_KP_5; + case OBS_KEY_NUM6: + return XK_KP_6; + case OBS_KEY_NUM7: + return XK_KP_7; + case OBS_KEY_NUM8: + return XK_KP_8; + case OBS_KEY_NUM9: + return XK_KP_9; + case OBS_KEY_COLON: + return XK_colon; + case OBS_KEY_SEMICOLON: + return XK_semicolon; + case OBS_KEY_LESS: + return XK_less; + case OBS_KEY_EQUAL: + return XK_equal; + case OBS_KEY_GREATER: + return XK_greater; + case OBS_KEY_QUESTION: + return XK_question; + case OBS_KEY_AT: + return XK_at; + case OBS_KEY_A: + return XK_A; + case OBS_KEY_B: + return XK_B; + case OBS_KEY_C: + return XK_C; + case OBS_KEY_D: + return XK_D; + case OBS_KEY_E: + return XK_E; + case OBS_KEY_F: + return XK_F; + case OBS_KEY_G: + return XK_G; + case OBS_KEY_H: + return XK_H; + case OBS_KEY_I: + return XK_I; + case OBS_KEY_J: + return XK_J; + case OBS_KEY_K: + return XK_K; + case OBS_KEY_L: + return XK_L; + case OBS_KEY_M: + return XK_M; + case OBS_KEY_N: + return XK_N; + case OBS_KEY_O: + return XK_O; + case OBS_KEY_P: + return XK_P; + case OBS_KEY_Q: + return XK_Q; + case OBS_KEY_R: + return XK_R; + case OBS_KEY_S: + return XK_S; + case OBS_KEY_T: + return XK_T; + case OBS_KEY_U: + return XK_U; + case OBS_KEY_V: + return XK_V; + case OBS_KEY_W: + return XK_W; + case OBS_KEY_X: + return XK_X; + case OBS_KEY_Y: + return XK_Y; + case OBS_KEY_Z: + return XK_Z; + case OBS_KEY_BRACKETLEFT: + return XK_bracketleft; + case OBS_KEY_BACKSLASH: + return XK_backslash; + case OBS_KEY_BRACKETRIGHT: + return XK_bracketright; + case OBS_KEY_ASCIICIRCUM: + return XK_asciicircum; + case OBS_KEY_UNDERSCORE: + return XK_underscore; + case OBS_KEY_QUOTELEFT: + return XK_quoteleft; + case OBS_KEY_BRACELEFT: + return XK_braceleft; + case OBS_KEY_BAR: + return XK_bar; + case OBS_KEY_BRACERIGHT: + return XK_braceright; + case OBS_KEY_ASCIITILDE: + return XK_grave; + case OBS_KEY_NOBREAKSPACE: + return XK_nobreakspace; + case OBS_KEY_EXCLAMDOWN: + return XK_exclamdown; + case OBS_KEY_CENT: + return XK_cent; + case OBS_KEY_STERLING: + return XK_sterling; + case OBS_KEY_CURRENCY: + return XK_currency; + case OBS_KEY_YEN: + return XK_yen; + case OBS_KEY_BROKENBAR: + return XK_brokenbar; + case OBS_KEY_SECTION: + return XK_section; + case OBS_KEY_DIAERESIS: + return XK_diaeresis; + case OBS_KEY_COPYRIGHT: + return XK_copyright; + case OBS_KEY_ORDFEMININE: + return XK_ordfeminine; + case OBS_KEY_GUILLEMOTLEFT: + return XK_guillemotleft; + case OBS_KEY_NOTSIGN: + return XK_notsign; + case OBS_KEY_HYPHEN: + return XK_hyphen; + case OBS_KEY_REGISTERED: + return XK_registered; + case OBS_KEY_MACRON: + return XK_macron; + case OBS_KEY_DEGREE: + return XK_degree; + case OBS_KEY_PLUSMINUS: + return XK_plusminus; + case OBS_KEY_TWOSUPERIOR: + return XK_twosuperior; + case OBS_KEY_THREESUPERIOR: + return XK_threesuperior; + case OBS_KEY_ACUTE: + return XK_acute; + case OBS_KEY_MU: + return XK_mu; + case OBS_KEY_PARAGRAPH: + return XK_paragraph; + case OBS_KEY_PERIODCENTERED: + return XK_periodcentered; + case OBS_KEY_CEDILLA: + return XK_cedilla; + case OBS_KEY_ONESUPERIOR: + return XK_onesuperior; + case OBS_KEY_MASCULINE: + return XK_masculine; + case OBS_KEY_GUILLEMOTRIGHT: + return XK_guillemotright; + case OBS_KEY_ONEQUARTER: + return XK_onequarter; + case OBS_KEY_ONEHALF: + return XK_onehalf; + case OBS_KEY_THREEQUARTERS: + return XK_threequarters; + case OBS_KEY_QUESTIONDOWN: + return XK_questiondown; + case OBS_KEY_AGRAVE: + return XK_Agrave; + case OBS_KEY_AACUTE: + return XK_Aacute; + case OBS_KEY_ACIRCUMFLEX: + return XK_Acircumflex; + case OBS_KEY_ATILDE: + return XK_Atilde; + case OBS_KEY_ADIAERESIS: + return XK_Adiaeresis; + case OBS_KEY_ARING: + return XK_Aring; + case OBS_KEY_AE: + return XK_AE; + case OBS_KEY_CCEDILLA: + return XK_cedilla; + case OBS_KEY_EGRAVE: + return XK_Egrave; + case OBS_KEY_EACUTE: + return XK_Eacute; + case OBS_KEY_ECIRCUMFLEX: + return XK_Ecircumflex; + case OBS_KEY_EDIAERESIS: + return XK_Ediaeresis; + case OBS_KEY_IGRAVE: + return XK_Igrave; + case OBS_KEY_IACUTE: + return XK_Iacute; + case OBS_KEY_ICIRCUMFLEX: + return XK_Icircumflex; + case OBS_KEY_IDIAERESIS: + return XK_Idiaeresis; + case OBS_KEY_ETH: + return XK_ETH; + case OBS_KEY_NTILDE: + return XK_Ntilde; + case OBS_KEY_OGRAVE: + return XK_Ograve; + case OBS_KEY_OACUTE: + return XK_Oacute; + case OBS_KEY_OCIRCUMFLEX: + return XK_Ocircumflex; + case OBS_KEY_ODIAERESIS: + return XK_Odiaeresis; + case OBS_KEY_MULTIPLY: + return XK_multiply; + case OBS_KEY_OOBLIQUE: + return XK_Ooblique; + case OBS_KEY_UGRAVE: + return XK_Ugrave; + case OBS_KEY_UACUTE: + return XK_Uacute; + case OBS_KEY_UCIRCUMFLEX: + return XK_Ucircumflex; + case OBS_KEY_UDIAERESIS: + return XK_Udiaeresis; + case OBS_KEY_YACUTE: + return XK_Yacute; + case OBS_KEY_THORN: + return XK_Thorn; + case OBS_KEY_SSHARP: + return XK_ssharp; + case OBS_KEY_DIVISION: + return XK_division; + case OBS_KEY_YDIAERESIS: + return XK_Ydiaeresis; + case OBS_KEY_MULTI_KEY: + return XK_Multi_key; + case OBS_KEY_CODEINPUT: + return XK_Codeinput; + case OBS_KEY_SINGLECANDIDATE: + return XK_SingleCandidate; + case OBS_KEY_MULTIPLECANDIDATE: + return XK_MultipleCandidate; + case OBS_KEY_PREVIOUSCANDIDATE: + return XK_PreviousCandidate; + case OBS_KEY_MODE_SWITCH: + return XK_Mode_switch; + case OBS_KEY_KANJI: + return XK_Kanji; + case OBS_KEY_MUHENKAN: + return XK_Muhenkan; + case OBS_KEY_HENKAN: + return XK_Henkan; + case OBS_KEY_ROMAJI: + return XK_Romaji; + case OBS_KEY_HIRAGANA: + return XK_Hiragana; + case OBS_KEY_KATAKANA: + return XK_Katakana; + case OBS_KEY_HIRAGANA_KATAKANA: + return XK_Hiragana_Katakana; + case OBS_KEY_ZENKAKU: + return XK_Zenkaku; + case OBS_KEY_HANKAKU: + return XK_Hankaku; + case OBS_KEY_ZENKAKU_HANKAKU: + return XK_Zenkaku_Hankaku; + case OBS_KEY_TOUROKU: + return XK_Touroku; + case OBS_KEY_MASSYO: + return XK_Massyo; + case OBS_KEY_KANA_LOCK: + return XK_Kana_Lock; + case OBS_KEY_KANA_SHIFT: + return XK_Kana_Shift; + case OBS_KEY_EISU_SHIFT: + return XK_Eisu_Shift; + case OBS_KEY_EISU_TOGGLE: + return XK_Eisu_toggle; + case OBS_KEY_HANGUL: + return XK_Hangul; + case OBS_KEY_HANGUL_START: + return XK_Hangul_Start; + case OBS_KEY_HANGUL_END: + return XK_Hangul_End; + case OBS_KEY_HANGUL_HANJA: + return XK_Hangul_Hanja; + case OBS_KEY_HANGUL_JAMO: + return XK_Hangul_Jamo; + case OBS_KEY_HANGUL_ROMAJA: + return XK_Hangul_Romaja; + case OBS_KEY_HANGUL_BANJA: + return XK_Hangul_Banja; + case OBS_KEY_HANGUL_PREHANJA: + return XK_Hangul_PreHanja; + case OBS_KEY_HANGUL_POSTHANJA: + return XK_Hangul_PostHanja; + case OBS_KEY_HANGUL_SPECIAL: + return XK_Hangul_Special; + case OBS_KEY_DEAD_GRAVE: + return XK_dead_grave; + case OBS_KEY_DEAD_ACUTE: + return XK_dead_acute; + case OBS_KEY_DEAD_CIRCUMFLEX: + return XK_dead_circumflex; + case OBS_KEY_DEAD_TILDE: + return XK_dead_tilde; + case OBS_KEY_DEAD_MACRON: + return XK_dead_macron; + case OBS_KEY_DEAD_BREVE: + return XK_dead_breve; + case OBS_KEY_DEAD_ABOVEDOT: + return XK_dead_abovedot; + case OBS_KEY_DEAD_DIAERESIS: + return XK_dead_diaeresis; + case OBS_KEY_DEAD_ABOVERING: + return XK_dead_abovering; + case OBS_KEY_DEAD_DOUBLEACUTE: + return XK_dead_doubleacute; + case OBS_KEY_DEAD_CARON: + return XK_dead_caron; + case OBS_KEY_DEAD_CEDILLA: + return XK_dead_cedilla; + case OBS_KEY_DEAD_OGONEK: + return XK_dead_ogonek; + case OBS_KEY_DEAD_IOTA: + return XK_dead_iota; + case OBS_KEY_DEAD_VOICED_SOUND: + return XK_dead_voiced_sound; + case OBS_KEY_DEAD_SEMIVOICED_SOUND: + return XK_dead_semivoiced_sound; + case OBS_KEY_DEAD_BELOWDOT: + return XK_dead_belowdot; + case OBS_KEY_DEAD_HOOK: + return XK_dead_hook; + case OBS_KEY_DEAD_HORN: + return XK_dead_horn; + + case OBS_KEY_MOUSE1: + return MOUSE_1; + case OBS_KEY_MOUSE2: + return MOUSE_2; + case OBS_KEY_MOUSE3: + return MOUSE_3; + case OBS_KEY_MOUSE4: + return MOUSE_4; + case OBS_KEY_MOUSE5: + return MOUSE_5; + + /* TODO: Implement keys for non-US keyboards */ + default:; + } + return 0; +} + +static inline void fill_base_keysyms(struct obs_core_hotkeys *hotkeys) +{ + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) + hotkeys->platform_context->base_keysyms[i] = get_keysym(i); +} + +static obs_key_t key_from_base_keysym(obs_hotkeys_platform_t *context, + xcb_keysym_t code) +{ + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { + if (context->base_keysyms[i] == (xcb_keysym_t)code) { + return (obs_key_t)i; + } + } + + return OBS_KEY_NONE; +} + +static inline void add_key(obs_hotkeys_platform_t *context, obs_key_t key, + int code) +{ + xcb_keycode_t kc = (xcb_keycode_t)code; + da_push_back(context->keycodes[key].list, &kc); + + if (context->keycodes[key].list.num > 1) { + blog(LOG_DEBUG, + "found alternate keycode %d for %s " + "which already has keycode %d", + code, obs_key_to_name(key), + (int)context->keycodes[key].list.array[0]); + } +} + +static inline bool fill_keycodes(struct obs_core_hotkeys *hotkeys) +{ + obs_hotkeys_platform_t *context = hotkeys->platform_context; + xcb_connection_t *connection = XGetXCBConnection(context->display); + const struct xcb_setup_t *setup = xcb_get_setup(connection); + xcb_get_keyboard_mapping_cookie_t cookie; + xcb_get_keyboard_mapping_reply_t *reply; + xcb_generic_error_t *error = NULL; + int code; + + int mincode = setup->min_keycode; + int maxcode = setup->max_keycode; + + context->min_keycode = setup->min_keycode; + + cookie = xcb_get_keyboard_mapping(connection, mincode, + maxcode - mincode + 1); + + reply = xcb_get_keyboard_mapping_reply(connection, cookie, &error); + + if (error || !reply) { + blog(LOG_WARNING, "xcb_get_keyboard_mapping_reply failed"); + goto error1; + } + + const xcb_keysym_t *keysyms = xcb_get_keyboard_mapping_keysyms(reply); + int syms_per_code = (int)reply->keysyms_per_keycode; + + context->num_keysyms = (maxcode - mincode + 1) * syms_per_code; + context->syms_per_code = syms_per_code; + context->keysyms = + bmemdup(keysyms, sizeof(xcb_keysym_t) * context->num_keysyms); + + for (code = mincode; code <= maxcode; code++) { + const xcb_keysym_t *sym; + obs_key_t key; + + sym = &keysyms[(code - mincode) * syms_per_code]; + + for (int i = 0; i < syms_per_code; i++) { + if (!sym[i]) + break; + + if (sym[i] == XK_Super_L) { + context->super_l_code = code; + break; + } else if (sym[i] == XK_Super_R) { + context->super_r_code = code; + break; + } else { + key = key_from_base_keysym(context, sym[i]); + + if (key != OBS_KEY_NONE) { + add_key(context, key, code); + break; + } + } + } + } + +error1: + free(reply); + free(error); + + return error != NULL || reply == NULL; +} + +static xcb_screen_t *default_screen(obs_hotkeys_platform_t *context, + xcb_connection_t *connection) +{ + int def_screen_idx = XDefaultScreen(context->display); + xcb_screen_iterator_t iter; + + iter = xcb_setup_roots_iterator(xcb_get_setup(connection)); + while (iter.rem) { + if (def_screen_idx-- == 0) + return iter.data; + + xcb_screen_next(&iter); + } + + return NULL; +} + +static inline xcb_window_t root_window(obs_hotkeys_platform_t *context, + xcb_connection_t *connection) +{ + xcb_screen_t *screen = default_screen(context, connection); + if (screen) + return screen->root; + return 0; +} + +#if USE_XINPUT +static inline void registerMouseEvents(struct obs_core_hotkeys *hotkeys) +{ + obs_hotkeys_platform_t *context = hotkeys->platform_context; + xcb_connection_t *connection = XGetXCBConnection(context->display); + xcb_window_t window = root_window(context, connection); + + struct { + xcb_input_event_mask_t head; + xcb_input_xi_event_mask_t mask; + } mask; + mask.head.deviceid = XCB_INPUT_DEVICE_ALL_MASTER; + mask.head.mask_len = sizeof(mask.mask) / sizeof(uint32_t); + mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | + XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE; + + xcb_input_xi_select_events(connection, window, 1, &mask.head); + xcb_flush(connection); +} +#endif + +static bool obs_nix_x11_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) +{ + Display *display = XOpenDisplay(NULL); + if (!display) + return false; + + hotkeys->platform_context = bzalloc(sizeof(obs_hotkeys_platform_t)); + hotkeys->platform_context->display = display; + +#if USE_XINPUT + registerMouseEvents(hotkeys); +#endif + fill_base_keysyms(hotkeys); + fill_keycodes(hotkeys); + return true; +} + +static void obs_nix_x11_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) +{ + obs_hotkeys_platform_t *context = hotkeys->platform_context; + + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) + da_free(context->keycodes[i].list); + + bfree(context->keysyms); + bfree(context); + + hotkeys->platform_context = NULL; +} + +static bool mouse_button_pressed(xcb_connection_t *connection, + obs_hotkeys_platform_t *context, obs_key_t key) +{ + bool ret = false; + +#if USE_XINPUT + memset(context->pressed, 0, XINPUT_MOUSE_LEN); + memset(context->update, 0, XINPUT_MOUSE_LEN); + + xcb_generic_event_t *ev; + while ((ev = xcb_poll_for_event(connection))) { + if ((ev->response_type & ~80) == XCB_GE_GENERIC) { + switch (((xcb_ge_event_t *)ev)->event_type) { + case XCB_INPUT_RAW_BUTTON_PRESS: { + xcb_input_raw_button_press_event_t *mot; + mot = (xcb_input_raw_button_press_event_t *)ev; + if (mot->detail < XINPUT_MOUSE_LEN) { + context->pressed[mot->detail - 1] = + true; + context->update[mot->detail - 1] = true; + } else { + blog(LOG_WARNING, "Unsupported button"); + } + break; + } + case XCB_INPUT_RAW_BUTTON_RELEASE: { + xcb_input_raw_button_release_event_t *mot; + mot = (xcb_input_raw_button_release_event_t *)ev; + if (mot->detail < XINPUT_MOUSE_LEN) + context->update[mot->detail - 1] = true; + else + blog(LOG_WARNING, "Unsupported button"); + break; + } + default: + break; + } + } + free(ev); + } + + // Mouse 2 for OBS is Right Click and Mouse 3 is Wheel Click. + // Mouse Wheel axis clicks (xinput mot->detail 4 5 6 7) are ignored. + switch (key) { + case OBS_KEY_MOUSE1: + ret = context->pressed[0] || context->button_pressed[0]; + break; + case OBS_KEY_MOUSE2: + ret = context->pressed[2] || context->button_pressed[2]; + break; + case OBS_KEY_MOUSE3: + ret = context->pressed[1] || context->button_pressed[1]; + break; + case OBS_KEY_MOUSE4: + ret = context->pressed[7] || context->button_pressed[7]; + break; + case OBS_KEY_MOUSE5: + ret = context->pressed[8] || context->button_pressed[8]; + break; + case OBS_KEY_MOUSE6: + ret = context->pressed[9] || context->button_pressed[9]; + break; + case OBS_KEY_MOUSE7: + ret = context->pressed[10] || context->button_pressed[10]; + break; + case OBS_KEY_MOUSE8: + ret = context->pressed[11] || context->button_pressed[11]; + break; + case OBS_KEY_MOUSE9: + ret = context->pressed[12] || context->button_pressed[12]; + break; + case OBS_KEY_MOUSE10: + ret = context->pressed[13] || context->button_pressed[13]; + break; + case OBS_KEY_MOUSE11: + ret = context->pressed[14] || context->button_pressed[14]; + break; + case OBS_KEY_MOUSE12: + ret = context->pressed[15] || context->button_pressed[15]; + break; + case OBS_KEY_MOUSE13: + ret = context->pressed[16] || context->button_pressed[16]; + break; + case OBS_KEY_MOUSE14: + ret = context->pressed[17] || context->button_pressed[17]; + break; + case OBS_KEY_MOUSE15: + ret = context->pressed[18] || context->button_pressed[18]; + break; + case OBS_KEY_MOUSE16: + ret = context->pressed[19] || context->button_pressed[19]; + break; + case OBS_KEY_MOUSE17: + ret = context->pressed[20] || context->button_pressed[20]; + break; + case OBS_KEY_MOUSE18: + ret = context->pressed[21] || context->button_pressed[21]; + break; + case OBS_KEY_MOUSE19: + ret = context->pressed[22] || context->button_pressed[22]; + break; + case OBS_KEY_MOUSE20: + ret = context->pressed[23] || context->button_pressed[23]; + break; + case OBS_KEY_MOUSE21: + ret = context->pressed[24] || context->button_pressed[24]; + break; + case OBS_KEY_MOUSE22: + ret = context->pressed[25] || context->button_pressed[25]; + break; + case OBS_KEY_MOUSE23: + ret = context->pressed[26] || context->button_pressed[26]; + break; + case OBS_KEY_MOUSE24: + ret = context->pressed[27] || context->button_pressed[27]; + break; + case OBS_KEY_MOUSE25: + ret = context->pressed[28] || context->button_pressed[28]; + break; + case OBS_KEY_MOUSE26: + ret = context->pressed[29] || context->button_pressed[29]; + break; + case OBS_KEY_MOUSE27: + ret = context->pressed[30] || context->button_pressed[30]; + break; + case OBS_KEY_MOUSE28: + ret = context->pressed[31] || context->button_pressed[31]; + break; + case OBS_KEY_MOUSE29: + ret = context->pressed[32] || context->button_pressed[32]; + break; + default: + break; + } + + for (int i = 0; i != XINPUT_MOUSE_LEN; i++) + if (context->update[i]) + context->button_pressed[i] = context->pressed[i]; +#else + xcb_generic_error_t *error = NULL; + xcb_query_pointer_cookie_t qpc; + xcb_query_pointer_reply_t *reply; + + qpc = xcb_query_pointer(connection, root_window(context, connection)); + reply = xcb_query_pointer_reply(connection, qpc, &error); + + if (error) { + blog(LOG_WARNING, "xcb_query_pointer_reply failed"); + } else { + uint16_t buttons = reply->mask; + + switch (key) { + case OBS_KEY_MOUSE1: + ret = buttons & XCB_BUTTON_MASK_1; + break; + case OBS_KEY_MOUSE2: + ret = buttons & XCB_BUTTON_MASK_3; + break; + case OBS_KEY_MOUSE3: + ret = buttons & XCB_BUTTON_MASK_2; + break; + default:; + } + } + + free(reply); + free(error); +#endif + return ret; +} + +static inline bool keycode_pressed(xcb_query_keymap_reply_t *reply, + xcb_keycode_t code) +{ + return (reply->keys[code / 8] & (1 << (code % 8))) != 0; +} + +static bool key_pressed(xcb_connection_t *connection, + obs_hotkeys_platform_t *context, obs_key_t key) +{ + struct keycode_list *codes = &context->keycodes[key]; + xcb_generic_error_t *error = NULL; + xcb_query_keymap_reply_t *reply; + bool pressed = false; + + reply = xcb_query_keymap_reply(connection, xcb_query_keymap(connection), + &error); + if (error) { + blog(LOG_WARNING, "xcb_query_keymap failed"); + + } else if (key == OBS_KEY_META) { + pressed = keycode_pressed(reply, context->super_l_code) || + keycode_pressed(reply, context->super_r_code); + + } else { + for (size_t i = 0; i < codes->list.num; i++) { + if (keycode_pressed(reply, codes->list.array[i])) { + pressed = true; + break; + } + } + } + + free(reply); + free(error); + return pressed; +} + +static bool +obs_nix_x11_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, + obs_key_t key) +{ + xcb_connection_t *conn = XGetXCBConnection(context->display); + + if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { + return mouse_button_pressed(conn, context, key); + } else { + return key_pressed(conn, context, key); + } +} + +static bool get_key_translation(struct dstr *dstr, xcb_keycode_t keycode) +{ + xcb_connection_t *connection; + char name[128]; + + connection = XGetXCBConnection(obs->hotkeys.platform_context->display); + + XKeyEvent event = {0}; + event.type = KeyPress; + event.display = obs->hotkeys.platform_context->display; + event.keycode = keycode; + event.root = root_window(obs->hotkeys.platform_context, connection); + event.window = event.root; + + if (keycode) { + int len = XLookupString(&event, name, 128, NULL, NULL); + if (len) { + dstr_ncopy(dstr, name, len); + dstr_to_upper(dstr); + return true; + } + } + + return false; +} + +static void obs_nix_x11_key_to_str(obs_key_t key, struct dstr *dstr) +{ + if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { + if (obs->hotkeys.translations[key]) { + dstr_copy(dstr, obs->hotkeys.translations[key]); + } else { + dstr_printf(dstr, "Mouse %d", + (int)(key - OBS_KEY_MOUSE1 + 1)); + } + return; + } + + if (key >= OBS_KEY_NUM0 && key <= OBS_KEY_NUM9) { + if (obs->hotkeys.translations[key]) { + dstr_copy(dstr, obs->hotkeys.translations[key]); + } else { + dstr_printf(dstr, "Numpad %d", + (int)(key - OBS_KEY_NUM0)); + } + return; + } + +#define translate_key(key, def) \ + dstr_copy(dstr, obs_get_hotkey_translation(key, def)) + + switch (key) { + case OBS_KEY_INSERT: + return translate_key(key, "Insert"); + case OBS_KEY_DELETE: + return translate_key(key, "Delete"); + case OBS_KEY_HOME: + return translate_key(key, "Home"); + case OBS_KEY_END: + return translate_key(key, "End"); + case OBS_KEY_PAGEUP: + return translate_key(key, "Page Up"); + case OBS_KEY_PAGEDOWN: + return translate_key(key, "Page Down"); + case OBS_KEY_NUMLOCK: + return translate_key(key, "Num Lock"); + case OBS_KEY_SCROLLLOCK: + return translate_key(key, "Scroll Lock"); + case OBS_KEY_CAPSLOCK: + return translate_key(key, "Caps Lock"); + case OBS_KEY_BACKSPACE: + return translate_key(key, "Backspace"); + case OBS_KEY_TAB: + return translate_key(key, "Tab"); + case OBS_KEY_PRINT: + return translate_key(key, "Print"); + case OBS_KEY_PAUSE: + return translate_key(key, "Pause"); + case OBS_KEY_LEFT: + return translate_key(key, "Left"); + case OBS_KEY_RIGHT: + return translate_key(key, "Right"); + case OBS_KEY_UP: + return translate_key(key, "Up"); + case OBS_KEY_DOWN: + return translate_key(key, "Down"); + case OBS_KEY_SHIFT: + return translate_key(key, "Shift"); + case OBS_KEY_ALT: + return translate_key(key, "Alt"); + case OBS_KEY_CONTROL: + return translate_key(key, "Control"); + case OBS_KEY_META: + return translate_key(key, "Super"); + case OBS_KEY_MENU: + return translate_key(key, "Menu"); + case OBS_KEY_NUMASTERISK: + return translate_key(key, "Numpad *"); + case OBS_KEY_NUMPLUS: + return translate_key(key, "Numpad +"); + case OBS_KEY_NUMCOMMA: + return translate_key(key, "Numpad ,"); + case OBS_KEY_NUMPERIOD: + return translate_key(key, "Numpad ."); + case OBS_KEY_NUMSLASH: + return translate_key(key, "Numpad /"); + case OBS_KEY_SPACE: + return translate_key(key, "Space"); + case OBS_KEY_ESCAPE: + return translate_key(key, "Escape"); + default:; + } + + if (key >= OBS_KEY_F1 && key <= OBS_KEY_F35) { + dstr_printf(dstr, "F%d", (int)(key - OBS_KEY_F1 + 1)); + return; + } + + obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; + struct keycode_list *keycodes = &context->keycodes[key]; + + for (size_t i = 0; i < keycodes->list.num; i++) { + if (get_key_translation(dstr, keycodes->list.array[i])) { + break; + } + } + + if (key != OBS_KEY_NONE && dstr_is_empty(dstr)) { + dstr_copy(dstr, obs_key_to_name(key)); + } +} + +static obs_key_t key_from_keycode(obs_hotkeys_platform_t *context, + xcb_keycode_t code) +{ + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { + struct keycode_list *codes = &context->keycodes[i]; + + for (size_t j = 0; j < codes->list.num; j++) { + if (codes->list.array[j] == code) { + return (obs_key_t)i; + } + } + } + + return OBS_KEY_NONE; +} + +static obs_key_t obs_nix_x11_key_from_virtual_key(int sym) +{ + obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; + const xcb_keysym_t *keysyms = context->keysyms; + int syms_per_code = context->syms_per_code; + int num_keysyms = context->num_keysyms; + + if (sym == 0) + return OBS_KEY_NONE; + + for (int i = 0; i < num_keysyms; i++) { + if (keysyms[i] == (xcb_keysym_t)sym) { + xcb_keycode_t code = (xcb_keycode_t)(i / syms_per_code); + code += context->min_keycode; + obs_key_t key = key_from_keycode(context, code); + + return key; + } + } + + return OBS_KEY_NONE; +} + +static int obs_nix_x11_key_to_virtual_key(obs_key_t key) +{ + if (key == OBS_KEY_META) + return XK_Super_L; + + return (int)obs->hotkeys.platform_context->base_keysyms[(int)key]; +} + +static const struct obs_nix_hotkeys_vtable x11_hotkeys_vtable = { + .init = obs_nix_x11_hotkeys_platform_init, + .free = obs_nix_x11_hotkeys_platform_free, + .is_pressed = obs_nix_x11_hotkeys_platform_is_pressed, + .key_to_str = obs_nix_x11_key_to_str, + .key_from_virtual_key = obs_nix_x11_key_from_virtual_key, + .key_to_virtual_key = obs_nix_x11_key_to_virtual_key, +}; + +const struct obs_nix_hotkeys_vtable *obs_nix_x11_get_hotkeys_vtable(void) +{ + return &x11_hotkeys_vtable; +} diff --git a/libobs/obs-nix-x11.h b/libobs/obs-nix-x11.h new file mode 100644 index 000000000..34b9dd0f5 --- /dev/null +++ b/libobs/obs-nix-x11.h @@ -0,0 +1,22 @@ +/****************************************************************************** + Copyright (C) 2019 by Jason Francis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include "obs-nix.h" + +void obs_nix_x11_log_info(void); + +const struct obs_nix_hotkeys_vtable *obs_nix_x11_get_hotkeys_vtable(void); diff --git a/libobs/obs-nix.c b/libobs/obs-nix.c index 382fa0546..4e8501224 100644 --- a/libobs/obs-nix.c +++ b/libobs/obs-nix.c @@ -17,6 +17,8 @@ ******************************************************************************/ #include "obs-internal.h" +#include "obs-nix.h" +#include "obs-nix-x11.h" #if defined(__FreeBSD__) #define _GNU_SOURCE #endif @@ -30,15 +32,6 @@ #include #endif #include -#include -#if USE_XINPUT -#include -#endif -#include -#include -#include -#include -#include #include const char *get_module_extension(void) @@ -64,6 +57,8 @@ static const char *module_data[] = { static const int module_patterns_size = sizeof(module_bin) / sizeof(module_bin[0]); +static const struct obs_nix_hotkeys_vtable *hotkeys_vtable = NULL; + void add_default_module_paths(void) { for (int i = 0; i < module_patterns_size; i++) @@ -256,37 +251,6 @@ static void log_kernel_version(void) blog(LOG_INFO, "Kernel Version: %s %s", info.sysname, info.release); } -static void log_x_info(void) -{ - Display *dpy = XOpenDisplay(NULL); - if (!dpy) { - blog(LOG_INFO, "Unable to open X display"); - return; - } - - int protocol_version = ProtocolVersion(dpy); - int protocol_revision = ProtocolRevision(dpy); - int vendor_release = VendorRelease(dpy); - const char *vendor_name = ServerVendor(dpy); - - if (strstr(vendor_name, "X.Org")) { - blog(LOG_INFO, - "Window System: X%d.%d, Vendor: %s, Version: %d" - ".%d.%d", - protocol_version, protocol_revision, vendor_name, - vendor_release / 10000000, (vendor_release / 100000) % 100, - (vendor_release / 1000) % 100); - } else { - blog(LOG_INFO, - "Window System: X%d.%d - vendor string: %s - " - "vendor release: %d", - protocol_version, protocol_revision, vendor_name, - vendor_release); - } - - XCloseDisplay(dpy); -} - #if defined(__linux__) static void log_distribution_info(void) { @@ -352,1203 +316,41 @@ void log_system_info(void) log_distribution_info(); log_desktop_session_info(); #endif - log_x_info(); + obs_nix_x11_log_info(); } -/* So here's how linux works with key mapping: - * - * First, there's a global key symbol enum (xcb_keysym_t) which has unique - * values for all possible symbols keys can have (e.g., '1' and '!' are - * different values). - * - * Then there's a key code (xcb_keycode_t), which is basically an index to the - * actual key itself on the keyboard (e.g., '1' and '!' will share the same - * value). - * - * xcb_keysym_t values should be given to libobs, and libobs will translate it - * to an obs_key_t, and although xcb_keysym_t can differ ('!' vs '1'), it will - * get the obs_key_t value that represents the actual key pressed; in other - * words it will be based on the key code rather than the key symbol. The same - * applies to checking key press states. - */ - -struct keycode_list { - DARRAY(xcb_keycode_t) list; -}; - -struct obs_hotkeys_platform { - Display *display; - xcb_keysym_t base_keysyms[OBS_KEY_LAST_VALUE]; - struct keycode_list keycodes[OBS_KEY_LAST_VALUE]; - xcb_keycode_t min_keycode; - xcb_keycode_t super_l_code; - xcb_keycode_t super_r_code; - - /* stores a copy of the keysym map for keycodes */ - xcb_keysym_t *keysyms; - int num_keysyms; - int syms_per_code; - -#if USE_XINPUT - bool pressed[XINPUT_MOUSE_LEN]; - bool update[XINPUT_MOUSE_LEN]; - bool button_pressed[XINPUT_MOUSE_LEN]; -#endif -}; - -#define MOUSE_1 (1 << 16) -#define MOUSE_2 (2 << 16) -#define MOUSE_3 (3 << 16) -#define MOUSE_4 (4 << 16) -#define MOUSE_5 (5 << 16) - -static int get_keysym(obs_key_t key) -{ - switch (key) { - case OBS_KEY_RETURN: - return XK_Return; - case OBS_KEY_ESCAPE: - return XK_Escape; - case OBS_KEY_TAB: - return XK_Tab; - case OBS_KEY_BACKSPACE: - return XK_BackSpace; - case OBS_KEY_INSERT: - return XK_Insert; - case OBS_KEY_DELETE: - return XK_Delete; - case OBS_KEY_PAUSE: - return XK_Pause; - case OBS_KEY_PRINT: - return XK_Print; - case OBS_KEY_HOME: - return XK_Home; - case OBS_KEY_END: - return XK_End; - case OBS_KEY_LEFT: - return XK_Left; - case OBS_KEY_UP: - return XK_Up; - case OBS_KEY_RIGHT: - return XK_Right; - case OBS_KEY_DOWN: - return XK_Down; - case OBS_KEY_PAGEUP: - return XK_Prior; - case OBS_KEY_PAGEDOWN: - return XK_Next; - - case OBS_KEY_SHIFT: - return XK_Shift_L; - case OBS_KEY_CONTROL: - return XK_Control_L; - case OBS_KEY_ALT: - return XK_Alt_L; - case OBS_KEY_CAPSLOCK: - return XK_Caps_Lock; - case OBS_KEY_NUMLOCK: - return XK_Num_Lock; - case OBS_KEY_SCROLLLOCK: - return XK_Scroll_Lock; - - case OBS_KEY_F1: - return XK_F1; - case OBS_KEY_F2: - return XK_F2; - case OBS_KEY_F3: - return XK_F3; - case OBS_KEY_F4: - return XK_F4; - case OBS_KEY_F5: - return XK_F5; - case OBS_KEY_F6: - return XK_F6; - case OBS_KEY_F7: - return XK_F7; - case OBS_KEY_F8: - return XK_F8; - case OBS_KEY_F9: - return XK_F9; - case OBS_KEY_F10: - return XK_F10; - case OBS_KEY_F11: - return XK_F11; - case OBS_KEY_F12: - return XK_F12; - case OBS_KEY_F13: - return XK_F13; - case OBS_KEY_F14: - return XK_F14; - case OBS_KEY_F15: - return XK_F15; - case OBS_KEY_F16: - return XK_F16; - case OBS_KEY_F17: - return XK_F17; - case OBS_KEY_F18: - return XK_F18; - case OBS_KEY_F19: - return XK_F19; - case OBS_KEY_F20: - return XK_F20; - case OBS_KEY_F21: - return XK_F21; - case OBS_KEY_F22: - return XK_F22; - case OBS_KEY_F23: - return XK_F23; - case OBS_KEY_F24: - return XK_F24; - case OBS_KEY_F25: - return XK_F25; - case OBS_KEY_F26: - return XK_F26; - case OBS_KEY_F27: - return XK_F27; - case OBS_KEY_F28: - return XK_F28; - case OBS_KEY_F29: - return XK_F29; - case OBS_KEY_F30: - return XK_F30; - case OBS_KEY_F31: - return XK_F31; - case OBS_KEY_F32: - return XK_F32; - case OBS_KEY_F33: - return XK_F33; - case OBS_KEY_F34: - return XK_F34; - case OBS_KEY_F35: - return XK_F35; - - case OBS_KEY_MENU: - return XK_Menu; - case OBS_KEY_HYPER_L: - return XK_Hyper_L; - case OBS_KEY_HYPER_R: - return XK_Hyper_R; - case OBS_KEY_HELP: - return XK_Help; - case OBS_KEY_CANCEL: - return XK_Cancel; - case OBS_KEY_FIND: - return XK_Find; - case OBS_KEY_REDO: - return XK_Redo; - case OBS_KEY_UNDO: - return XK_Undo; - case OBS_KEY_SPACE: - return XK_space; - - case OBS_KEY_COPY: - return XF86XK_Copy; - case OBS_KEY_CUT: - return XF86XK_Cut; - case OBS_KEY_OPEN: - return XF86XK_Open; - case OBS_KEY_PASTE: - return XF86XK_Paste; - case OBS_KEY_FRONT: - return SunXK_Front; - case OBS_KEY_PROPS: - return SunXK_Props; - - case OBS_KEY_EXCLAM: - return XK_exclam; - case OBS_KEY_QUOTEDBL: - return XK_quotedbl; - case OBS_KEY_NUMBERSIGN: - return XK_numbersign; - case OBS_KEY_DOLLAR: - return XK_dollar; - case OBS_KEY_PERCENT: - return XK_percent; - case OBS_KEY_AMPERSAND: - return XK_ampersand; - case OBS_KEY_APOSTROPHE: - return XK_apostrophe; - case OBS_KEY_PARENLEFT: - return XK_parenleft; - case OBS_KEY_PARENRIGHT: - return XK_parenright; - case OBS_KEY_ASTERISK: - return XK_asterisk; - case OBS_KEY_PLUS: - return XK_plus; - case OBS_KEY_COMMA: - return XK_comma; - case OBS_KEY_MINUS: - return XK_minus; - case OBS_KEY_PERIOD: - return XK_period; - case OBS_KEY_SLASH: - return XK_slash; - case OBS_KEY_0: - return XK_0; - case OBS_KEY_1: - return XK_1; - case OBS_KEY_2: - return XK_2; - case OBS_KEY_3: - return XK_3; - case OBS_KEY_4: - return XK_4; - case OBS_KEY_5: - return XK_5; - case OBS_KEY_6: - return XK_6; - case OBS_KEY_7: - return XK_7; - case OBS_KEY_8: - return XK_8; - case OBS_KEY_9: - return XK_9; - case OBS_KEY_NUMEQUAL: - return XK_KP_Equal; - case OBS_KEY_NUMASTERISK: - return XK_KP_Multiply; - case OBS_KEY_NUMPLUS: - return XK_KP_Add; - case OBS_KEY_NUMCOMMA: - return XK_KP_Separator; - case OBS_KEY_NUMMINUS: - return XK_KP_Subtract; - case OBS_KEY_NUMPERIOD: - return XK_KP_Decimal; - case OBS_KEY_NUMSLASH: - return XK_KP_Divide; - case OBS_KEY_NUM0: - return XK_KP_0; - case OBS_KEY_NUM1: - return XK_KP_1; - case OBS_KEY_NUM2: - return XK_KP_2; - case OBS_KEY_NUM3: - return XK_KP_3; - case OBS_KEY_NUM4: - return XK_KP_4; - case OBS_KEY_NUM5: - return XK_KP_5; - case OBS_KEY_NUM6: - return XK_KP_6; - case OBS_KEY_NUM7: - return XK_KP_7; - case OBS_KEY_NUM8: - return XK_KP_8; - case OBS_KEY_NUM9: - return XK_KP_9; - case OBS_KEY_COLON: - return XK_colon; - case OBS_KEY_SEMICOLON: - return XK_semicolon; - case OBS_KEY_LESS: - return XK_less; - case OBS_KEY_EQUAL: - return XK_equal; - case OBS_KEY_GREATER: - return XK_greater; - case OBS_KEY_QUESTION: - return XK_question; - case OBS_KEY_AT: - return XK_at; - case OBS_KEY_A: - return XK_A; - case OBS_KEY_B: - return XK_B; - case OBS_KEY_C: - return XK_C; - case OBS_KEY_D: - return XK_D; - case OBS_KEY_E: - return XK_E; - case OBS_KEY_F: - return XK_F; - case OBS_KEY_G: - return XK_G; - case OBS_KEY_H: - return XK_H; - case OBS_KEY_I: - return XK_I; - case OBS_KEY_J: - return XK_J; - case OBS_KEY_K: - return XK_K; - case OBS_KEY_L: - return XK_L; - case OBS_KEY_M: - return XK_M; - case OBS_KEY_N: - return XK_N; - case OBS_KEY_O: - return XK_O; - case OBS_KEY_P: - return XK_P; - case OBS_KEY_Q: - return XK_Q; - case OBS_KEY_R: - return XK_R; - case OBS_KEY_S: - return XK_S; - case OBS_KEY_T: - return XK_T; - case OBS_KEY_U: - return XK_U; - case OBS_KEY_V: - return XK_V; - case OBS_KEY_W: - return XK_W; - case OBS_KEY_X: - return XK_X; - case OBS_KEY_Y: - return XK_Y; - case OBS_KEY_Z: - return XK_Z; - case OBS_KEY_BRACKETLEFT: - return XK_bracketleft; - case OBS_KEY_BACKSLASH: - return XK_backslash; - case OBS_KEY_BRACKETRIGHT: - return XK_bracketright; - case OBS_KEY_ASCIICIRCUM: - return XK_asciicircum; - case OBS_KEY_UNDERSCORE: - return XK_underscore; - case OBS_KEY_QUOTELEFT: - return XK_quoteleft; - case OBS_KEY_BRACELEFT: - return XK_braceleft; - case OBS_KEY_BAR: - return XK_bar; - case OBS_KEY_BRACERIGHT: - return XK_braceright; - case OBS_KEY_ASCIITILDE: - return XK_grave; - case OBS_KEY_NOBREAKSPACE: - return XK_nobreakspace; - case OBS_KEY_EXCLAMDOWN: - return XK_exclamdown; - case OBS_KEY_CENT: - return XK_cent; - case OBS_KEY_STERLING: - return XK_sterling; - case OBS_KEY_CURRENCY: - return XK_currency; - case OBS_KEY_YEN: - return XK_yen; - case OBS_KEY_BROKENBAR: - return XK_brokenbar; - case OBS_KEY_SECTION: - return XK_section; - case OBS_KEY_DIAERESIS: - return XK_diaeresis; - case OBS_KEY_COPYRIGHT: - return XK_copyright; - case OBS_KEY_ORDFEMININE: - return XK_ordfeminine; - case OBS_KEY_GUILLEMOTLEFT: - return XK_guillemotleft; - case OBS_KEY_NOTSIGN: - return XK_notsign; - case OBS_KEY_HYPHEN: - return XK_hyphen; - case OBS_KEY_REGISTERED: - return XK_registered; - case OBS_KEY_MACRON: - return XK_macron; - case OBS_KEY_DEGREE: - return XK_degree; - case OBS_KEY_PLUSMINUS: - return XK_plusminus; - case OBS_KEY_TWOSUPERIOR: - return XK_twosuperior; - case OBS_KEY_THREESUPERIOR: - return XK_threesuperior; - case OBS_KEY_ACUTE: - return XK_acute; - case OBS_KEY_MU: - return XK_mu; - case OBS_KEY_PARAGRAPH: - return XK_paragraph; - case OBS_KEY_PERIODCENTERED: - return XK_periodcentered; - case OBS_KEY_CEDILLA: - return XK_cedilla; - case OBS_KEY_ONESUPERIOR: - return XK_onesuperior; - case OBS_KEY_MASCULINE: - return XK_masculine; - case OBS_KEY_GUILLEMOTRIGHT: - return XK_guillemotright; - case OBS_KEY_ONEQUARTER: - return XK_onequarter; - case OBS_KEY_ONEHALF: - return XK_onehalf; - case OBS_KEY_THREEQUARTERS: - return XK_threequarters; - case OBS_KEY_QUESTIONDOWN: - return XK_questiondown; - case OBS_KEY_AGRAVE: - return XK_Agrave; - case OBS_KEY_AACUTE: - return XK_Aacute; - case OBS_KEY_ACIRCUMFLEX: - return XK_Acircumflex; - case OBS_KEY_ATILDE: - return XK_Atilde; - case OBS_KEY_ADIAERESIS: - return XK_Adiaeresis; - case OBS_KEY_ARING: - return XK_Aring; - case OBS_KEY_AE: - return XK_AE; - case OBS_KEY_CCEDILLA: - return XK_cedilla; - case OBS_KEY_EGRAVE: - return XK_Egrave; - case OBS_KEY_EACUTE: - return XK_Eacute; - case OBS_KEY_ECIRCUMFLEX: - return XK_Ecircumflex; - case OBS_KEY_EDIAERESIS: - return XK_Ediaeresis; - case OBS_KEY_IGRAVE: - return XK_Igrave; - case OBS_KEY_IACUTE: - return XK_Iacute; - case OBS_KEY_ICIRCUMFLEX: - return XK_Icircumflex; - case OBS_KEY_IDIAERESIS: - return XK_Idiaeresis; - case OBS_KEY_ETH: - return XK_ETH; - case OBS_KEY_NTILDE: - return XK_Ntilde; - case OBS_KEY_OGRAVE: - return XK_Ograve; - case OBS_KEY_OACUTE: - return XK_Oacute; - case OBS_KEY_OCIRCUMFLEX: - return XK_Ocircumflex; - case OBS_KEY_ODIAERESIS: - return XK_Odiaeresis; - case OBS_KEY_MULTIPLY: - return XK_multiply; - case OBS_KEY_OOBLIQUE: - return XK_Ooblique; - case OBS_KEY_UGRAVE: - return XK_Ugrave; - case OBS_KEY_UACUTE: - return XK_Uacute; - case OBS_KEY_UCIRCUMFLEX: - return XK_Ucircumflex; - case OBS_KEY_UDIAERESIS: - return XK_Udiaeresis; - case OBS_KEY_YACUTE: - return XK_Yacute; - case OBS_KEY_THORN: - return XK_Thorn; - case OBS_KEY_SSHARP: - return XK_ssharp; - case OBS_KEY_DIVISION: - return XK_division; - case OBS_KEY_YDIAERESIS: - return XK_Ydiaeresis; - case OBS_KEY_MULTI_KEY: - return XK_Multi_key; - case OBS_KEY_CODEINPUT: - return XK_Codeinput; - case OBS_KEY_SINGLECANDIDATE: - return XK_SingleCandidate; - case OBS_KEY_MULTIPLECANDIDATE: - return XK_MultipleCandidate; - case OBS_KEY_PREVIOUSCANDIDATE: - return XK_PreviousCandidate; - case OBS_KEY_MODE_SWITCH: - return XK_Mode_switch; - case OBS_KEY_KANJI: - return XK_Kanji; - case OBS_KEY_MUHENKAN: - return XK_Muhenkan; - case OBS_KEY_HENKAN: - return XK_Henkan; - case OBS_KEY_ROMAJI: - return XK_Romaji; - case OBS_KEY_HIRAGANA: - return XK_Hiragana; - case OBS_KEY_KATAKANA: - return XK_Katakana; - case OBS_KEY_HIRAGANA_KATAKANA: - return XK_Hiragana_Katakana; - case OBS_KEY_ZENKAKU: - return XK_Zenkaku; - case OBS_KEY_HANKAKU: - return XK_Hankaku; - case OBS_KEY_ZENKAKU_HANKAKU: - return XK_Zenkaku_Hankaku; - case OBS_KEY_TOUROKU: - return XK_Touroku; - case OBS_KEY_MASSYO: - return XK_Massyo; - case OBS_KEY_KANA_LOCK: - return XK_Kana_Lock; - case OBS_KEY_KANA_SHIFT: - return XK_Kana_Shift; - case OBS_KEY_EISU_SHIFT: - return XK_Eisu_Shift; - case OBS_KEY_EISU_TOGGLE: - return XK_Eisu_toggle; - case OBS_KEY_HANGUL: - return XK_Hangul; - case OBS_KEY_HANGUL_START: - return XK_Hangul_Start; - case OBS_KEY_HANGUL_END: - return XK_Hangul_End; - case OBS_KEY_HANGUL_HANJA: - return XK_Hangul_Hanja; - case OBS_KEY_HANGUL_JAMO: - return XK_Hangul_Jamo; - case OBS_KEY_HANGUL_ROMAJA: - return XK_Hangul_Romaja; - case OBS_KEY_HANGUL_BANJA: - return XK_Hangul_Banja; - case OBS_KEY_HANGUL_PREHANJA: - return XK_Hangul_PreHanja; - case OBS_KEY_HANGUL_POSTHANJA: - return XK_Hangul_PostHanja; - case OBS_KEY_HANGUL_SPECIAL: - return XK_Hangul_Special; - case OBS_KEY_DEAD_GRAVE: - return XK_dead_grave; - case OBS_KEY_DEAD_ACUTE: - return XK_dead_acute; - case OBS_KEY_DEAD_CIRCUMFLEX: - return XK_dead_circumflex; - case OBS_KEY_DEAD_TILDE: - return XK_dead_tilde; - case OBS_KEY_DEAD_MACRON: - return XK_dead_macron; - case OBS_KEY_DEAD_BREVE: - return XK_dead_breve; - case OBS_KEY_DEAD_ABOVEDOT: - return XK_dead_abovedot; - case OBS_KEY_DEAD_DIAERESIS: - return XK_dead_diaeresis; - case OBS_KEY_DEAD_ABOVERING: - return XK_dead_abovering; - case OBS_KEY_DEAD_DOUBLEACUTE: - return XK_dead_doubleacute; - case OBS_KEY_DEAD_CARON: - return XK_dead_caron; - case OBS_KEY_DEAD_CEDILLA: - return XK_dead_cedilla; - case OBS_KEY_DEAD_OGONEK: - return XK_dead_ogonek; - case OBS_KEY_DEAD_IOTA: - return XK_dead_iota; - case OBS_KEY_DEAD_VOICED_SOUND: - return XK_dead_voiced_sound; - case OBS_KEY_DEAD_SEMIVOICED_SOUND: - return XK_dead_semivoiced_sound; - case OBS_KEY_DEAD_BELOWDOT: - return XK_dead_belowdot; - case OBS_KEY_DEAD_HOOK: - return XK_dead_hook; - case OBS_KEY_DEAD_HORN: - return XK_dead_horn; - - case OBS_KEY_MOUSE1: - return MOUSE_1; - case OBS_KEY_MOUSE2: - return MOUSE_2; - case OBS_KEY_MOUSE3: - return MOUSE_3; - case OBS_KEY_MOUSE4: - return MOUSE_4; - case OBS_KEY_MOUSE5: - return MOUSE_5; - - /* TODO: Implement keys for non-US keyboards */ - default:; - } - return 0; -} - -static inline void fill_base_keysyms(struct obs_core_hotkeys *hotkeys) -{ - for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) - hotkeys->platform_context->base_keysyms[i] = get_keysym(i); -} - -static obs_key_t key_from_base_keysym(obs_hotkeys_platform_t *context, - xcb_keysym_t code) -{ - for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { - if (context->base_keysyms[i] == (xcb_keysym_t)code) { - return (obs_key_t)i; - } - } - - return OBS_KEY_NONE; -} - -static inline void add_key(obs_hotkeys_platform_t *context, obs_key_t key, - int code) -{ - xcb_keycode_t kc = (xcb_keycode_t)code; - da_push_back(context->keycodes[key].list, &kc); - - if (context->keycodes[key].list.num > 1) { - blog(LOG_DEBUG, - "found alternate keycode %d for %s " - "which already has keycode %d", - code, obs_key_to_name(key), - (int)context->keycodes[key].list.array[0]); - } -} - -static inline bool fill_keycodes(struct obs_core_hotkeys *hotkeys) -{ - obs_hotkeys_platform_t *context = hotkeys->platform_context; - xcb_connection_t *connection = XGetXCBConnection(context->display); - const struct xcb_setup_t *setup = xcb_get_setup(connection); - xcb_get_keyboard_mapping_cookie_t cookie; - xcb_get_keyboard_mapping_reply_t *reply; - xcb_generic_error_t *error = NULL; - int code; - - int mincode = setup->min_keycode; - int maxcode = setup->max_keycode; - - context->min_keycode = setup->min_keycode; - - cookie = xcb_get_keyboard_mapping(connection, mincode, - maxcode - mincode + 1); - - reply = xcb_get_keyboard_mapping_reply(connection, cookie, &error); - - if (error || !reply) { - blog(LOG_WARNING, "xcb_get_keyboard_mapping_reply failed"); - goto error1; - } - - const xcb_keysym_t *keysyms = xcb_get_keyboard_mapping_keysyms(reply); - int syms_per_code = (int)reply->keysyms_per_keycode; - - context->num_keysyms = (maxcode - mincode + 1) * syms_per_code; - context->syms_per_code = syms_per_code; - context->keysyms = - bmemdup(keysyms, sizeof(xcb_keysym_t) * context->num_keysyms); - - for (code = mincode; code <= maxcode; code++) { - const xcb_keysym_t *sym; - obs_key_t key; - - sym = &keysyms[(code - mincode) * syms_per_code]; - - for (int i = 0; i < syms_per_code; i++) { - if (!sym[i]) - break; - - if (sym[i] == XK_Super_L) { - context->super_l_code = code; - break; - } else if (sym[i] == XK_Super_R) { - context->super_r_code = code; - break; - } else { - key = key_from_base_keysym(context, sym[i]); - - if (key != OBS_KEY_NONE) { - add_key(context, key, code); - break; - } - } - } - } - -error1: - free(reply); - free(error); - - return error != NULL || reply == NULL; -} - -static xcb_screen_t *default_screen(obs_hotkeys_platform_t *context, - xcb_connection_t *connection) -{ - int def_screen_idx = XDefaultScreen(context->display); - xcb_screen_iterator_t iter; - - iter = xcb_setup_roots_iterator(xcb_get_setup(connection)); - while (iter.rem) { - if (def_screen_idx-- == 0) - return iter.data; - - xcb_screen_next(&iter); - } - - return NULL; -} - -static inline xcb_window_t root_window(obs_hotkeys_platform_t *context, - xcb_connection_t *connection) -{ - xcb_screen_t *screen = default_screen(context, connection); - if (screen) - return screen->root; - return 0; -} - -#if USE_XINPUT -static inline void registerMouseEvents(struct obs_core_hotkeys *hotkeys) -{ - obs_hotkeys_platform_t *context = hotkeys->platform_context; - xcb_connection_t *connection = XGetXCBConnection(context->display); - xcb_window_t window = root_window(context, connection); - - struct { - xcb_input_event_mask_t head; - xcb_input_xi_event_mask_t mask; - } mask; - mask.head.deviceid = XCB_INPUT_DEVICE_ALL_MASTER; - mask.head.mask_len = sizeof(mask.mask) / sizeof(uint32_t); - mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | - XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE; - - xcb_input_xi_select_events(connection, window, 1, &mask.head); - xcb_flush(connection); -} -#endif - bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) { - Display *display = XOpenDisplay(NULL); - if (!display) - return false; + hotkeys_vtable = obs_nix_x11_get_hotkeys_vtable(); - hotkeys->platform_context = bzalloc(sizeof(obs_hotkeys_platform_t)); - hotkeys->platform_context->display = display; - -#if USE_XINPUT - registerMouseEvents(hotkeys); -#endif - fill_base_keysyms(hotkeys); - fill_keycodes(hotkeys); - return true; + return hotkeys_vtable->init(hotkeys); } void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) { - obs_hotkeys_platform_t *context = hotkeys->platform_context; - - for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) - da_free(context->keycodes[i].list); - - XCloseDisplay(context->display); - bfree(context->keysyms); - bfree(context); - - hotkeys->platform_context = NULL; -} - -static bool mouse_button_pressed(xcb_connection_t *connection, - obs_hotkeys_platform_t *context, obs_key_t key) -{ - bool ret = false; - -#if USE_XINPUT - memset(context->pressed, 0, XINPUT_MOUSE_LEN); - memset(context->update, 0, XINPUT_MOUSE_LEN); - - xcb_generic_event_t *ev; - while ((ev = xcb_poll_for_event(connection))) { - if ((ev->response_type & ~80) == XCB_GE_GENERIC) { - switch (((xcb_ge_event_t *)ev)->event_type) { - case XCB_INPUT_RAW_BUTTON_PRESS: { - xcb_input_raw_button_press_event_t *mot; - mot = (xcb_input_raw_button_press_event_t *)ev; - if (mot->detail < XINPUT_MOUSE_LEN) { - context->pressed[mot->detail - 1] = - true; - context->update[mot->detail - 1] = true; - } else { - blog(LOG_WARNING, "Unsupported button"); - } - break; - } - case XCB_INPUT_RAW_BUTTON_RELEASE: { - xcb_input_raw_button_release_event_t *mot; - mot = (xcb_input_raw_button_release_event_t *)ev; - if (mot->detail < XINPUT_MOUSE_LEN) - context->update[mot->detail - 1] = true; - else - blog(LOG_WARNING, "Unsupported button"); - break; - } - default: - break; - } - } - free(ev); - } - - // Mouse 2 for OBS is Right Click and Mouse 3 is Wheel Click. - // Mouse Wheel axis clicks (xinput mot->detail 4 5 6 7) are ignored. - switch (key) { - case OBS_KEY_MOUSE1: - ret = context->pressed[0] || context->button_pressed[0]; - break; - case OBS_KEY_MOUSE2: - ret = context->pressed[2] || context->button_pressed[2]; - break; - case OBS_KEY_MOUSE3: - ret = context->pressed[1] || context->button_pressed[1]; - break; - case OBS_KEY_MOUSE4: - ret = context->pressed[7] || context->button_pressed[7]; - break; - case OBS_KEY_MOUSE5: - ret = context->pressed[8] || context->button_pressed[8]; - break; - case OBS_KEY_MOUSE6: - ret = context->pressed[9] || context->button_pressed[9]; - break; - case OBS_KEY_MOUSE7: - ret = context->pressed[10] || context->button_pressed[10]; - break; - case OBS_KEY_MOUSE8: - ret = context->pressed[11] || context->button_pressed[11]; - break; - case OBS_KEY_MOUSE9: - ret = context->pressed[12] || context->button_pressed[12]; - break; - case OBS_KEY_MOUSE10: - ret = context->pressed[13] || context->button_pressed[13]; - break; - case OBS_KEY_MOUSE11: - ret = context->pressed[14] || context->button_pressed[14]; - break; - case OBS_KEY_MOUSE12: - ret = context->pressed[15] || context->button_pressed[15]; - break; - case OBS_KEY_MOUSE13: - ret = context->pressed[16] || context->button_pressed[16]; - break; - case OBS_KEY_MOUSE14: - ret = context->pressed[17] || context->button_pressed[17]; - break; - case OBS_KEY_MOUSE15: - ret = context->pressed[18] || context->button_pressed[18]; - break; - case OBS_KEY_MOUSE16: - ret = context->pressed[19] || context->button_pressed[19]; - break; - case OBS_KEY_MOUSE17: - ret = context->pressed[20] || context->button_pressed[20]; - break; - case OBS_KEY_MOUSE18: - ret = context->pressed[21] || context->button_pressed[21]; - break; - case OBS_KEY_MOUSE19: - ret = context->pressed[22] || context->button_pressed[22]; - break; - case OBS_KEY_MOUSE20: - ret = context->pressed[23] || context->button_pressed[23]; - break; - case OBS_KEY_MOUSE21: - ret = context->pressed[24] || context->button_pressed[24]; - break; - case OBS_KEY_MOUSE22: - ret = context->pressed[25] || context->button_pressed[25]; - break; - case OBS_KEY_MOUSE23: - ret = context->pressed[26] || context->button_pressed[26]; - break; - case OBS_KEY_MOUSE24: - ret = context->pressed[27] || context->button_pressed[27]; - break; - case OBS_KEY_MOUSE25: - ret = context->pressed[28] || context->button_pressed[28]; - break; - case OBS_KEY_MOUSE26: - ret = context->pressed[29] || context->button_pressed[29]; - break; - case OBS_KEY_MOUSE27: - ret = context->pressed[30] || context->button_pressed[30]; - break; - case OBS_KEY_MOUSE28: - ret = context->pressed[31] || context->button_pressed[31]; - break; - case OBS_KEY_MOUSE29: - ret = context->pressed[32] || context->button_pressed[32]; - break; - default: - break; - } - - for (int i = 0; i != XINPUT_MOUSE_LEN; i++) - if (context->update[i]) - context->button_pressed[i] = context->pressed[i]; -#else - xcb_generic_error_t *error = NULL; - xcb_query_pointer_cookie_t qpc; - xcb_query_pointer_reply_t *reply; - - qpc = xcb_query_pointer(connection, root_window(context, connection)); - reply = xcb_query_pointer_reply(connection, qpc, &error); - - if (error) { - blog(LOG_WARNING, "xcb_query_pointer_reply failed"); - } else { - uint16_t buttons = reply->mask; - - switch (key) { - case OBS_KEY_MOUSE1: - ret = buttons & XCB_BUTTON_MASK_1; - break; - case OBS_KEY_MOUSE2: - ret = buttons & XCB_BUTTON_MASK_3; - break; - case OBS_KEY_MOUSE3: - ret = buttons & XCB_BUTTON_MASK_2; - break; - default:; - } - } - - free(reply); - free(error); -#endif - return ret; -} - -static inline bool keycode_pressed(xcb_query_keymap_reply_t *reply, - xcb_keycode_t code) -{ - return (reply->keys[code / 8] & (1 << (code % 8))) != 0; -} - -static bool key_pressed(xcb_connection_t *connection, - obs_hotkeys_platform_t *context, obs_key_t key) -{ - struct keycode_list *codes = &context->keycodes[key]; - xcb_generic_error_t *error = NULL; - xcb_query_keymap_reply_t *reply; - bool pressed = false; - - reply = xcb_query_keymap_reply(connection, xcb_query_keymap(connection), - &error); - if (error) { - blog(LOG_WARNING, "xcb_query_keymap failed"); - - } else if (key == OBS_KEY_META) { - pressed = keycode_pressed(reply, context->super_l_code) || - keycode_pressed(reply, context->super_r_code); - - } else { - for (size_t i = 0; i < codes->list.num; i++) { - if (keycode_pressed(reply, codes->list.array[i])) { - pressed = true; - break; - } - } - } - - free(reply); - free(error); - return pressed; + hotkeys_vtable->free(hotkeys); + hotkeys_vtable = NULL; } bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, obs_key_t key) { - xcb_connection_t *conn = XGetXCBConnection(context->display); - - if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { - return mouse_button_pressed(conn, context, key); - } else { - return key_pressed(conn, context, key); - } -} - -static bool get_key_translation(struct dstr *dstr, xcb_keycode_t keycode) -{ - xcb_connection_t *connection; - char name[128]; - - connection = XGetXCBConnection(obs->hotkeys.platform_context->display); - - XKeyEvent event = {0}; - event.type = KeyPress; - event.display = obs->hotkeys.platform_context->display; - event.keycode = keycode; - event.root = root_window(obs->hotkeys.platform_context, connection); - event.window = event.root; - - if (keycode) { - int len = XLookupString(&event, name, 128, NULL, NULL); - if (len) { - dstr_ncopy(dstr, name, len); - dstr_to_upper(dstr); - return true; - } - } - - return false; + return hotkeys_vtable->is_pressed(context, key); } void obs_key_to_str(obs_key_t key, struct dstr *dstr) { - if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { - if (obs->hotkeys.translations[key]) { - dstr_copy(dstr, obs->hotkeys.translations[key]); - } else { - dstr_printf(dstr, "Mouse %d", - (int)(key - OBS_KEY_MOUSE1 + 1)); - } - return; - } - - if (key >= OBS_KEY_NUM0 && key <= OBS_KEY_NUM9) { - if (obs->hotkeys.translations[key]) { - dstr_copy(dstr, obs->hotkeys.translations[key]); - } else { - dstr_printf(dstr, "Numpad %d", - (int)(key - OBS_KEY_NUM0)); - } - return; - } - -#define translate_key(key, def) \ - dstr_copy(dstr, obs_get_hotkey_translation(key, def)) - - switch (key) { - case OBS_KEY_INSERT: - return translate_key(key, "Insert"); - case OBS_KEY_DELETE: - return translate_key(key, "Delete"); - case OBS_KEY_HOME: - return translate_key(key, "Home"); - case OBS_KEY_END: - return translate_key(key, "End"); - case OBS_KEY_PAGEUP: - return translate_key(key, "Page Up"); - case OBS_KEY_PAGEDOWN: - return translate_key(key, "Page Down"); - case OBS_KEY_NUMLOCK: - return translate_key(key, "Num Lock"); - case OBS_KEY_SCROLLLOCK: - return translate_key(key, "Scroll Lock"); - case OBS_KEY_CAPSLOCK: - return translate_key(key, "Caps Lock"); - case OBS_KEY_BACKSPACE: - return translate_key(key, "Backspace"); - case OBS_KEY_TAB: - return translate_key(key, "Tab"); - case OBS_KEY_PRINT: - return translate_key(key, "Print"); - case OBS_KEY_PAUSE: - return translate_key(key, "Pause"); - case OBS_KEY_LEFT: - return translate_key(key, "Left"); - case OBS_KEY_RIGHT: - return translate_key(key, "Right"); - case OBS_KEY_UP: - return translate_key(key, "Up"); - case OBS_KEY_DOWN: - return translate_key(key, "Down"); - case OBS_KEY_SHIFT: - return translate_key(key, "Shift"); - case OBS_KEY_ALT: - return translate_key(key, "Alt"); - case OBS_KEY_CONTROL: - return translate_key(key, "Control"); - case OBS_KEY_META: - return translate_key(key, "Super"); - case OBS_KEY_MENU: - return translate_key(key, "Menu"); - case OBS_KEY_NUMASTERISK: - return translate_key(key, "Numpad *"); - case OBS_KEY_NUMPLUS: - return translate_key(key, "Numpad +"); - case OBS_KEY_NUMCOMMA: - return translate_key(key, "Numpad ,"); - case OBS_KEY_NUMPERIOD: - return translate_key(key, "Numpad ."); - case OBS_KEY_NUMSLASH: - return translate_key(key, "Numpad /"); - case OBS_KEY_SPACE: - return translate_key(key, "Space"); - case OBS_KEY_ESCAPE: - return translate_key(key, "Escape"); - default:; - } - - if (key >= OBS_KEY_F1 && key <= OBS_KEY_F35) { - dstr_printf(dstr, "F%d", (int)(key - OBS_KEY_F1 + 1)); - return; - } - - obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; - struct keycode_list *keycodes = &context->keycodes[key]; - - for (size_t i = 0; i < keycodes->list.num; i++) { - if (get_key_translation(dstr, keycodes->list.array[i])) { - break; - } - } - - if (key != OBS_KEY_NONE && dstr_is_empty(dstr)) { - dstr_copy(dstr, obs_key_to_name(key)); - } -} - -static obs_key_t key_from_keycode(obs_hotkeys_platform_t *context, - xcb_keycode_t code) -{ - for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { - struct keycode_list *codes = &context->keycodes[i]; - - for (size_t j = 0; j < codes->list.num; j++) { - if (codes->list.array[j] == code) { - return (obs_key_t)i; - } - } - } - - return OBS_KEY_NONE; + return hotkeys_vtable->key_to_str(key, dstr); } obs_key_t obs_key_from_virtual_key(int sym) { - obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; - const xcb_keysym_t *keysyms = context->keysyms; - int syms_per_code = context->syms_per_code; - int num_keysyms = context->num_keysyms; - - if (sym == 0) - return OBS_KEY_NONE; - - for (int i = 0; i < num_keysyms; i++) { - if (keysyms[i] == (xcb_keysym_t)sym) { - xcb_keycode_t code = (xcb_keycode_t)(i / syms_per_code); - code += context->min_keycode; - obs_key_t key = key_from_keycode(context, code); - - return key; - } - } - - return OBS_KEY_NONE; + return hotkeys_vtable->key_from_virtual_key(sym); } int obs_key_to_virtual_key(obs_key_t key) { - if (key == OBS_KEY_META) - return XK_Super_L; - - return (int)obs->hotkeys.platform_context->base_keysyms[(int)key]; + return hotkeys_vtable->key_to_virtual_key(key); } static inline void add_combo_key(obs_key_t key, struct dstr *str) diff --git a/libobs/obs-nix.h b/libobs/obs-nix.h new file mode 100644 index 000000000..46eb9f3d3 --- /dev/null +++ b/libobs/obs-nix.h @@ -0,0 +1,42 @@ +/****************************************************************************** + Copyright (C) 2020 by Georges Basile Stavracas Neto + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "obs-internal.h" + +struct obs_nix_hotkeys_vtable { + bool (*init)(struct obs_core_hotkeys *hotkeys); + + void (*free)(struct obs_core_hotkeys *hotkeys); + + bool (*is_pressed)(obs_hotkeys_platform_t *context, obs_key_t key); + + void (*key_to_str)(obs_key_t key, struct dstr *dstr); + + obs_key_t (*key_from_virtual_key)(int sym); + + int (*key_to_virtual_key)(obs_key_t key); +}; + +#ifdef __cplusplus +} +#endif