Revision control
++ // Copyright (C) 2019 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtVirtualKeyboard>
++ #include <QApplication>
++ #include <QLoggingCategory>
++
++ #include "handlekeyevents.h"
++ #include "vkbhidetimer.h"
++ #include "xcbkeyboard.h"
++ #include "keysymmapsforfakeinput.h"
++
++ extern "C" {
++ #include <xdo.h>
++ #include <X11/extensions/XTest.h>
++ #include "atspi/atspi.h"
++ }
++
++ namespace {
++ const bool KKeyPress = true;
++ const bool KKeyRelease = false;
++ const int NoKeySymFound = 63;
++
++ // Undefine define KeyRelease from X.h.
++ // Enables using QEvent::KeyRelease type in eventFilter.
++ #ifdef KeyRelease
++ #undef KeyRelease
++ #endif
++ }
++
++ Q_LOGGING_CATEGORY(lcHandleKeyEvents, "qt.virtualkeyboard.tests.manual.x11vkbwrapper.handlekeyevents")
++
++ /**
++ * @brief qtKeyToXKeySym
++ * Mapping a Qt::Key to the KeySym.
++ * @param key a Pressed virtual key
++ * @param upper True if a Shift in use
++ * @return
++ */
++ KeySym qtKeyToXKeySym(Qt::Key key, bool upper)
++ {
++ const auto keySym = XStringToKeysym(QKeySequence(key).toString().toUtf8().data());
++ KeySym keyS = upper ? keySym : static_cast<unsigned long>(tolower(static_cast<int>(keySym)));
++ if (keyS != NoSymbol) {
++ return keyS;
++ }
++
++ for (int i = 0; KeyTbl[i] != 0; i += 2) {
++ if (KeyTbl[i + 1] == key) {
++ return KeyTbl[i];
++ }
++ }
++ return static_cast<ushort>(key);
++ }
++
++ /**
++ * @brief HandleKeyEvents::HandleKeyEvents
++ * Handling a key release events.
++ * @param parent
++ */
++ HandleKeyEvents::HandleKeyEvents(QObject *parent) : QObject(parent),
++ m_xdo(nullptr)
++ {
++
++ }
++
++ /**
++ * @brief HandleKeyEvents::~HandleKeyEvents
++ */
++ HandleKeyEvents::~HandleKeyEvents()
++ {
++ qCDebug(lcHandleKeyEvents) << Q_FUNC_INFO;
++ xdo_free(m_xdo);
++ }
++
++ /**
++ * @brief HandleKeyEvents::init
++ * @return false if xdo_new fail
++ */
++ bool HandleKeyEvents::init()
++
++ {
++ m_temporaryKeyCodes = QHash<Qt::Key, int>();
++ m_xdo = xdo_new(nullptr);
++ return m_xdo != nullptr;
++ }
++
++ /**
++ * @brief HandleKeyEvents::eventFilter
++ * Catch the QEvent::KeyRelease event.
++ * @param watched
++ * @param event a QEvent::KeyRelease
++ * @return
++ */
++ bool HandleKeyEvents::eventFilter(QObject *watched, QEvent *event)
++ {
++ Q_UNUSED( watched );
++
++ const auto type = event->type();
++ if (type == QEvent::MouseButtonRelease && !QGuiApplication::inputMethod()->isVisible()) {
++ // Start the VKB hide timer when the VKB indicator image is pressed.
++ VkbHideTimer::getInstance()->startTimer();
++ }
++ if (type == QEvent::MouseMove) {
++ // Start/re-start the VKB hide timer when there's mouse move event
++ // anywhere on the virtual keyboard surface.
++ VkbHideTimer::getInstance()->startTimer();
++ }
++ if (type == QEvent::MouseButtonPress) {
++ // Start/re-start the VKB hide timer when there's button press event
++ // anywhere on the virtual keyboard surface.
++ VkbHideTimer::getInstance()->startTimer();
++ }
++ if (type == QEvent::KeyRelease) {
++ if (m_xdo == nullptr) {
++ qWarning() << Q_FUNC_INFO << "xdo_t instance is not valid, shutting down...";
++ QApplication::exit(1);
++ }
++ // Start/re-start the VKB hide timer when the VKB keys are pressed.
++ VkbHideTimer::getInstance()->startTimer();
++
++ auto keyEvent = static_cast<QKeyEvent *>(event);
++ bool shiftOn = keyEvent->modifiers() == Qt::ShiftModifier;
++ auto key = static_cast<Qt::Key>(keyEvent->key());
++ QString str = keyEvent->text();
++ /** a Qt special keys. */
++ if (qtKeyCodes.indexOf(key) >= 0) {
++ xdo_send_keysequence_window(m_xdo, CURRENTWINDOW,
++ XKeysymToString(qtKeyToXKeySym(key, shiftOn)),
++ 2000);
++ /** a Key strings as Emojis ":-), <3". */
++ } else if (str.length() > 1) {
++ for (auto sKey : str) {
++ sendKeyWithAtspi(nullptr, sKey);
++ }
++ /** a Normal Keys. */
++ } else if (key != Qt::Key_Shift) {
++ sendKeyWithAtspi(keyEvent, QString(""));
++ }
++ }
++ return false;
++ }
++
++ /**
++ * @brief HandleKeyEvents::keyTap
++ * @param keyEvent a QKeyEvent pointer
++ * @param key a Key as a string
++ */
++ void HandleKeyEvents::keyTap(const QKeyEvent *keyEvent, const QString &key)
++ {
++ qCDebug(lcHandleKeyEvents) << Q_FUNC_INFO;
++
++ /** Scratch space for temporary keycode bindings */
++ int scratchKeyCode = 0;
++ KeyCode keyCode = 0x0;
++
++ if (keyEvent) {
++ auto qtKey = static_cast<Qt::Key>(keyEvent->key());
++ if (m_temporaryKeyCodes.contains(qtKey)) {
++ scratchKeyCode = m_temporaryKeyCodes.value(qtKey);
++ } else {
++ scratchKeyCode = getTemporaryKeyCode();
++ m_temporaryKeyCodes.insert(qtKey, scratchKeyCode);
++ }
++
++ /** find the keysym for the given unicode char */
++ const QString str = qtKeyCodes2.indexOf(qtKey) >= 0 ? "U"+ QString::number(keyEvent->key(), 16 ) :
++ "U"+ keyEvent->text().toLatin1().toHex();
++ const KeySym sym = XStringToKeysym(str.toUtf8().data());
++ if (sym && sym != NoKeySymFound) {
++ remapScratchKeyCode(sym, scratchKeyCode);
++ keyCode = static_cast<KeyCode>(scratchKeyCode);
++ } else {
++ keyCode = getUnicodeKeyCode(keyEvent->text(), scratchKeyCode);
++ }
++ keyClick(keyCode, keyEvent->text());
++ } else {
++ keyCode = getUnicodeKeyCode(key, getTemporaryKeyCode());
++ keyClick(keyCode, key);
++ }
++
++ /** Revert Keymapping */
++ remapScratchKeyCode(NoSymbol, scratchKeyCode);
++ }
++
++ /**
++ * @brief HandleKeyEvents::keyClick
++ * @param key
++ * @param keyText Key as a string
++ */
++ void HandleKeyEvents::keyClick(const KeyCode key, const QString &keyText) const
++ {
++ qCDebug(lcHandleKeyEvents) << Q_FUNC_INFO;
++ KeyCode shift = XKeysymToKeycode(m_xdo->xdpy, XK_Shift_L);
++ bool shiftDown = false;
++
++ /** Press a Shift button down if capital letter. */
++ if (keyText.length() == 1 && (keyText.toLower() != keyText || xUpKeyCodes.count(keyText))) {
++ keyPressRelease(shift, KKeyPress);
++ shiftDown = true;
++ }
++
++ /** A Key press */
++ keyPressRelease(key, KKeyPress);
++ /** A Key release */
++ keyPressRelease(key, KKeyRelease);
++
++ /** Release a Shift button if capital letter. */
++ if (shiftDown) {
++ keyPressRelease(shift, KKeyRelease);
++ }
++ }
++
++ /**
++ * @brief HandleKeyEvents::keyPressRelease
++ * Press / Release a key
++ * @param key a Key as a string
++ * @param eventType true when press a key
++ */
++ void HandleKeyEvents::keyPressRelease(const KeyCode key, const bool eventType) const
++ {
++ qCDebug(lcHandleKeyEvents) << Q_FUNC_INFO;
++ XTestFakeKeyEvent(m_xdo->xdpy, key, eventType ? 1 : 0, 0);
++ XFlush(m_xdo->xdpy);
++ }
++
++ /**
++ * @brief HandleKeyEvents::getUnicodeKeyCode
++ * Get a correct key mapping for a key.
++ * @param key a Key as a string
++ * @return Keycode
++ */
++ KeyCode HandleKeyEvents::getUnicodeKeyCode(const QString &key, int scratchKeyCode) const
++ {
++ qCDebug(lcHandleKeyEvents) << Q_FUNC_INFO;
++ KeyCode code = 0;
++ if (xKeyCodes.count(key)) {
++ auto xKeyIter = xKeyCodes.find(key);
++ code = XKeysymToKeycode(m_xdo->xdpy, xKeyIter->second);
++ } else if (xUpKeyCodes.count(key)) {
++ auto xUpKeyIter = xKeyCodes.find(key);
++ code = XKeysymToKeycode(m_xdo->xdpy, xUpKeyIter->second);
++ } else {
++ const KeySym sym = unicodeKeySymbols.find(key)->second;
++ remapScratchKeyCode(sym, scratchKeyCode);
++ code = static_cast<KeyCode>(scratchKeyCode);
++ }
++ return code;
++ }
++
++ /**
++ * @brief HandleKeyEvents::remapScratchKeyCode
++ * Remap the requested KeySym
++ * @param sym KeySymbol
++ * @param scratchKeyCode unused keycode to use for remapping
++ */
++ void HandleKeyEvents::remapScratchKeyCode(const KeySym sym, int scratchKeyCode) const
++ {
++ qCDebug(lcHandleKeyEvents) << Q_FUNC_INFO;
++ KeySym keysyms[] = {sym, sym};
++ /** Remap */
++ XChangeKeyboardMapping(m_xdo->xdpy, scratchKeyCode, 2, keysyms, 1);
++ }
++
++ /**
++ * @brief HandleKeyEvents::sendKeyWithAtspi
++ * To send a ordinary keys via atspi(D-Bus). It is faster than a XTestFakeKeyEvent
++ * @param keyEvent KeyEvent
++ * @param key Key as string
++ */
++ void HandleKeyEvents::sendKeyWithAtspi(const QKeyEvent *keyEvent, const QString key)
++ {
++ qCDebug(lcHandleKeyEvents) << Q_FUNC_INFO;
++ const QString str = keyEvent != nullptr ? keyEvent->text() : key;
++ if (str.isEmpty()) {
++ return;
++ }
++
++ const KeySym sym = unicodeKeySymbolsForAtspi.find(str)->second;
++ if (sym == 0) {
++ keyTap(keyEvent, QString(""));
++ } else {
++ GError *error = nullptr;
++ if (!atspi_generate_keyboard_event(static_cast<long>(sym), nullptr, ATSPI_KEY_SYM, &error)) {
++ qCDebug(lcHandleKeyEvents) << "Error message: " << error->message;
++ }
++ }
++ }
++
++ /**
++ * @brief HandleKeyEvents::getTemporaryKeyCode
++ * @return
++ */
++ int HandleKeyEvents::getTemporaryKeyCode()
++ {
++ qCDebug(lcHandleKeyEvents) << Q_FUNC_INFO;
++ KeySym *keySyms = nullptr;
++ int keySymsPerKeyCode = 0;
++
++ /** Scratch space for temporary keycode bindings */
++ int scratchKeyCode = 0;
++ int keyCodeLow = 0;
++ int keyCodeHigh = 0;
++
++ /** get the range of keycodes usually from 8 - 255 */
++ XDisplayKeycodes(m_xdo->xdpy, &keyCodeLow, &keyCodeHigh);
++ /** get all the mapped keysyms availabl */
++ const KeyCode keyCodeL = static_cast<KeyCode>(keyCodeLow);
++ keySyms = XGetKeyboardMapping(
++ m_xdo->xdpy,
++ keyCodeL,
++ keyCodeHigh - keyCodeLow,
++ &keySymsPerKeyCode);
++
++ /** find unused keycode for unmapped keysyms so we can
++ hook up our own keycode and map every keysym on it
++ so we just need to 'click' our once unmapped keycode */
++ int i;
++ for (i = keyCodeLow; i <= keyCodeHigh; i++) {
++ int keyIsEmpty = 1;
++ for (int j = 0; j < keySymsPerKeyCode; j++) {
++ const int symindex = (i - keyCodeLow) * keySymsPerKeyCode + j;
++ if (keySyms[symindex] != 0) {
++ keyIsEmpty = 0;
++ } else {
++ break;
++ }
++ }
++ if (keyIsEmpty) {
++ scratchKeyCode = i;
++ break;
++ }
++ }
++ XFree(keySyms);
++ XFlush(m_xdo->xdpy);
++
++ return scratchKeyCode;
++ }
++