File indexing completed on 2025-01-26 05:06:34

0001 /*
0002     SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 #include "x11_libinput_dummydevice.h"
0007 #include "libinput_settings.h"
0008 
0009 #include <libinput-properties.h>
0010 
0011 #include <X11/Xatom.h>
0012 #include <X11/extensions/XInput.h>
0013 #include <X11/extensions/XInput2.h>
0014 
0015 static Atom s_touchpadAtom;
0016 
0017 template<typename Callback>
0018 static void XIForallPointerDevices(Display *dpy, const Callback &callback)
0019 {
0020     int ndevices_return;
0021     XDeviceInfo *info = XListInputDevices(dpy, &ndevices_return);
0022     if (!info) {
0023         return;
0024     }
0025     for (int i = 0; i < ndevices_return; ++i) {
0026         XDeviceInfo *dev = info + i;
0027         if ((dev->use == IsXPointer || dev->use == IsXExtensionPointer) && dev->type != s_touchpadAtom) {
0028             callback(dev);
0029         }
0030     }
0031     XFreeDeviceList(info);
0032 }
0033 
0034 struct ScopedXDeleter {
0035     static inline void cleanup(void *pointer)
0036     {
0037         if (pointer) {
0038             XFree(pointer);
0039         }
0040     }
0041 };
0042 
0043 namespace
0044 {
0045 template<typename T>
0046 void valueWriterPart(T val, Atom valAtom, Display *dpy)
0047 {
0048     Q_UNUSED(val);
0049     Q_UNUSED(valAtom);
0050     Q_UNUSED(dpy);
0051 }
0052 
0053 template<>
0054 void valueWriterPart<bool>(bool val, Atom valAtom, Display *dpy)
0055 {
0056     XIForallPointerDevices(dpy, [&](XDeviceInfo *info) {
0057         int deviceid = info->id;
0058         Status status;
0059         Atom type_return;
0060         int format_return;
0061         unsigned long num_items_return;
0062         unsigned long bytes_after_return;
0063 
0064         unsigned char *_data = nullptr;
0065         // data returned is an 1 byte boolean
0066         status = XIGetProperty(dpy, deviceid, valAtom, 0, 1, False, XA_INTEGER, &type_return, &format_return, &num_items_return, &bytes_after_return, &_data);
0067         if (status != Success) {
0068             return;
0069         }
0070 
0071         QScopedArrayPointer<unsigned char, ScopedXDeleter> data(_data);
0072         _data = nullptr;
0073 
0074         if (type_return != XA_INTEGER || !data || format_return != 8) {
0075             return;
0076         }
0077 
0078         unsigned char sendVal[3] = {0};
0079         if (num_items_return == 1) {
0080             sendVal[0] = val;
0081         } else {
0082             // Special case for acceleration profile.
0083             const Atom accel = XInternAtom(dpy, LIBINPUT_PROP_ACCEL_PROFILE_ENABLED, True);
0084             if (num_items_return < 2 || num_items_return > 3 || valAtom != accel) {
0085                 return;
0086             }
0087             sendVal[val] = 1;
0088         }
0089 
0090         XIChangeProperty(dpy, deviceid, valAtom, XA_INTEGER, 8, XIPropModeReplace, sendVal, num_items_return);
0091     });
0092 }
0093 
0094 template<>
0095 void valueWriterPart<qreal>(qreal val, Atom valAtom, Display *dpy)
0096 {
0097     XIForallPointerDevices(dpy, [&](XDeviceInfo *info) {
0098         int deviceid = info->id;
0099         Status status;
0100         Atom float_type = XInternAtom(dpy, "FLOAT", False);
0101         Atom type_return;
0102         int format_return;
0103         unsigned long num_items_return;
0104         unsigned long bytes_after_return;
0105 
0106         unsigned char *_data = nullptr;
0107         // data returned is an 1 byte boolean
0108         status = XIGetProperty(dpy, deviceid, valAtom, 0, 1, False, float_type, &type_return, &format_return, &num_items_return, &bytes_after_return, &_data);
0109         if (status != Success) {
0110             return;
0111         }
0112 
0113         QScopedArrayPointer<unsigned char, ScopedXDeleter> data(_data);
0114         _data = nullptr;
0115 
0116         if (type_return != float_type || !data || format_return != 32 || num_items_return != 1) {
0117             return;
0118         }
0119 
0120         unsigned char buffer[4096];
0121         float *sendPtr = (float *)buffer;
0122         *sendPtr = val;
0123 
0124         XIChangeProperty(dpy, deviceid, valAtom, float_type, format_return, XIPropModeReplace, buffer, 1);
0125     });
0126 }
0127 }
0128 
0129 X11LibinputDummyDevice::X11LibinputDummyDevice(QObject *parent, Display *dpy)
0130     : QObject(parent)
0131     , m_settings(new LibinputSettings())
0132     , m_dpy(dpy)
0133 {
0134     m_leftHanded.atom = XInternAtom(dpy, LIBINPUT_PROP_LEFT_HANDED, True);
0135     m_middleEmulation.atom = XInternAtom(dpy, LIBINPUT_PROP_MIDDLE_EMULATION_ENABLED, True);
0136     m_naturalScroll.atom = XInternAtom(dpy, LIBINPUT_PROP_NATURAL_SCROLL, True);
0137     m_pointerAcceleration.atom = XInternAtom(dpy, LIBINPUT_PROP_ACCEL, True);
0138     m_pointerAccelerationProfileFlat.atom = XInternAtom(dpy, LIBINPUT_PROP_ACCEL_PROFILE_ENABLED, True);
0139 
0140     m_supportsDisableEvents.val = false;
0141     m_enabled.val = true;
0142     m_supportedButtons.val = Qt::LeftButton | Qt::MiddleButton | Qt::RightButton;
0143     m_supportsLeftHanded.val = true;
0144     m_supportsMiddleEmulation.val = true;
0145     m_middleEmulationEnabledByDefault.val = false;
0146 
0147     m_supportsPointerAcceleration.val = true;
0148     m_defaultPointerAcceleration.val = 0;
0149 
0150     m_supportsPointerAccelerationProfileAdaptive.val = true;
0151     m_supportsPointerAccelerationProfileFlat.val = true;
0152 
0153     auto x11DefaultFlat = m_settings->load(QStringLiteral("X11LibInputXAccelProfileFlat"), false);
0154     m_defaultPointerAccelerationProfileFlat.val = x11DefaultFlat;
0155     m_defaultPointerAccelerationProfileAdaptive.val = !x11DefaultFlat;
0156 
0157     m_supportsNaturalScroll.val = true;
0158     m_naturalScrollEnabledByDefault.val = false;
0159 
0160     s_touchpadAtom = XInternAtom(m_dpy, XI_TOUCHPAD, True);
0161 }
0162 
0163 X11LibinputDummyDevice::~X11LibinputDummyDevice()
0164 {
0165     delete m_settings;
0166 }
0167 
0168 bool X11LibinputDummyDevice::getConfig()
0169 {
0170     auto reset = [this](Prop<bool> &prop, bool defVal) {
0171         prop.reset(m_settings->load(prop.cfgName, defVal));
0172     };
0173 
0174     reset(m_leftHanded, false);
0175 
0176     reset(m_middleEmulation, false);
0177     reset(m_naturalScroll, false);
0178     auto flatDefault = m_defaultPointerAccelerationProfileFlat.val;
0179     reset(m_pointerAccelerationProfileFlat, flatDefault);
0180 
0181     m_pointerAccelerationProfileAdaptive.reset(!m_settings->load(m_pointerAccelerationProfileFlat.cfgName, flatDefault));
0182     m_pointerAcceleration.reset(m_settings->load(m_pointerAcceleration.cfgName, 0.));
0183 
0184     return true;
0185 }
0186 
0187 bool X11LibinputDummyDevice::getDefaultConfig()
0188 {
0189     m_leftHanded.set(false);
0190 
0191     m_pointerAcceleration.set(m_defaultPointerAcceleration);
0192     m_pointerAccelerationProfileFlat.set(m_defaultPointerAccelerationProfileFlat);
0193     m_pointerAccelerationProfileAdaptive.set(m_defaultPointerAccelerationProfileAdaptive);
0194 
0195     m_middleEmulation.set(m_middleEmulationEnabledByDefault);
0196     m_naturalScroll.set(m_naturalScrollEnabledByDefault);
0197 
0198     return true;
0199 }
0200 
0201 bool X11LibinputDummyDevice::applyConfig()
0202 {
0203     valueWriter(m_leftHanded);
0204     valueWriter(m_middleEmulation);
0205     valueWriter(m_naturalScroll);
0206     valueWriter(m_pointerAcceleration);
0207     valueWriter(m_pointerAccelerationProfileFlat);
0208 
0209     return true;
0210 }
0211 
0212 void X11LibinputDummyDevice::getDefaultConfigFromX()
0213 {
0214     // The user can override certain values in their X configuration. We want to
0215     // account for those in our default values, but if we just read this when
0216     // loading the KCM, we end up reading the current settings which may already
0217     // have been modified by us. So instead, read these defaults during startup
0218     // and write them to config, so we can later on read them again to know the
0219     // system-wide defaults.
0220     bool flatProfile = true;
0221     XIForallPointerDevices(m_dpy, [&](XDeviceInfo *info) {
0222         Atom property = m_pointerAccelerationProfileFlat.atom;
0223         Atom type_return;
0224         int format_return;
0225         unsigned long num_items_return;
0226         unsigned long bytes_after_return;
0227         unsigned char *_data = nullptr;
0228 
0229         auto status =
0230             XIGetProperty(m_dpy, info->id, property, 0, 1, False, XA_INTEGER, &type_return, &format_return, &num_items_return, &bytes_after_return, &_data);
0231         if (status != Success) {
0232             return;
0233         }
0234 
0235         QScopedArrayPointer<unsigned char, ScopedXDeleter> data(_data);
0236         _data = nullptr;
0237 
0238         if (type_return != XA_INTEGER || !data || format_return != 8 || num_items_return != 2) {
0239             return;
0240         }
0241 
0242         if (data[0] == 1 && data[1] == 0) {
0243             flatProfile = false;
0244         }
0245     });
0246     m_settings->save(QStringLiteral("X11LibInputXAccelProfileFlat"), flatProfile);
0247 }
0248 
0249 template<typename T>
0250 bool X11LibinputDummyDevice::valueWriter(Prop<T> &prop)
0251 {
0252     // Check atom availability first.
0253     if (prop.atom == None) {
0254         return false;
0255     }
0256 
0257     if (prop.val != prop.old) {
0258         m_settings->save(prop.cfgName, prop.val);
0259     }
0260 
0261     valueWriterPart(prop.val, prop.atom, m_dpy);
0262 
0263     prop.old = prop.val;
0264 
0265     return true;
0266 }
0267 
0268 bool X11LibinputDummyDevice::isChangedConfig() const
0269 {
0270     return m_leftHanded.changed() || m_pointerAcceleration.changed() || m_pointerAccelerationProfileFlat.changed()
0271         || m_pointerAccelerationProfileAdaptive.changed() || m_middleEmulation.changed() || m_naturalScroll.changed();
0272 }