File indexing completed on 2024-12-08 13:22:02

0001 /*
0002     SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 
0007 */
0008 
0009 #include "tabletmodemanager.h"
0010 
0011 #include "backends/fakeinput/fakeinputdevice.h"
0012 #include "backends/libinput/device.h"
0013 #include "core/inputdevice.h"
0014 #include "input.h"
0015 #include "input_event.h"
0016 #include "input_event_spy.h"
0017 #include "main.h"
0018 #include "wayland_server.h"
0019 
0020 #include <QDBusConnection>
0021 
0022 namespace KWin
0023 {
0024 
0025 static bool shouldIgnoreDevice(InputDevice *device)
0026 {
0027     if (qobject_cast<FakeInputDevice*>(device)) {
0028         return true;
0029     }
0030 
0031     auto libinput_device = qobject_cast<LibInput::Device *>(device);
0032     if (!libinput_device) {
0033         return false;
0034     }
0035 
0036     bool ignore = false;
0037     if (auto udev = libinput_device_get_udev_device(libinput_device->device()); udev) {
0038         ignore = udev_device_has_tag(udev, "kwin-ignore-tablet-mode");
0039         udev_device_unref(udev);
0040     }
0041     return ignore;
0042 }
0043 
0044 class TabletModeSwitchEventSpy : public QObject, public InputEventSpy
0045 {
0046 public:
0047     explicit TabletModeSwitchEventSpy(TabletModeManager *parent)
0048         : QObject(parent)
0049         , m_parent(parent)
0050     {
0051     }
0052 
0053     void switchEvent(SwitchEvent *event) override
0054     {
0055         if (!event->device()->isTabletModeSwitch()) {
0056             return;
0057         }
0058 
0059         switch (event->state()) {
0060         case SwitchEvent::State::Off:
0061             m_parent->setIsTablet(false);
0062             break;
0063         case SwitchEvent::State::On:
0064             m_parent->setIsTablet(true);
0065             break;
0066         default:
0067             Q_UNREACHABLE();
0068         }
0069     }
0070 
0071 private:
0072     TabletModeManager *const m_parent;
0073 };
0074 
0075 class TabletModeTouchpadRemovedSpy : public QObject
0076 {
0077 public:
0078     explicit TabletModeTouchpadRemovedSpy(TabletModeManager *parent)
0079         : QObject(parent)
0080         , m_parent(parent)
0081     {
0082         connect(input(), &InputRedirection::deviceAdded, this, &TabletModeTouchpadRemovedSpy::refresh);
0083         connect(input(), &InputRedirection::deviceRemoved, this, &TabletModeTouchpadRemovedSpy::refresh);
0084 
0085         check();
0086     }
0087 
0088     void refresh(InputDevice *inputDevice)
0089     {
0090         if (inputDevice->isTouch() || inputDevice->isPointer()) {
0091             check();
0092         }
0093     }
0094 
0095     void check()
0096     {
0097         const auto devices = input()->devices();
0098         const bool hasTouch = std::any_of(devices.constBegin(), devices.constEnd(), [](InputDevice *device) {
0099             return device->isTouch() && !shouldIgnoreDevice(device);
0100         });
0101         m_parent->setTabletModeAvailable(hasTouch);
0102 
0103         const bool hasPointer = std::any_of(devices.constBegin(), devices.constEnd(), [](InputDevice *device) {
0104             return device->isPointer() && !shouldIgnoreDevice(device);
0105         });
0106         m_parent->setIsTablet(hasTouch && !hasPointer);
0107     }
0108 
0109 private:
0110     TabletModeManager *const m_parent;
0111 };
0112 
0113 TabletModeManager::TabletModeManager()
0114 {
0115     if (waylandServer()) {
0116         if (input()->hasTabletModeSwitch()) {
0117             input()->installInputEventSpy(new TabletModeSwitchEventSpy(this));
0118         } else {
0119             hasTabletModeInputChanged(false);
0120         }
0121     }
0122 
0123     KSharedConfig::Ptr kwinSettings = kwinApp()->config();
0124     m_settingsWatcher = KConfigWatcher::create(kwinSettings);
0125     connect(m_settingsWatcher.data(), &KConfigWatcher::configChanged, this, &KWin::TabletModeManager::refreshSettings);
0126     refreshSettings();
0127 
0128     QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/KWin"),
0129                                                  QStringLiteral("org.kde.KWin.TabletModeManager"),
0130                                                  this,
0131                                                  // NOTE: slots must be exported for properties to work correctly
0132                                                  QDBusConnection::ExportAllProperties | QDBusConnection::ExportAllSignals | QDBusConnection::ExportAllSlots);
0133 
0134     if (waylandServer()) {
0135         connect(input(), &InputRedirection::hasTabletModeSwitchChanged, this, &TabletModeManager::hasTabletModeInputChanged);
0136     }
0137 }
0138 
0139 void KWin::TabletModeManager::refreshSettings()
0140 {
0141     KSharedConfig::Ptr kwinSettings = kwinApp()->config();
0142     KConfigGroup cg = kwinSettings->group("Input");
0143     const QString tabletModeConfig = cg.readPathEntry("TabletMode", QStringLiteral("auto"));
0144     const bool oldEffectiveTabletMode = effectiveTabletMode();
0145     if (tabletModeConfig == QStringLiteral("on")) {
0146         m_configuredMode = ConfiguredMode::On;
0147         if (!m_detecting) {
0148             Q_EMIT tabletModeAvailableChanged(true);
0149         }
0150     } else if (tabletModeConfig == QStringLiteral("off")) {
0151         m_configuredMode = ConfiguredMode::Off;
0152     } else {
0153         m_configuredMode = ConfiguredMode::Auto;
0154     }
0155     if (effectiveTabletMode() != oldEffectiveTabletMode) {
0156         Q_EMIT tabletModeChanged(effectiveTabletMode());
0157     }
0158 }
0159 
0160 void KWin::TabletModeManager::hasTabletModeInputChanged(bool set)
0161 {
0162     if (set) {
0163         input()->installInputEventSpy(new TabletModeSwitchEventSpy(this));
0164         setTabletModeAvailable(true);
0165     } else {
0166         auto spy = new TabletModeTouchpadRemovedSpy(this);
0167         connect(input(), &InputRedirection::hasTabletModeSwitchChanged, spy, [spy](bool set) {
0168             if (set) {
0169                 spy->deleteLater();
0170             }
0171         });
0172     }
0173 }
0174 
0175 bool TabletModeManager::isTabletModeAvailable() const
0176 {
0177     return m_detecting;
0178 }
0179 
0180 bool TabletModeManager::effectiveTabletMode() const
0181 {
0182     switch (m_configuredMode) {
0183     case ConfiguredMode::Off:
0184         return false;
0185     case ConfiguredMode::On:
0186         return true;
0187     case ConfiguredMode::Auto:
0188     default:
0189         if (!waylandServer()) {
0190             return false;
0191         } else {
0192             return m_isTabletMode;
0193         }
0194     }
0195 }
0196 
0197 bool TabletModeManager::isTablet() const
0198 {
0199     return m_isTabletMode;
0200 }
0201 
0202 void TabletModeManager::setIsTablet(bool tablet)
0203 {
0204     if (m_isTabletMode == tablet) {
0205         return;
0206     }
0207 
0208     const bool oldTabletMode = effectiveTabletMode();
0209     m_isTabletMode = tablet;
0210     if (effectiveTabletMode() != oldTabletMode) {
0211         Q_EMIT tabletModeChanged(effectiveTabletMode());
0212     }
0213 }
0214 
0215 void KWin::TabletModeManager::setTabletModeAvailable(bool detecting)
0216 {
0217     if (m_detecting == detecting) {
0218         return;
0219     }
0220 
0221     m_detecting = detecting;
0222     Q_EMIT tabletModeAvailableChanged(isTabletModeAvailable());
0223 }
0224 
0225 KWin::TabletModeManager::ConfiguredMode KWin::TabletModeManager::configuredMode() const
0226 {
0227     return m_configuredMode;
0228 }
0229 
0230 } // namespace KWin