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 }