File indexing completed on 2024-05-19 16:38:56

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