swift
keyboardlinux.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "keyboardlinux.h"
5 
6 #include <X11/XKBlib.h>
7 #include <X11/keysym.h>
8 #include <fcntl.h>
9 #include <linux/input.h>
10 
11 #include <QDebug>
12 #include <QHash>
13 #include <QSocketNotifier>
14 
15 #include "misc/logmessage.h"
16 
17 using namespace swift::misc;
18 using namespace swift::misc::input;
19 
20 namespace swift::input
21 {
22  // https://www.cl.cam.ac.uk/~mgk25/ucs/keysymdef.h
23  static QHash<int, misc::input::KeyCode> keyMapping {
24  { XK_0, Key_0 },
25  { XK_1, Key_1 },
26  { XK_2, Key_2 },
27  { XK_3, Key_3 },
28  { XK_4, Key_4 },
29  { XK_5, Key_5 },
30  { XK_6, Key_6 },
31  { XK_7, Key_7 },
32  { XK_8, Key_8 },
33  { XK_9, Key_9 },
34  { XK_a, Key_A },
35  { XK_b, Key_B },
36  { XK_c, Key_C },
37  { XK_d, Key_D },
38  { XK_e, Key_E },
39  { XK_f, Key_F },
40  { XK_g, Key_G },
41  { XK_h, Key_H },
42  { XK_i, Key_I },
43  { XK_j, Key_J },
44  { XK_k, Key_K },
45  { XK_l, Key_L },
46  { XK_m, Key_M },
47  { XK_n, Key_N },
48  { XK_o, Key_O },
49  { XK_p, Key_P },
50  { XK_q, Key_Q },
51  { XK_r, Key_R },
52  { XK_s, Key_S },
53  { XK_t, Key_T },
54  { XK_u, Key_U },
55  { XK_v, Key_V },
56  { XK_w, Key_W },
57  { XK_x, Key_X },
58  { XK_y, Key_Y },
59  { XK_z, Key_Z },
60  { XK_Shift_L, Key_ShiftLeft },
61  { XK_Shift_R, Key_ShiftRight },
62  { XK_Control_L, Key_ControlLeft },
63  { XK_Control_R, Key_ControlRight },
64  { XK_Alt_L, Key_AltLeft },
65  { XK_Alt_R, Key_AltRight },
66  { XK_KP_Add, Key_Plus },
67  { XK_plus, Key_Plus },
68  { XK_KP_Subtract, Key_Minus },
69  { XK_minus, Key_Minus },
70  { XK_period, Key_Period },
71  { XK_KP_Divide, Key_Divide },
72  { XK_KP_Multiply, Key_Multiply },
73  { XK_KP_Subtract, Key_NumpadMinus },
74  { XK_KP_Add, Key_NumpadPlus },
75  { XK_KP_Delete, Key_NumpadDelete },
76  { XK_BackSpace, Key_Back },
77  { XK_Tab, Key_Tab },
78  { XK_Escape, Key_Esc },
79  { XK_space, Key_Space },
80  { XK_dead_grave, Key_DeadGrave },
81  { XK_comma, Key_Comma },
82  { XK_Delete, Key_Delete },
83  { XK_Insert, Key_Insert },
84  { XK_Home, Key_Home },
85  { XK_End, Key_End },
86  { XK_Page_Up, Key_PageUp },
87  { XK_Page_Down, Key_PageDown },
88  { XK_Caps_Lock, Key_CapsLock },
89  { XK_Return, Key_Enter },
90  { XK_F1, Key_Function1 },
91  { XK_F2, Key_Function2 },
92  { XK_F3, Key_Function3 },
93  { XK_F4, Key_Function4 },
94  { XK_F5, Key_Function5 },
95  { XK_F6, Key_Function6 },
96  { XK_F7, Key_Function7 },
97  { XK_F8, Key_Function8 },
98  { XK_F9, Key_Function9 },
99  { XK_F10, Key_Function10 },
100  { XK_F11, Key_Function11 },
101  { XK_F12, Key_Function12 },
102  { XK_F13, Key_Function13 },
103  { XK_F14, Key_Function14 },
104  { XK_F15, Key_Function15 },
105  { XK_F16, Key_Function16 },
106  { XK_F17, Key_Function17 },
107  { XK_F18, Key_Function18 },
108  { XK_F19, Key_Function19 },
109  { XK_F20, Key_Function20 },
110  { XK_F21, Key_Function21 },
111  { XK_F22, Key_Function22 },
112  { XK_F23, Key_Function23 },
113  { XK_F24, Key_Function24 },
114 
127  };
128 
129  CKeyboardLinux::CKeyboardLinux(QObject *parent) : IKeyboard(parent) { m_display = XOpenDisplay(nullptr); }
130 
131  CKeyboardLinux::~CKeyboardLinux()
132  {
133  if (m_display) XCloseDisplay(m_display);
134  }
135 
136  bool CKeyboardLinux::init()
137  {
138  QString dir = QLatin1String("/dev/input");
139  m_devInputWatcher = new QFileSystemWatcher(QStringList(dir), this);
140  connect(m_devInputWatcher, &QFileSystemWatcher::directoryChanged, this,
141  &CKeyboardLinux::deviceDirectoryChanged);
142  deviceDirectoryChanged(dir);
143 
144  return true;
145  }
146 
147  void CKeyboardLinux::deviceDirectoryChanged(const QString &dir)
148  {
149  QDir eventFiles(dir, QLatin1String("event*"), QDir::Name, QDir::System);
150 
151  foreach (QFileInfo fileInfo, eventFiles.entryInfoList())
152  {
153  QString path = fileInfo.absoluteFilePath();
154  if (!m_keyboardDevices.contains(path)) addRawInputDevice(path);
155  }
156  }
157 
158  void CKeyboardLinux::inputReadyRead(int)
159  {
160  struct input_event eventInput;
161 
162  QFile *fileInput = qobject_cast<QFile *>(sender()->parent());
163  if (!fileInput) return;
164 
165  bool found = false;
166 
167  while (fileInput->read(reinterpret_cast<char *>(&eventInput), sizeof(eventInput)) == sizeof(eventInput))
168  {
169  found = true;
170  if (eventInput.type != EV_KEY) continue;
171  bool isPressed = false;
172  switch (eventInput.value)
173  {
174  case 0: isPressed = false; break;
175  case 1: isPressed = true; break;
176  default: continue;
177  }
178 
179  // The + 8 offset is required for XkbKeycodeToKeysym to output the correct Keysym
180  int keyCode = eventInput.code + 8;
181  keyEvent(keyCode, isPressed);
182  }
183 
184  if (!found)
185  {
186  int fd = fileInput->handle();
187  int version = 0;
188  if ((ioctl(fd, EVIOCGVERSION, &version) < 0) || (((version >> 16) & 0xFF) < 1))
189  {
190  qWarning("CKeyboardLinux: Removing dead input device %s", qPrintable(fileInput->fileName()));
191  m_keyboardDevices.remove(fileInput->fileName());
192  }
193  }
194  }
195 
196  void CKeyboardLinux::addRawInputDevice(const QString &filePath)
197  {
198  QSharedPointer<QFile> inputFile(new QFile(filePath));
199  if (inputFile->open(QIODevice::ReadOnly))
200  {
201  int fd = inputFile->handle();
202  if (fd < 0) { return; }
203 
204  int version = 0;
205  if (ioctl(fd, EVIOCGVERSION, &version) < 0) { return; }
206 
207  char deviceName[255];
208  if (ioctl(fd, EVIOCGNAME(sizeof(deviceName)), deviceName) < 0) { return; }
209 
210  uint8_t bitmask[EV_MAX / 8 + 1];
211  memset(bitmask, 0, sizeof(bitmask));
212  if (ioctl(fd, EVIOCGBIT(0, sizeof(bitmask)), &bitmask) < 0) { return; }
213 
214  // Keyboards support EV_SYN and EV_KEY
215  // but do NOT support EV_REL and EV_ABS
216  if (!(bitmask[EV_SYN / 8] & (1 << (EV_SYN % 8))) && !(bitmask[EV_KEY / 8] & (1 << (EV_KEY % 8))) &&
217  (bitmask[EV_REL / 8] & (1 << (EV_REL % 8))) && (bitmask[EV_ABS / 8] & (1 << (EV_ABS % 8))))
218  {
219  return;
220  }
221 
222  // Is it grabbed by someone else?
223  if ((ioctl(fd, EVIOCGRAB, 1) < 0))
224  {
226  u"Device exclusively grabbed by someone else (X11 using exclusive-mode evdev?)")
227  << deviceName;
228  }
229  else
230  {
231  ioctl(fd, EVIOCGRAB, 0);
232  uint8_t keys[KEY_MAX / 8 + 1];
233  if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keys)), &keys) >= 0) &&
234  (keys[KEY_SPACE / 8] & (1 << (KEY_SPACE % 8))))
235  {
236  swift::misc::CLogMessage(this).info(u"Found keyboard: %1") << deviceName;
237 
238  fcntl(inputFile->handle(), F_SETFL, O_NONBLOCK);
239  connect(new QSocketNotifier(inputFile->handle(), QSocketNotifier::Read, inputFile.data()),
240  &QSocketNotifier::activated, this, &CKeyboardLinux::inputReadyRead);
241 
242  m_keyboardDevices.insert(filePath, inputFile);
243  }
244  }
245  }
246  else
247  {
248  swift::misc::CLogMessage(this).error(u"Failed to open keyboard device %1: %2")
249  << inputFile->fileName() << inputFile->errorString();
250  }
251  }
252 
253  void CKeyboardLinux::keyEvent(int keyCode, bool isPressed)
254  {
255  if (isMouseButton(keyCode)) { return; }
256 
257  swift::misc::input::CHotkeyCombination oldCombination(m_keyCombination);
258  if (isPressed)
259  {
260  auto key = convertToKey(keyCode);
261  if (key == Key_Unknown) { return; }
262 
263  m_keyCombination.addKeyboardKey(key);
264  }
265  else
266  {
267  auto key = convertToKey(keyCode);
268  if (key == Key_Unknown) { return; }
269 
270  m_keyCombination.removeKeyboardKey(key);
271  }
272 
273  if (oldCombination != m_keyCombination) { emit keyCombinationChanged(m_keyCombination); }
274  }
275 
276  swift::misc::input::KeyCode CKeyboardLinux::convertToKey(int keyCode)
277  {
278  // The keycode received from kernel does not take keyboard layouts into account.
279  // It always defaults to US keyboards. In contrast to kernel devices, X11 is aware
280  // of user keyboard layouts. The magic below translates the key code
281  // into the correct symbol via a X11 connection.
282  // Summary of translations:
283  // Kernel key code -> X11 key symbol -> swift key code
284 
285  auto keySym = XkbKeycodeToKeysym(m_display, keyCode, 0, 0);
286  return keyMapping.value(keySym, Key_Unknown);
287  }
288 
289  bool CKeyboardLinux::isModifier(int keyCode)
290  {
291  auto keySym = XkbKeycodeToKeysym(m_display, keyCode, 0, 0);
292  switch (keySym)
293  {
294  case XK_Shift_L:
295  case XK_Shift_R:
296  case XK_Control_L:
297  case XK_Control_R:
298  case XK_Alt_L:
299  case XK_Alt_R: return true;
300  default: return false;
301  }
302 
303  return false;
304  }
305 
306  bool CKeyboardLinux::isMouseButton(int keyCode)
307  {
308  switch (keyCode)
309  {
310  case BTN_LEFT:
311  case BTN_RIGHT:
312  case BTN_MIDDLE: return true;
313  default: return false;
314  }
315  }
316 } // namespace swift::input
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
Value object representing hotkey sequence.
KeyCode
Key code http://www.kbdlayout.info/.
Definition: keycodes.h:16
Free functions in swift::misc.