Files
pilotclient/src/blackinput/linux/keyboardlinux.cpp
Mat Sutcliffe b46d4c7bb9 Add more keyboard key codes assignable to hotkeys
F1-F12, CapsLock, Home, End, PgUp, PgDn, and some more numpad keys.
2021-11-13 01:13:52 +00:00

317 lines
9.7 KiB
C++

/* Copyright (C) 2013
* swift Project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated,
* or distributed except according to the terms contained in the LICENSE file.
*/
#include "keyboardlinux.h"
#include "blackmisc/logmessage.h"
#include <QDebug>
#include <QHash>
#include <QSocketNotifier>
#include <linux/input.h>
#include <fcntl.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
using namespace BlackMisc;
using namespace BlackMisc::Input;
namespace BlackInput
{
// https://www.cl.cam.ac.uk/~mgk25/ucs/keysymdef.h
static QHash<int, Input::KeyCode> keyMapping
{
{ XK_0, Key_0 },
{ XK_1, Key_1 },
{ XK_2, Key_2 },
{ XK_3, Key_3 },
{ XK_4, Key_4 },
{ XK_5, Key_5 },
{ XK_6, Key_6 },
{ XK_7, Key_7 },
{ XK_8, Key_8 },
{ XK_9, Key_9 },
{ XK_a, Key_A },
{ XK_b, Key_B },
{ XK_c, Key_C },
{ XK_d, Key_D },
{ XK_e, Key_E },
{ XK_f, Key_F },
{ XK_g, Key_G },
{ XK_h, Key_H },
{ XK_i, Key_I },
{ XK_j, Key_J },
{ XK_k, Key_K },
{ XK_l, Key_L },
{ XK_m, Key_M },
{ XK_n, Key_N },
{ XK_o, Key_O },
{ XK_p, Key_P },
{ XK_q, Key_Q },
{ XK_r, Key_R },
{ XK_s, Key_S },
{ XK_t, Key_T },
{ XK_u, Key_U },
{ XK_v, Key_V },
{ XK_w, Key_W },
{ XK_x, Key_X },
{ XK_y, Key_Y },
{ XK_z, Key_Z },
{ XK_Shift_L, Key_ShiftLeft },
{ XK_Shift_R, Key_ShiftRight },
{ XK_Control_L, Key_ControlLeft },
{ XK_Control_R, Key_ControlRight },
{ XK_Alt_L, Key_AltLeft },
{ XK_Alt_R, Key_AltRight },
{ XK_KP_Add, Key_Plus },
{ XK_plus, Key_Plus },
{ XK_KP_Subtract, Key_Minus },
{ XK_minus, Key_Minus },
{ XK_period, Key_Period },
{ XK_KP_Divide, Key_Divide },
{ XK_KP_Multiply, Key_Multiply },
{ XK_KP_Subtract, Key_NumpadMinus },
{ XK_KP_Add, Key_NumpadPlus },
{ XK_KP_Delete, Key_NumpadDelete },
{ XK_BackSpace, Key_Back },
{ XK_Tab, Key_Tab },
{ XK_Escape, Key_Esc },
{ XK_space, Key_Space },
{ XK_dead_grave, Key_DeadGrave },
{ XK_comma, Key_Comma },
{ XK_Delete, Key_Delete },
{ XK_Insert, Key_Insert },
{ XK_Home, Key_Home },
{ XK_End, Key_End },
{ XK_Page_Up, Key_PageUp },
{ XK_Page_Down, Key_PageDown },
{ XK_Caps_Lock, Key_CapsLock },
{ XK_F1, Key_Function1 },
{ XK_F2, Key_Function2 },
{ XK_F3, Key_Function3 },
{ XK_F4, Key_Function4 },
{ XK_F5, Key_Function5 },
{ XK_F6, Key_Function6 },
{ XK_F7, Key_Function7 },
{ XK_F8, Key_Function8 },
{ XK_F9, Key_Function9 },
{ XK_F10, Key_Function10 },
{ XK_F11, Key_Function11 },
{ XK_F12, Key_Function12 },
/** fixme Missing ones
CKeyboardKey(Key_NumpadEqual),
CKeyboardKey(Key_OEM1),
CKeyboardKey(Key_OEM2),
CKeyboardKey(Key_OEM3),
CKeyboardKey(Key_OEM4),
CKeyboardKey(Key_OEM5),
CKeyboardKey(Key_OEM6),
CKeyboardKey(Key_OEM7),
CKeyboardKey(Key_OEM8),
CKeyboardKey(Key_OEM102)
**/
};
CKeyboardLinux::CKeyboardLinux(QObject *parent) :
IKeyboard(parent)
{
m_display = XOpenDisplay(nullptr);
}
CKeyboardLinux::~CKeyboardLinux()
{
if (m_display) XCloseDisplay(m_display);
}
bool CKeyboardLinux::init()
{
QString dir = QLatin1String("/dev/input");
m_devInputWatcher = new QFileSystemWatcher(QStringList(dir), this);
connect(m_devInputWatcher, &QFileSystemWatcher::directoryChanged, this, &CKeyboardLinux::deviceDirectoryChanged);
deviceDirectoryChanged(dir);
return true;
}
void CKeyboardLinux::deviceDirectoryChanged(const QString &dir)
{
QDir eventFiles(dir, QLatin1String("event*"), QDir::Name, QDir::System);
foreach (QFileInfo fileInfo, eventFiles.entryInfoList())
{
QString path = fileInfo.absoluteFilePath();
if (!m_keyboardDevices.contains(path))
addRawInputDevice(path);
}
}
void CKeyboardLinux::inputReadyRead(int)
{
struct input_event eventInput;
QFile *fileInput = qobject_cast<QFile *>(sender()->parent());
if (!fileInput)
return;
bool found = false;
while (fileInput->read(reinterpret_cast<char *>(&eventInput), sizeof(eventInput)) == sizeof(eventInput))
{
found = true;
if (eventInput.type != EV_KEY)
continue;
bool isPressed = false;
switch (eventInput.value)
{
case 0:
isPressed = false;
break;
case 1:
isPressed = true;
break;
default:
continue;
}
// The + 8 offset is required for XkbKeycodeToKeysym to output the correct Keysym
int keyCode = eventInput.code + 8;
keyEvent(keyCode, isPressed);
}
if (!found)
{
int fd = fileInput->handle();
int version = 0;
if ((ioctl(fd, EVIOCGVERSION, &version) < 0) || (((version >> 16) & 0xFF) < 1))
{
qWarning("CKeyboardLinux: Removing dead input device %s", qPrintable(fileInput->fileName()));
m_keyboardDevices.remove(fileInput->fileName());
}
}
}
void CKeyboardLinux::addRawInputDevice(const QString &filePath)
{
QSharedPointer<QFile> inputFile(new QFile(filePath));
if (inputFile->open(QIODevice::ReadOnly))
{
int fd = inputFile->handle();
if (fd < 0) { return; }
int version = 0;
if (ioctl(fd, EVIOCGVERSION, &version) < 0) { return; }
char deviceName[255];
if (ioctl(fd, EVIOCGNAME(sizeof(deviceName)), deviceName) < 0) { return; }
uint8_t bitmask[EV_MAX / 8 + 1];
memset(bitmask, 0, sizeof(bitmask));
if (ioctl(fd, EVIOCGBIT(0, sizeof(bitmask)), &bitmask) < 0) { return; }
// Keyboards support EV_SYN and EV_KEY
// but do NOT support EV_REL and EV_ABS
if (!(bitmask[EV_SYN / 8] & (1 << (EV_SYN % 8))) &&
!(bitmask[EV_KEY / 8] & (1 << (EV_KEY % 8))) &&
(bitmask[EV_REL / 8] & (1 << (EV_REL % 8))) &&
(bitmask[EV_ABS / 8] & (1 << (EV_ABS % 8))))
{
return;
}
// Is it grabbed by someone else?
if ((ioctl(fd, EVIOCGRAB, 1) < 0))
{
BlackMisc::CLogMessage(this).warning(u"Device exclusively grabbed by someone else (X11 using exclusive-mode evdev?)") << deviceName;
}
else
{
ioctl(fd, EVIOCGRAB, 0);
uint8_t keys[KEY_MAX / 8 + 1];
if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keys)), &keys) >= 0) && (keys[KEY_SPACE / 8] & (1 << (KEY_SPACE % 8))))
{
BlackMisc::CLogMessage(this).info(u"Found keyboard: %1") << deviceName;
fcntl(inputFile->handle(), F_SETFL, O_NONBLOCK);
connect(new QSocketNotifier(inputFile->handle(), QSocketNotifier::Read, inputFile.data()), &QSocketNotifier::activated, this, &CKeyboardLinux::inputReadyRead);
m_keyboardDevices.insert(filePath, inputFile);
}
}
}
}
void CKeyboardLinux::keyEvent(int keyCode, bool isPressed)
{
if (isMouseButton(keyCode)) { return; }
BlackMisc::Input::CHotkeyCombination oldCombination(m_keyCombination);
if (isPressed)
{
auto key = convertToKey(keyCode);
if (key == Key_Unknown) { return; }
m_keyCombination.addKeyboardKey(key);
}
else
{
auto key = convertToKey(keyCode);
if (key == Key_Unknown) { return; }
m_keyCombination.removeKeyboardKey(key);
}
if (oldCombination != m_keyCombination)
{
emit keyCombinationChanged(m_keyCombination);
}
}
BlackMisc::Input::KeyCode CKeyboardLinux::convertToKey(int keyCode)
{
// The keycode received from kernel does not take keyboard layouts into account.
// It always defaults to US keyboards. In contrast to kernel devices, X11 is aware
// of user keyboard layouts. The magic below translates the key code
// into the correct symbol via a X11 connection.
// Summary of translations:
// Kernel key code -> X11 key symbol -> swift key code
auto keySym = XkbKeycodeToKeysym(m_display, keyCode, 0, 0);
return keyMapping.value(keySym, Key_Unknown);
}
bool CKeyboardLinux::isModifier(int keyCode)
{
auto keySym = XkbKeycodeToKeysym(m_display, keyCode, 0, 0);
switch (keySym)
{
case XK_Shift_L:
case XK_Shift_R:
case XK_Control_L:
case XK_Control_R:
case XK_Alt_L:
case XK_Alt_R:
return true;
default: return false;
}
return false;
}
bool CKeyboardLinux::isMouseButton(int keyCode)
{
switch (keyCode)
{
case BTN_LEFT:
case BTN_RIGHT:
case BTN_MIDDLE:
return true;
default:
return false;
}
}
}