File indexing completed on 2025-09-14 05:27:46

0001 /*
0002     SPDX-FileCopyrightText: 2010 Andriy Rysin <rysin@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "xinput_helper.h"
0008 #include "debug.h"
0009 #include <config-keyboard.h>
0010 
0011 #include <QCoreApplication>
0012 #include <QDebug>
0013 #include <QTimer>
0014 #include <QtGui/private/qtx11extras_p.h>
0015 
0016 #include <X11/X.h>
0017 #include <X11/Xatom.h>
0018 
0019 #if HAVE_XINPUT
0020 #include <X11/extensions/XInput.h>
0021 #include <xcb/xproto.h>
0022 typedef struct xcb_input_device_presence_notify_event_t {
0023     uint8_t response_type;
0024     uint8_t pad0;
0025     uint16_t sequence;
0026     xcb_timestamp_t time;
0027     uint8_t devchange;
0028     uint8_t device_id;
0029     uint16_t control;
0030     uint8_t pad1[20];
0031 } xcb_input_device_presence_notify_event_t;
0032 // FIXME: #include <xcb/xinput.h> once xcb-xinput is stable
0033 #endif
0034 
0035 #include "udev_helper.h"
0036 
0037 static const int DEVICE_NONE = 0;
0038 static const int DEVICE_KEYBOARD = 1;
0039 static const int DEVICE_POINTER = 2;
0040 
0041 XInputEventNotifier::XInputEventNotifier(QWidget *parent)
0042     : XEventNotifier()
0043     , // TODO: destruct properly?
0044     xinputEventType(-1)
0045     , udevNotifier(nullptr)
0046     , keyboardNotificationTimer(new QTimer(this))
0047     , mouseNotificationTimer(new QTimer(this))
0048 {
0049     Q_UNUSED(parent)
0050 
0051     // Q_EMIT signal only once, even after X11 re-enables N keyboards after resuming from suspend
0052     keyboardNotificationTimer->setSingleShot(true);
0053     keyboardNotificationTimer->setInterval(500);
0054     connect(keyboardNotificationTimer, &QTimer::timeout, this, &XInputEventNotifier::newKeyboardDevice);
0055 
0056     // same for mouse
0057     mouseNotificationTimer->setSingleShot(true);
0058     mouseNotificationTimer->setInterval(500);
0059     connect(mouseNotificationTimer, &QTimer::timeout, this, &XInputEventNotifier::newPointerDevice);
0060 }
0061 
0062 void XInputEventNotifier::start()
0063 {
0064     if (QCoreApplication::instance() != nullptr) {
0065         registerForNewDeviceEvent(QX11Info::display());
0066     }
0067 
0068     XEventNotifier::start();
0069 }
0070 
0071 void XInputEventNotifier::stop()
0072 {
0073     XEventNotifier::stop();
0074 }
0075 
0076 bool XInputEventNotifier::processOtherEvents(xcb_generic_event_t *event)
0077 {
0078     int newDeviceType = getNewDeviceEventType(event);
0079     if (newDeviceType == DEVICE_KEYBOARD) {
0080         if (!keyboardNotificationTimer->isActive()) {
0081             keyboardNotificationTimer->start();
0082         }
0083     } else if (newDeviceType == DEVICE_POINTER) {
0084         if (!mouseNotificationTimer->isActive()) {
0085             mouseNotificationTimer->start();
0086         }
0087         // arghhh, looks like X resets xkb map even when only pointer device is connected
0088         if (!keyboardNotificationTimer->isActive()) {
0089             keyboardNotificationTimer->start();
0090         }
0091     }
0092     return true;
0093 }
0094 
0095 #if HAVE_XINPUT
0096 
0097 // This is ugly but allows to skip multiple execution of setxkbmap
0098 // for all keyboard devices that don't care about layouts
0099 static bool isRealKeyboard(const char *deviceName)
0100 {
0101     return strstr(deviceName, "Video Bus") == nullptr && strstr(deviceName, "Sleep Button") == nullptr && strstr(deviceName, "Power Button") == nullptr
0102         && strstr(deviceName, "WMI hotkeys") == nullptr && strstr(deviceName, "Camera") == nullptr;
0103 }
0104 
0105 int XInputEventNotifier::getNewDeviceEventType(xcb_generic_event_t *event)
0106 {
0107     int newDeviceType = DEVICE_NONE;
0108     if (xinputEventType != -1 && event->response_type == xinputEventType) {
0109         xcb_input_device_presence_notify_event_t *xdpne = reinterpret_cast<xcb_input_device_presence_notify_event_t *>(event);
0110         if (xdpne->devchange == DeviceEnabled) {
0111             int ndevices;
0112             XDeviceInfo *devices = XListInputDevices(display, &ndevices);
0113             if (devices != nullptr) {
0114                 qCDebug(KCM_KEYBOARD) << "New device id:" << xdpne->device_id;
0115                 for (int i = 0; i < ndevices; i++) {
0116                     qCDebug(KCM_KEYBOARD) << "id:" << devices[i].id << "name:" << devices[i].name << "used as:" << devices[i].use;
0117                     if (devices[i].id == xdpne->device_id) {
0118                         if (devices[i].use == IsXKeyboard || devices[i].use == IsXExtensionKeyboard) {
0119                             if (isRealKeyboard(devices[i].name)) {
0120                                 newDeviceType = DEVICE_KEYBOARD;
0121                                 qCDebug(KCM_KEYBOARD) << "new keyboard device, id:" << devices[i].id << "name:" << devices[i].name
0122                                                       << "used as:" << devices[i].use;
0123                                 break;
0124                             }
0125                         }
0126                         if (devices[i].use == IsXPointer || devices[i].use == IsXExtensionPointer) {
0127                             newDeviceType = DEVICE_POINTER;
0128                             qCDebug(KCM_KEYBOARD) << "new pointer device, id:" << devices[i].id << "name:" << devices[i].name << "used as:" << devices[i].use;
0129                             break;
0130                         }
0131                     }
0132                 }
0133                 XFreeDeviceList(devices);
0134             }
0135         }
0136     }
0137     return newDeviceType;
0138 }
0139 
0140 int XInputEventNotifier::registerForNewDeviceEvent(Display *display_)
0141 {
0142     int xitype;
0143     XEventClass xiclass;
0144     display = display_;
0145 
0146     DevicePresence(display, xitype, xiclass);
0147     XSelectExtensionEvent(display, DefaultRootWindow(display), &xiclass, 1);
0148     qCDebug(KCM_KEYBOARD) << "Registered for new device events from XInput, class" << xitype;
0149     xinputEventType = xitype;
0150     return xitype;
0151 }
0152 
0153 #elif defined(HAVE_UDEV)
0154 
0155 int XInputEventNotifier::registerForNewDeviceEvent(Display * /*display*/)
0156 {
0157     if (!udevNotifier) {
0158         udevNotifier = new UdevDeviceNotifier(this);
0159         connect(udevNotifier, &UdevDeviceNotifier::newKeyboardDevice, this, &XInputEventNotifier::newKeyboardDevice);
0160         connect(udevNotifier, &UdevDeviceNotifier::newPointerDevice, this, &XInputEventNotifier::newPointerDevice);
0161         // Same as with XInput notifier, also Q_EMIT newKeyboardDevice when pointer device is found
0162         connect(udevNotifier, &UdevDeviceNotifier::newPointerDevice, this, &XInputEventNotifier::newKeyboardDevice);
0163     }
0164 
0165     return -1;
0166 }
0167 
0168 int XInputEventNotifier::getNewDeviceEventType(xcb_generic_event_t * /*event*/)
0169 {
0170     return DEVICE_NONE;
0171 }
0172 
0173 #else
0174 
0175 #ifdef __GNUC__
0176 #warning "Keyboard daemon is compiled without XInput and UDev, keyboard settings will be reset when new keyboard device is plugged in!"
0177 #endif
0178 
0179 int XInputEventNotifier::registerForNewDeviceEvent(Display * /*display*/)
0180 {
0181     qCWarning(KCM_KEYBOARD) << "Keyboard kded daemon is compiled without XInput, xkb configuration will be reset when new keyboard device is plugged in!";
0182     return -1;
0183 }
0184 
0185 int XInputEventNotifier::getNewDeviceEventType(xcb_generic_event_t * /*event*/)
0186 {
0187     return DEVICE_NONE;
0188 }
0189 
0190 #endif