File indexing completed on 2024-11-24 05:00:47

0001 /*
0002     SPDX-FileCopyrightText: 2013 Alexander Mezin <mezin.alexander@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <cmath>
0008 #include <cstring>
0009 
0010 #include <QtAlgorithms>
0011 
0012 #include <KLocalizedString>
0013 #include <QDebug>
0014 
0015 #include <config-X11.h>
0016 
0017 // Includes are ordered this way because of #defines in Xorg's headers
0018 #include "xlibbackend.h" // krazy:exclude=includes
0019 #include "xlibnotifications.h" // krazy:exclude=includes
0020 #include "xrecordkeyboardmonitor.h" // krazy:exclude=includes
0021 #if HAVE_XORGLIBINPUT
0022 #endif
0023 
0024 #include <X11/Xatom.h>
0025 #include <X11/Xlib-xcb.h>
0026 #include <X11/extensions/XInput.h>
0027 #include <X11/extensions/XInput2.h>
0028 
0029 #include <xserver-properties.h>
0030 
0031 struct DeviceListDeleter {
0032     void operator()(XDeviceInfo *p)
0033     {
0034         if (p) {
0035             XFreeDeviceList(p);
0036         }
0037     }
0038 };
0039 
0040 void XlibBackend::XDisplayCleanup::operator()(Display *p)
0041 {
0042     if (p) {
0043         XCloseDisplay(p);
0044     }
0045 }
0046 
0047 XlibBackend *XlibBackend::initialize(QObject *parent)
0048 {
0049     XlibBackend *backend = new XlibBackend(parent);
0050     if (!backend->m_display) {
0051         delete backend;
0052         return nullptr;
0053     }
0054     return backend;
0055 }
0056 
0057 XlibBackend::~XlibBackend()
0058 {
0059 }
0060 
0061 XlibBackend::XlibBackend(QObject *parent)
0062     : TouchpadBackend(parent)
0063     , m_display(XOpenDisplay(nullptr))
0064     , m_connection(nullptr)
0065 {
0066     if (m_display) {
0067         m_connection = XGetXCBConnection(m_display.get());
0068     }
0069 
0070     if (!m_connection) {
0071         m_errorString = i18n("Cannot connect to X server");
0072         return;
0073     }
0074 
0075     m_mouseAtom.intern(m_connection, XI_MOUSE);
0076     m_keyboardAtom.intern(m_connection, XI_KEYBOARD);
0077     m_touchpadAtom.intern(m_connection, XI_TOUCHPAD);
0078     m_enabledAtom.intern(m_connection, XI_PROP_ENABLED);
0079 
0080     m_libinputIdentifierAtom.intern(m_connection, "libinput Send Events Modes Available");
0081 
0082     m_device.reset(findTouchpad());
0083     if (!m_device) {
0084         m_errorString = i18n("No touchpad found");
0085     }
0086 }
0087 
0088 XlibTouchpad *XlibBackend::findTouchpad()
0089 {
0090     int nDevices = 0;
0091     std::unique_ptr<XDeviceInfo, DeviceListDeleter> deviceInfo(XListInputDevices(m_display.get(), &nDevices));
0092 
0093     for (XDeviceInfo *info = deviceInfo.get(); info < deviceInfo.get() + nDevices; info++) {
0094         // Make sure device is touchpad
0095         if (info->type != m_touchpadAtom.atom()) {
0096             continue;
0097         }
0098         int nProperties = 0;
0099         std::shared_ptr<Atom> properties(XIListProperties(m_display.get(), info->id, &nProperties), XDeleter);
0100 
0101         Atom *atom = properties.get();
0102         Atom *atomEnd = properties.get() + nProperties;
0103         for (; atom != atomEnd; atom++) {
0104 #if HAVE_XORGLIBINPUT
0105             if (*atom == m_libinputIdentifierAtom.atom()) {
0106                 setMode(TouchpadInputBackendMode::XLibinput);
0107                 return new LibinputTouchpad(m_display.get(), info->id);
0108             }
0109 #endif
0110         }
0111     }
0112 
0113     return nullptr;
0114 }
0115 
0116 bool XlibBackend::applyConfig(const QVariantHash &p)
0117 {
0118     if (!m_device) {
0119         return false;
0120     }
0121 
0122     bool success = m_device->applyConfig(p);
0123     if (!success) {
0124         m_errorString = i18n("Cannot apply touchpad configuration");
0125     }
0126 
0127     return success;
0128 }
0129 
0130 bool XlibBackend::applyConfig()
0131 {
0132     if (!m_device) {
0133         return false;
0134     }
0135 
0136     bool success = m_device->applyConfig();
0137     if (!success) {
0138         m_errorString = i18n("Cannot apply touchpad configuration");
0139     }
0140 
0141     return success;
0142 }
0143 
0144 bool XlibBackend::getConfig(QVariantHash &p)
0145 {
0146     if (!m_device) {
0147         return false;
0148     }
0149 
0150     bool success = m_device->getConfig(p);
0151     if (!success) {
0152         m_errorString = i18n("Cannot read touchpad configuration");
0153     }
0154     return success;
0155 }
0156 
0157 bool XlibBackend::getConfig()
0158 {
0159     if (!m_device) {
0160         return false;
0161     }
0162 
0163     bool success = m_device->getConfig();
0164     if (!success) {
0165         m_errorString = i18n("Cannot read touchpad configuration");
0166     }
0167     return success;
0168 }
0169 
0170 bool XlibBackend::getDefaultConfig()
0171 {
0172     if (!m_device) {
0173         return false;
0174     }
0175 
0176     bool success = m_device->getDefaultConfig();
0177     if (!success) {
0178         m_errorString = i18n("Cannot read default touchpad configuration");
0179     }
0180     return success;
0181 }
0182 
0183 bool XlibBackend::isChangedConfig() const
0184 {
0185     if (!m_device) {
0186         return false;
0187     }
0188 
0189     return m_device->isChangedConfig();
0190 }
0191 
0192 void XlibBackend::setTouchpadEnabled(bool enable)
0193 {
0194     if (!m_device) {
0195         return;
0196     }
0197 
0198     m_device->setEnabled(enable);
0199 
0200     // FIXME? This should not be needed, m_notifications should trigger
0201     // a propertyChanged signal when we enable/disable the touchpad,
0202     // that will Q_EMIT touchpadStateChanged, but for some reason
0203     // XlibNotifications is not getting the property change events
0204     // so we just Q_EMIT touchpadStateChanged from here as a workaround
0205     Q_EMIT touchpadStateChanged();
0206 }
0207 
0208 void XlibBackend::setTouchpadOff(TouchpadBackend::TouchpadOffState state)
0209 {
0210     if (!m_device) {
0211         return;
0212     }
0213 
0214     int touchpadOff = 0;
0215     switch (state) {
0216     case TouchpadEnabled:
0217         touchpadOff = 0;
0218         break;
0219     case TouchpadFullyDisabled:
0220         touchpadOff = 1;
0221         break;
0222     case TouchpadTapAndScrollDisabled:
0223         touchpadOff = 2;
0224         break;
0225     default:
0226         qCritical() << "Unknown TouchpadOffState" << state;
0227         return;
0228     }
0229 
0230     m_device->setTouchpadOff(touchpadOff);
0231 }
0232 
0233 bool XlibBackend::isTouchpadAvailable()
0234 {
0235     return m_device != nullptr;
0236 }
0237 
0238 bool XlibBackend::isTouchpadEnabled()
0239 {
0240     if (!m_device) {
0241         return false;
0242     }
0243 
0244     return m_device->enabled();
0245 }
0246 
0247 TouchpadBackend::TouchpadOffState XlibBackend::getTouchpadOff()
0248 {
0249     if (!m_device) {
0250         return TouchpadFullyDisabled;
0251     }
0252     int value = m_device->touchpadOff();
0253     switch (value) {
0254     case 0:
0255         return TouchpadEnabled;
0256     case 1:
0257         return TouchpadFullyDisabled;
0258     case 2:
0259         return TouchpadTapAndScrollDisabled;
0260     default:
0261         qCritical() << "Unknown TouchpadOff value" << value;
0262         return TouchpadFullyDisabled;
0263     }
0264 }
0265 
0266 void XlibBackend::touchpadDetached()
0267 {
0268     qWarning() << "Touchpad detached";
0269     m_device.reset();
0270     Q_EMIT touchpadReset();
0271 }
0272 
0273 void XlibBackend::devicePlugged(int device)
0274 {
0275     if (!m_device) {
0276         m_device.reset(findTouchpad());
0277         if (m_device) {
0278             qWarning() << "Touchpad reset";
0279             // We get called by m_notifications, need to use deleteLater
0280             m_notifications.release()->deleteLater();
0281             watchForEvents(m_keyboard != nullptr);
0282             Q_EMIT touchpadReset();
0283         }
0284     }
0285     if (!m_device || device != m_device->deviceId()) {
0286         Q_EMIT mousesChanged();
0287     }
0288 }
0289 
0290 void XlibBackend::propertyChanged(xcb_atom_t prop)
0291 {
0292     if ((m_device && prop == m_device->touchpadOffAtom().atom()) || prop == m_enabledAtom.atom()) {
0293         Q_EMIT touchpadStateChanged();
0294     }
0295 }
0296 
0297 QStringList XlibBackend::listMouses(const QStringList &blacklist)
0298 {
0299     int nDevices = 0;
0300     std::unique_ptr<XDeviceInfo, DeviceListDeleter> info(XListInputDevices(m_display.get(), &nDevices));
0301     QStringList list;
0302     for (XDeviceInfo *i = info.get(); i != info.get() + nDevices; i++) {
0303         if (m_device && i->id == static_cast<XID>(m_device->deviceId())) {
0304             continue;
0305         }
0306         if (i->use != IsXExtensionPointer && i->use != IsXPointer) {
0307             continue;
0308         }
0309         // type = KEYBOARD && use = Pointer means usb receiver for both keyboard
0310         // and mouse
0311         if (i->type != m_mouseAtom.atom() && i->type != m_keyboardAtom.atom()) {
0312             continue;
0313         }
0314         QString name(i->name);
0315         if (blacklist.contains(name, Qt::CaseInsensitive)) {
0316             continue;
0317         }
0318         PropertyInfo enabled(m_display.get(), i->id, m_enabledAtom.atom(), 0);
0319         if (enabled.value(0) == false) {
0320             continue;
0321         }
0322         list.append(name);
0323     }
0324 
0325     return list;
0326 }
0327 
0328 QList<QObject *> XlibBackend::getDevices() const
0329 {
0330     QList<QObject *> touchpads;
0331 
0332 #if HAVE_XORGLIBINPUT
0333     LibinputTouchpad *libinputtouchpad = dynamic_cast<LibinputTouchpad *>(m_device.get());
0334     if (libinputtouchpad) {
0335         touchpads.push_back(libinputtouchpad);
0336     }
0337 #endif
0338 
0339     return touchpads;
0340 }
0341 
0342 void XlibBackend::watchForEvents(bool keyboard)
0343 {
0344     if (!m_notifications) {
0345         m_notifications.reset(new XlibNotifications(m_display.get(), m_device ? m_device->deviceId() : XIAllDevices));
0346         connect(m_notifications.get(), &XlibNotifications::devicePlugged, this, &XlibBackend::devicePlugged);
0347         connect(m_notifications.get(), &XlibNotifications::touchpadDetached, this, &XlibBackend::touchpadDetached);
0348         connect(m_notifications.get(), &XlibNotifications::propertyChanged, this, &XlibBackend::propertyChanged);
0349     }
0350 
0351     if (keyboard == (m_keyboard != nullptr)) {
0352         return;
0353     }
0354 
0355     if (!keyboard) {
0356         m_keyboard.reset();
0357         return;
0358     }
0359 
0360     m_keyboard.reset(new XRecordKeyboardMonitor(m_display.get()));
0361     connect(m_keyboard.get(), &XRecordKeyboardMonitor::keyboardActivityStarted, this, &XlibBackend::keyboardActivityStarted);
0362     connect(m_keyboard.get(), &XRecordKeyboardMonitor::keyboardActivityFinished, this, &XlibBackend::keyboardActivityFinished);
0363 }