From 05b3cd16078497a326bb604d475289f1719dc96d Mon Sep 17 00:00:00 2001 From: RichardG867 Date: Fri, 7 Apr 2023 22:33:53 -0300 Subject: [PATCH] qt: Initial xkbcommon keyboard support for X11 --- .ci/build.sh | 4 +- .ci/static2dll.sh | 0 src/qt/CMakeLists.txt | 15 ++ src/qt/qt_mainwindow.cpp | 57 ++++--- src/qt/xkbcommon_keyboard.cpp | 258 ++++++++++++++++++++++++++++++ src/qt/xkbcommon_keyboard.hpp | 19 +++ src/qt/xkbcommon_x11_keyboard.cpp | 85 ++++++++++ src/qt/xkbcommon_x11_keyboard.hpp | 17 ++ 8 files changed, 432 insertions(+), 23 deletions(-) mode change 100755 => 100644 .ci/build.sh mode change 100755 => 100644 .ci/static2dll.sh create mode 100644 src/qt/xkbcommon_keyboard.cpp create mode 100644 src/qt/xkbcommon_keyboard.hpp create mode 100644 src/qt/xkbcommon_x11_keyboard.cpp create mode 100644 src/qt/xkbcommon_x11_keyboard.hpp diff --git a/.ci/build.sh b/.ci/build.sh old mode 100755 new mode 100644 index 9b4494c46..67320c2e2 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -584,7 +584,7 @@ else # ...and the ones we do want listed. Non-dev packages fill missing spots on the list. libpkgs="" longest_libpkg=0 - for pkg in libc6-dev libstdc++6 libopenal-dev libfreetype6-dev libx11-dev libsdl2-dev libpng-dev librtmidi-dev qtdeclarative5-dev libwayland-dev libevdev-dev libglib2.0-dev libslirp-dev libfaudio-dev libaudio-dev libjack-jackd2-dev libpipewire-0.3-dev libsamplerate0-dev libsndio-dev + for pkg in libc6-dev libstdc++6 libopenal-dev libfreetype6-dev libx11-dev libsdl2-dev libpng-dev librtmidi-dev qtdeclarative5-dev libwayland-dev libevdev-dev libxkbcommon-x11-dev libglib2.0-dev libslirp-dev libfaudio-dev libaudio-dev libjack-jackd2-dev libpipewire-0.3-dev libsamplerate0-dev libsndio-dev do libpkgs="$libpkgs $pkg:$arch_deb" length=$(echo -n $pkg | sed 's/-dev$//' | sed "s/qtdeclarative/qt/" | wc -c) @@ -1014,7 +1014,7 @@ else mkdir -p "$icon_dir" cp -rp "$icon_size" "$icon_dir/apps" done - project_icon=$(ls "$icon_base/"[0-9]*x[0-9]*/* | head -1 | grep -oP '/\K([^/]+)(?=\.[^\.]+$)') + project_icon=$(find "$icon_base/"[0-9]*x[0-9]*/* -type f -name '*.png' -o -name '*.svg' | head -1 | grep -oP '/\K([^/]+)(?=\.[^\.]+$)') # Archive executable, while also stripping it if requested. mkdir -p archive_tmp/usr/local/bin diff --git a/.ci/static2dll.sh b/.ci/static2dll.sh old mode 100755 new mode 100644 diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 1cb26a239..0cce392a9 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -373,6 +373,21 @@ if (UNIX AND NOT APPLE AND NOT HAIKU) target_link_libraries(ui PUBLIC PkgConfig::LIBEVDEV) target_sources(ui PRIVATE evdev_mouse.cpp) endif() + pkg_check_modules(XKBCOMMON IMPORTED_TARGET xkbcommon) + if (XKBCOMMON_FOUND) + target_compile_definitions(ui PRIVATE XKBCOMMON) + target_link_libraries(ui PUBLIC PkgConfig::XKBCOMMON) + target_sources(ui PRIVATE xkbcommon_keyboard.cpp) + + if (X11_xcb_FOUND) + pkg_check_modules(XKBCOMMON_X11 IMPORTED_TARGET xkbcommon-x11) + if (XKBCOMMON_X11_FOUND) + target_compile_definitions(ui PRIVATE XKBCOMMON_X11) + target_link_libraries(ui PRIVATE X11::xcb PUBLIC PkgConfig::XKBCOMMON_X11) + target_sources(ui PRIVATE xkbcommon_x11_keyboard.cpp) + endif() + endif() + endif() find_package(ECM NO_MODULE) if (ECM_FOUND) diff --git a/src/qt/qt_mainwindow.cpp b/src/qt/qt_mainwindow.cpp index f360a83e9..de7bceafb 100644 --- a/src/qt/qt_mainwindow.cpp +++ b/src/qt/qt_mainwindow.cpp @@ -96,8 +96,11 @@ extern int qt_nvr_save(void); #include "qt_util.hpp" #if defined __unix__ && !defined __HAIKU__ -# ifdef WAYLAND -# include "wl_mouse.hpp" +# ifdef XKBCOMMON +# include "xkbcommon_keyboard.hpp" +# ifdef XKBCOMMON_X11 +# include "xkbcommon_x11_keyboard.hpp" +# endif # endif # include # include @@ -648,6 +651,11 @@ MainWindow::MainWindow(QWidget *parent) } else { ui->actionCursor_Puck->setChecked(true); } + +#ifdef XKBCOMMON_X11 + if (QApplication::platformName().contains("xcb")) + xkbcommon_x11_init(); +#endif } void @@ -1504,29 +1512,36 @@ x11_keycode_to_keysym(uint32_t keycode) #elif defined(__HAIKU__) finalkeycode = be_to_xt[keycode]; #else - static Display *x11display = nullptr; - if (QApplication::platformName().contains("wayland")) { - selected_keycode = x11_to_xt_2; - } else if (QApplication::platformName().contains("eglfs")) { - keycode -= 8; - if (keycode <= 88) - finalkeycode = keycode; - else - finalkeycode = evdev_to_xt[keycode]; - } else if (!x11display) { - x11display = XOpenDisplay(nullptr); - if (XKeysymToKeycode(x11display, XK_Home) == 110) { - selected_keycode = x11_to_xt_2; - } else if (XKeysymToKeycode(x11display, XK_Home) == 69) { - selected_keycode = x11_to_xt_vnc; +# ifdef XKBCOMMON + if (xkbcommon_keymap) { + finalkeycode = xkbcommon_translate(keycode); + } else +# endif + { + static Display *x11display = nullptr; + if (QApplication::platformName().contains("eglfs")) { + keycode -= 8; + if (keycode <= 88) + finalkeycode = keycode; + else + finalkeycode = evdev_to_xt[keycode]; + } else { + if (QApplication::platformName().contains("wayland")) { + selected_keycode = x11_to_xt_2; + } else if (!x11display) { + x11display = XOpenDisplay(nullptr); + if (XKeysymToKeycode(x11display, XK_Home) == 110) { + selected_keycode = x11_to_xt_2; + } else if (XKeysymToKeycode(x11display, XK_Home) == 69) { + selected_keycode = x11_to_xt_vnc; + } + } + finalkeycode = selected_keycode[keycode]; } } - if (!QApplication::platformName().contains("eglfs")) - finalkeycode = selected_keycode[keycode]; #endif - if (rctrl_is_lalt && finalkeycode == 0x11D) { + if (rctrl_is_lalt && finalkeycode == 0x11D) finalkeycode = 0x38; - } return finalkeycode; } diff --git a/src/qt/xkbcommon_keyboard.cpp b/src/qt/xkbcommon_keyboard.cpp new file mode 100644 index 000000000..1b4998b1f --- /dev/null +++ b/src/qt/xkbcommon_keyboard.cpp @@ -0,0 +1,258 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * xkbcommon keyboard input module. + * + * + * + * Authors: RichardG, + * + * Copyright 2023 RichardG. + */ +extern "C" { +#include +}; + +#include +#include + +#define IS_HEX_DIGIT(c) ((((c) >= '0') && ((c) <= '9')) || (((c) >= 'A') && ((c) <= 'F')) || (((c) >= 'a') && ((c) <= 'f'))) + +std::unordered_map xkb_keycodes{ + {"ESC", 0x01}, + {"AE01", 0x02}, + {"AE02", 0x03}, + {"AE03", 0x04}, + {"AE04", 0x05}, + {"AE05", 0x06}, + {"AE06", 0x07}, + {"AE07", 0x08}, + {"AE08", 0x09}, + {"AE09", 0x0a}, + {"AE10", 0x0b}, + {"AE11", 0x0c}, + {"AE12", 0x0d}, + {"BKSP", 0x0e}, + + {"TAB", 0x0f}, + {"AD01", 0x10}, + {"AD02", 0x11}, + {"AD03", 0x12}, + {"AD04", 0x13}, + {"AD05", 0x14}, + {"AD06", 0x15}, + {"AD07", 0x16}, + {"AD08", 0x17}, + {"AD09", 0x18}, + {"AD10", 0x19}, + {"AD11", 0x1a}, + {"AD12", 0x1b}, + {"RTRN", 0x1c}, + + {"LCTL", 0x1d}, + {"AC01", 0x1e}, + {"AC02", 0x1f}, + {"AC03", 0x20}, + {"AC04", 0x21}, + {"AC05", 0x22}, + {"AC06", 0x23}, + {"AC07", 0x24}, + {"AC08", 0x25}, + {"AC09", 0x26}, + {"AC10", 0x27}, + {"AC11", 0x28}, + + {"TLDE", 0x29}, + {"LFSH", 0x2a}, + {"BKSL", 0x2b}, + {"AB01", 0x2c}, + {"AB02", 0x2d}, + {"AB03", 0x2e}, + {"AB04", 0x2f}, + {"AB05", 0x30}, + {"AB06", 0x31}, + {"AB07", 0x32}, + {"AB08", 0x33}, + {"AB09", 0x34}, + {"AB10", 0x35}, + {"RTSH", 0x36}, + + {"KPMU", 0x37}, + {"LALT", 0x38}, + {"SPCE", 0x39}, + {"CAPS", 0x3a}, + {"FK01", 0x3b}, + {"FK02", 0x3c}, + {"FK03", 0x3d}, + {"FK04", 0x3e}, + {"FK05", 0x3f}, + {"FK06", 0x40}, + {"FK07", 0x41}, + {"FK08", 0x42}, + {"FK09", 0x43}, + {"FK10", 0x44}, + + {"NMLK", 0x45}, + {"SCLK", 0x46}, + {"FK14", 0x46}, /* F14 as Scroll Lock */ + {"KP7", 0x47}, + {"KP8", 0x48}, + {"KP9", 0x49}, + {"KPSU", 0x4a}, + {"KP4", 0x4b}, + {"KP5", 0x4c}, + {"KP6", 0x4d}, + {"KPAD", 0x4e}, + {"KP1", 0x4f}, + {"KP2", 0x50}, + {"KP3", 0x51}, + {"KP0", 0x52}, + {"KPDL", 0x53}, + + {"LSGT", 0x56}, + {"FK11", 0x57}, + {"FK12", 0x58}, + + /* Japanese keys. */ + {"HKTG", 0x70}, /* hiragana-katakana toggle... */ + {"HIRA", 0x70}, /* ...and individual keys */ + {"KATA", 0x70}, + {"AB11", 0x73}, /* \_ and Brazilian /? */ + {"HENK", 0x79}, + {"MUHE", 0x7b}, + {"AE13", 0x7d}, /* \| */ + {"KPPT", 0x7e}, /* Brazilian Num. */ + {"I06", 0x7e}, /* alias of KPPT on keycodes/xfree86 (i.e. X11 forwarding) */ + + /* Korean keys. */ + {"HJCV", 0xf1}, /* hancha toggle */ + {"HNGL", 0xf2}, /* latin toggle */ + + {"KPEN", 0x11c}, + {"RCTL", 0x11d}, + {"KPDV", 0x135}, + {"PRSC", 0x137}, + {"SYRQ", 0x137}, + {"FK13", 0x137}, /* F13 as SysRq */ + {"RALT", 0x138}, + {"PAUS", 0x146}, /* special case */ + {"FK15", 0x146}, /* F15 as Pause */ + {"HOME", 0x147}, + {"UP", 0x148}, + {"PGUP", 0x149}, + {"LEFT", 0x14b}, + {"RGHT", 0x14d}, + {"END", 0x14f}, + {"DOWN", 0x150}, + {"PGDN", 0x151}, + {"INS", 0x152}, + {"DELE", 0x153}, + + {"LWIN", 0x15b}, + {"RWIN", 0x15c}, + {"COMP", 0x15d}, + + /* Multimedia keys, using Linux evdev-specific keycodes where required. Guideline is to try + and follow the Microsoft standard, then fill in some OEM-specific keys for redundancy sake. + Keys marked with # are not translated into evdev codes by the standard atkbd driver. */ + {"KPEQ", 0x59}, /* Num= */ + {"FRNT", 0x101}, /* # Logitech Task Select */ + {"I224", 0x105}, /* CHAT# => Messenger/Files */ + {"I190", 0x107}, /* REDO */ + {"UNDO", 0x108}, + {"PAST", 0x10a}, /* # Paste */ + {"I185", 0x10b}, /* SCROLLUP# => normal speed */ + {"I173", 0x110}, /* PREVIOUSSONG */ + {"FIND", 0x112}, /* # Logitech */ + {"I156", 0x113}, /* PROG1# => Word */ + {"I157", 0x114}, /* PROG2# => Excel */ + {"I210", 0x115}, /* PROG3# => Calendar */ + {"I182", 0x116}, /* EXIT# => Log Off */ + {"CUT", 0x117}, + {"COPY", 0x118}, + {"I171", 0x119}, /* NEXTSONG */ + {"I162", 0x11e}, /* CYCLEWINDOWS => Application Right (no left counterpart) */ + {"MUTE", 0x120}, + {"I148", 0x121}, /* CALC */ + {"I172", 0x122}, /* PLAYPAUSE */ + {"I158", 0x123}, /* WWW# => Compaq online start */ + {"I174", 0x124}, /* STOPCD */ + {"I147", 0x126}, /* MENU# => Shortcut/Menu/Help for a few OEMs */ + {"VOL-", 0x12e}, + {"I168", 0x12f}, /* CLOSECD# => Logitech Eject */ + {"I169", 0x12f}, /* EJECTCD# => Logitech */ + {"I170", 0x12f}, /* EJECTCLOSECD# => Logitech */ + {"VOL+", 0x130}, + {"I180", 0x132}, /* HOMEPAGE */ + {"HELP", 0x13b}, /* # */ + {"I221", 0x13c}, /* SOUND# => My Music */ + {"I212", 0x13d}, /* DASHBOARD# => Task Pane */ + {"I189", 0x13e}, /* NEW# */ + {"OPEN", 0x13f}, /* # */ + {"I214", 0x140}, /* CLOSE# */ + {"I240", 0x141}, /* REPLY# */ + {"I241", 0x142}, /* FORWARDMAIL# */ + {"I239", 0x143}, /* SEND# */ + {"I159", 0x144}, /* MSDOS# */ + {"I120", 0x14c}, /* MACRO */ + {"I187", 0x14c}, /* KPLEFTPAREN# */ + {"I243", 0x155}, /* DOCUMENTS# => Logitech */ + {"I242", 0x157}, /* SAVE# */ + {"I218", 0x158}, /* PRINT# */ + {"POWR", 0x15e}, + {"I150", 0x15f}, /* SLEEP */ + {"I151", 0x163}, /* WAKEUP */ + {"I188", 0x164}, /* KPRIGHTPAREN# */ + {"I220", 0x164}, /* CAMERA# => My Pictures */ + {"I225", 0x165}, /* SEARCH */ + {"I164", 0x166}, /* BOOKMARKS => Favorites */ + {"I181", 0x167}, /* REFRESH */ + {"STOP", 0x168}, + {"I167", 0x169}, /* FORWARD */ + {"I166", 0x16a}, /* BACK */ + {"I165", 0x16b}, /* COMPUTER */ + {"I163", 0x16c}, /* MAIL */ + {"I223", 0x16c}, /* EMAIL# */ + {"I234", 0x16d}, /* MEDIA */ + {"I175", 0x178}, /* RECORD# => Logitech */ + {"I160", 0x17a}, /* COFFEE# */ + {"I186", 0x18b}, /* SCROLLDOWN# => normal speed */ +}; +struct xkb_keymap *xkbcommon_keymap = nullptr; + +void +xkbcommon_init(struct xkb_keymap *keymap) +{ + xkbcommon_keymap = keymap; +} + +uint16_t +xkbcommon_translate(uint32_t keycode) +{ + const char *key_name = xkb_keymap_key_get_name(xkbcommon_keymap, keycode); + if (!key_name) { + qWarning() << "XKB Keyboard: Unknown keycode" << Qt::hex << keycode; + return 0; + } + + std::string key_name_s(key_name); + uint16_t ret = xkb_keycodes[key_name_s]; + + /* Observed with multimedia keys on a Windows X11 client. */ + if (!ret && (key_name_s.length() == 3) && (key_name_s[0] == 'I') && IS_HEX_DIGIT(key_name_s[1]) && IS_HEX_DIGIT(key_name_s[2])) + ret = 0x100 | stoi(key_name_s.substr(1), nullptr, 16); + + if (!ret) + qWarning() << "XKB Keyboard: Unknown key" << Qt::hex << keycode << "/" << QString::fromStdString(key_name_s); +#if 0 + else + qInfo() << "XKB Keyboard: Key" << Qt::hex << keycode << "/" << QString::fromStdString(key_name_s) << "scancode" << Qt::hex << ret; +#endif + + return ret; +} diff --git a/src/qt/xkbcommon_keyboard.hpp b/src/qt/xkbcommon_keyboard.hpp new file mode 100644 index 000000000..afcba41bb --- /dev/null +++ b/src/qt/xkbcommon_keyboard.hpp @@ -0,0 +1,19 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Definitions for xkbcommon keyboard input module. + * + * + * + * Authors: RichardG, + * + * Copyright 2023 RichardG. + */ +extern void *xkbcommon_keymap; +void xkbcommon_init(struct xkb_keymap *keymap); +uint16_t xkbcommon_translate(uint32_t keycode); diff --git a/src/qt/xkbcommon_x11_keyboard.cpp b/src/qt/xkbcommon_x11_keyboard.cpp new file mode 100644 index 000000000..b9c16cc9b --- /dev/null +++ b/src/qt/xkbcommon_x11_keyboard.cpp @@ -0,0 +1,85 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * xkbcommon-x11 keyboard input module. + * + * Heavily inspired by libxkbcommon interactive-x11.c + * + * + * + * Authors: RichardG, + * + * Copyright 2023 RichardG. + */ +extern "C" { +/* xkb.h has identifiers named "explicit", which is a C++ keyword now... */ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wkeyword-macro" +#endif +#define explicit explicit_ +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#include +#undef explicit + +#include +}; +#include "xkbcommon_keyboard.hpp" + +#include + +void +xkbcommon_x11_init() +{ + xcb_connection_t *conn; + struct xkb_context *ctx; + int32_t core_kbd_device_id; + struct xkb_keymap *keymap; + + conn = xcb_connect(NULL, NULL); + if (!conn || xcb_connection_has_error(conn)) { + qWarning() << "XKB Keyboard: X server connection failed with error" << (conn ? xcb_connection_has_error(conn) : -1); + return; + } + + int ret = xkb_x11_setup_xkb_extension(conn, + XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION, + XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, + NULL, NULL, NULL, NULL); + if (!ret) { + qWarning() << "XKB Keyboard: XKB extension setup failed"; + goto err_conn; + } + + ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!ctx) { + qWarning() << "XKB Keyboard: XKB context creation failed"; + goto err_conn; + } + + core_kbd_device_id = xkb_x11_get_core_keyboard_device_id(conn); + if (core_kbd_device_id == -1) { + qWarning() << "XKB Keyboard: Core keyboard device not found"; + goto err_ctx; + } + + keymap = xkb_x11_keymap_new_from_device(ctx, conn, core_kbd_device_id, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + qWarning() << "XKB Keyboard: Keymap loading failed"; + goto err_ctx; + } + + xkbcommon_init(keymap); + +err_ctx: + xkb_context_unref(ctx); +err_conn: + xcb_disconnect(conn); +} diff --git a/src/qt/xkbcommon_x11_keyboard.hpp b/src/qt/xkbcommon_x11_keyboard.hpp new file mode 100644 index 000000000..d8c063acb --- /dev/null +++ b/src/qt/xkbcommon_x11_keyboard.hpp @@ -0,0 +1,17 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Definitions for xkbcommon-x11 keyboard input module. + * + * + * + * Authors: RichardG, + * + * Copyright 2023 RichardG. + */ +void xkbcommon_x11_init();