From fa8912d9e82334714646eb3e23047fb5f00026f6 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Fri, 6 Mar 2020 17:24:33 -0300 Subject: [PATCH 07/25] libobs/nix: Move X11-specific code to obs-nix-x11.c Currently, obs-nix.c is highly tied to the X11 display server. It includes X11 headers directly, and make use of X11 functions. Most of the code inside obs-nix.c that is X11-specific is related to hotkeys handling. Introduce a new vtable for hotkeys callbacks, that will used by X11 and Wayland to expose their specific routines. In this commit, only the X11 hotkeys vtable is implemented. Move all the X11-specific code to obs-nix-x11.c, and add a new function to retrieve the X11 hotkeys vtable. --- libobs/CMakeLists.txt | 1 + libobs/obs-nix-x11.c | 1270 +++++++++++++++++++++++++++++++++++++++++ libobs/obs-nix-x11.h | 22 + libobs/obs-nix.c | 1224 +-------------------------------------- libobs/obs-nix.h | 42 ++ 5 files changed, 1348 insertions(+), 1211 deletions(-) create mode 100644 libobs/obs-nix-x11.c create mode 100644 libobs/obs-nix-x11.h create mode 100644 libobs/obs-nix.h diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 3702875e..e1765034 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -181,6 +181,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 00000000..29aa3c7f --- /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 00000000..34b9dd0f --- /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 de039c15..df1d99df 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 @@ -28,15 +30,6 @@ #endif #include #include -#include -#if USE_XINPUT -#include -#endif -#include -#include -#include -#include -#include #include const char *get_module_extension(void) @@ -62,6 +55,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++) @@ -238,37 +233,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) { @@ -325,1203 +289,41 @@ void log_system_info(void) #if defined(__linux__) log_distribution_info(); #endif - log_x_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]); - } + obs_nix_x11_log_info(); } -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 00000000..46eb9f3d --- /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 -- 2.28.0