File indexing completed on 2024-12-01 11:09:56

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 // own
0010 #include "globalshortcuts.h"
0011 // config
0012 #include <config-kwin.h>
0013 // kwin
0014 #include "gestures.h"
0015 #include "kwinglobals.h"
0016 #include "main.h"
0017 #include "utils/common.h"
0018 // KDE
0019 #include <KGlobalAccel/private/kglobalaccel_interface.h>
0020 #include <KGlobalAccel/private/kglobalacceld.h>
0021 // Qt
0022 #include <QAction>
0023 // system
0024 #include <signal.h>
0025 #include <variant>
0026 
0027 namespace KWin
0028 {
0029 GlobalShortcut::GlobalShortcut(Shortcut &&sc, QAction *action)
0030     : m_shortcut(sc)
0031     , m_action(action)
0032 {
0033     static const QMap<SwipeDirection, SwipeGesture::Direction> swipeDirs = {
0034         {SwipeDirection::Up, SwipeGesture::Direction::Up},
0035         {SwipeDirection::Down, SwipeGesture::Direction::Down},
0036         {SwipeDirection::Left, SwipeGesture::Direction::Left},
0037         {SwipeDirection::Right, SwipeGesture::Direction::Right},
0038     };
0039     static const QMap<PinchDirection, PinchGesture::Direction> pinchDirs = {
0040         {PinchDirection::Expanding, PinchGesture::Direction::Expanding},
0041         {PinchDirection::Contracting, PinchGesture::Direction::Contracting}};
0042     if (auto swipeGesture = std::get_if<SwipeShortcut>(&sc)) {
0043         m_swipeGesture.reset(new SwipeGesture());
0044         m_swipeGesture->setDirection(swipeDirs[swipeGesture->direction]);
0045         m_swipeGesture->setMaximumFingerCount(swipeGesture->fingerCount);
0046         m_swipeGesture->setMinimumFingerCount(swipeGesture->fingerCount);
0047         QObject::connect(m_swipeGesture.get(), &SwipeGesture::triggered, m_action, &QAction::trigger, Qt::QueuedConnection);
0048     } else if (auto rtSwipeGesture = std::get_if<RealtimeFeedbackSwipeShortcut>(&sc)) {
0049         m_swipeGesture.reset(new SwipeGesture());
0050         m_swipeGesture->setDirection(swipeDirs[rtSwipeGesture->direction]);
0051         m_swipeGesture->setMinimumDelta(QPointF(200, 200));
0052         m_swipeGesture->setMaximumFingerCount(rtSwipeGesture->fingerCount);
0053         m_swipeGesture->setMinimumFingerCount(rtSwipeGesture->fingerCount);
0054         QObject::connect(m_swipeGesture.get(), &SwipeGesture::triggered, m_action, &QAction::trigger, Qt::QueuedConnection);
0055         QObject::connect(m_swipeGesture.get(), &SwipeGesture::cancelled, m_action, &QAction::trigger, Qt::QueuedConnection);
0056         QObject::connect(m_swipeGesture.get(), &SwipeGesture::progress, [cb = rtSwipeGesture->progressCallback](qreal v) {
0057             cb(v);
0058         });
0059     } else if (auto pinchGesture = std::get_if<PinchShortcut>(&sc)) {
0060         m_pinchGesture.reset(new PinchGesture());
0061         m_pinchGesture->setDirection(pinchDirs[pinchGesture->direction]);
0062         m_pinchGesture->setMaximumFingerCount(pinchGesture->fingerCount);
0063         m_pinchGesture->setMinimumFingerCount(pinchGesture->fingerCount);
0064         QObject::connect(m_pinchGesture.get(), &PinchGesture::triggered, m_action, &QAction::trigger, Qt::QueuedConnection);
0065     } else if (auto rtPinchGesture = std::get_if<RealtimeFeedbackPinchShortcut>(&sc)) {
0066         m_pinchGesture.reset(new PinchGesture());
0067         m_pinchGesture->setDirection(pinchDirs[rtPinchGesture->direction]);
0068         m_pinchGesture->setMaximumFingerCount(rtPinchGesture->fingerCount);
0069         m_pinchGesture->setMinimumFingerCount(rtPinchGesture->fingerCount);
0070         QObject::connect(m_pinchGesture.get(), &PinchGesture::triggered, m_action, &QAction::trigger, Qt::QueuedConnection);
0071         QObject::connect(m_pinchGesture.get(), &PinchGesture::cancelled, m_action, &QAction::trigger, Qt::QueuedConnection);
0072         QObject::connect(m_pinchGesture.get(), &PinchGesture::progress, [cb = rtPinchGesture->scaleCallback](qreal v) {
0073             cb(v);
0074         });
0075     }
0076 }
0077 
0078 GlobalShortcut::~GlobalShortcut()
0079 {
0080 }
0081 
0082 QAction *GlobalShortcut::action() const
0083 {
0084     return m_action;
0085 }
0086 
0087 void GlobalShortcut::invoke() const
0088 {
0089     QMetaObject::invokeMethod(m_action, &QAction::trigger, Qt::QueuedConnection);
0090 }
0091 
0092 const Shortcut &GlobalShortcut::shortcut() const
0093 {
0094     return m_shortcut;
0095 }
0096 
0097 SwipeGesture *GlobalShortcut::swipeGesture() const
0098 {
0099     return m_swipeGesture.get();
0100 }
0101 
0102 PinchGesture *GlobalShortcut::pinchGesture() const
0103 {
0104     return m_pinchGesture.get();
0105 }
0106 
0107 GlobalShortcutsManager::GlobalShortcutsManager(QObject *parent)
0108     : QObject(parent)
0109     , m_touchpadGestureRecognizer(new GestureRecognizer(this))
0110     , m_touchscreenGestureRecognizer(new GestureRecognizer(this))
0111 {
0112 }
0113 
0114 GlobalShortcutsManager::~GlobalShortcutsManager()
0115 {
0116 }
0117 
0118 void GlobalShortcutsManager::init()
0119 {
0120     if (kwinApp()->shouldUseWaylandForCompositing()) {
0121         qputenv("KGLOBALACCELD_PLATFORM", QByteArrayLiteral("org.kde.kwin"));
0122         m_kglobalAccel = std::make_unique<KGlobalAccelD>();
0123         if (!m_kglobalAccel->init()) {
0124             qCDebug(KWIN_CORE) << "Init of kglobalaccel failed";
0125             m_kglobalAccel.reset();
0126         } else {
0127             qCDebug(KWIN_CORE) << "KGlobalAcceld inited";
0128         }
0129     }
0130 }
0131 
0132 void GlobalShortcutsManager::objectDeleted(QObject *object)
0133 {
0134     auto it = m_shortcuts.begin();
0135     while (it != m_shortcuts.end()) {
0136         if (it->action() == object) {
0137             it = m_shortcuts.erase(it);
0138         } else {
0139             ++it;
0140         }
0141     }
0142 }
0143 
0144 bool GlobalShortcutsManager::addIfNotExists(GlobalShortcut sc, DeviceType device)
0145 {
0146     for (const auto &cs : std::as_const(m_shortcuts)) {
0147         if (sc.shortcut() == cs.shortcut()) {
0148             return false;
0149         }
0150     }
0151 
0152     const auto &recognizer = device == DeviceType::Touchpad ? m_touchpadGestureRecognizer : m_touchscreenGestureRecognizer;
0153     if (std::holds_alternative<SwipeShortcut>(sc.shortcut()) || std::holds_alternative<RealtimeFeedbackSwipeShortcut>(sc.shortcut())) {
0154         recognizer->registerSwipeGesture(sc.swipeGesture());
0155     } else if (std::holds_alternative<PinchShortcut>(sc.shortcut()) || std::holds_alternative<RealtimeFeedbackPinchShortcut>(sc.shortcut())) {
0156         recognizer->registerPinchGesture(sc.pinchGesture());
0157     }
0158     connect(sc.action(), &QAction::destroyed, this, &GlobalShortcutsManager::objectDeleted);
0159     m_shortcuts.push_back(std::move(sc));
0160     return true;
0161 }
0162 
0163 void GlobalShortcutsManager::registerPointerShortcut(QAction *action, Qt::KeyboardModifiers modifiers, Qt::MouseButtons pointerButtons)
0164 {
0165     addIfNotExists(GlobalShortcut(PointerButtonShortcut{modifiers, pointerButtons}, action));
0166 }
0167 
0168 void GlobalShortcutsManager::registerAxisShortcut(QAction *action, Qt::KeyboardModifiers modifiers, PointerAxisDirection axis)
0169 {
0170     addIfNotExists(GlobalShortcut(PointerAxisShortcut{modifiers, axis}, action));
0171 }
0172 
0173 void GlobalShortcutsManager::registerTouchpadSwipe(QAction *action, SwipeDirection direction, uint fingerCount)
0174 {
0175     addIfNotExists(GlobalShortcut(SwipeShortcut{DeviceType::Touchpad, direction, fingerCount}, action), DeviceType::Touchpad);
0176 }
0177 
0178 void GlobalShortcutsManager::registerRealtimeTouchpadSwipe(QAction *action, std::function<void(qreal)> progressCallback, SwipeDirection direction, uint fingerCount)
0179 {
0180     addIfNotExists(GlobalShortcut(RealtimeFeedbackSwipeShortcut{DeviceType::Touchpad, direction, progressCallback, fingerCount}, action), DeviceType::Touchpad);
0181 }
0182 
0183 void GlobalShortcutsManager::registerTouchpadPinch(QAction *action, PinchDirection direction, uint fingerCount)
0184 {
0185     addIfNotExists(GlobalShortcut(PinchShortcut{direction, fingerCount}, action), DeviceType::Touchpad);
0186 }
0187 
0188 void GlobalShortcutsManager::registerRealtimeTouchpadPinch(QAction *onUp, std::function<void(qreal)> progressCallback, PinchDirection direction, uint fingerCount)
0189 {
0190     addIfNotExists(GlobalShortcut(RealtimeFeedbackPinchShortcut{direction, progressCallback, fingerCount}, onUp), DeviceType::Touchpad);
0191 }
0192 
0193 void GlobalShortcutsManager::registerTouchscreenSwipe(QAction *action, std::function<void(qreal)> progressCallback, SwipeDirection direction, uint fingerCount)
0194 {
0195     addIfNotExists(GlobalShortcut(RealtimeFeedbackSwipeShortcut{DeviceType::Touchscreen, direction, progressCallback, fingerCount}, action), DeviceType::Touchscreen);
0196 }
0197 
0198 void GlobalShortcutsManager::forceRegisterTouchscreenSwipe(QAction *action, std::function<void(qreal)> progressCallback, SwipeDirection direction, uint fingerCount)
0199 {
0200     GlobalShortcut shortcut{RealtimeFeedbackSwipeShortcut{DeviceType::Touchscreen, direction, progressCallback, fingerCount}, action};
0201     const auto it = std::find_if(m_shortcuts.begin(), m_shortcuts.end(), [&shortcut](const auto &s) {
0202         return shortcut.shortcut() == s.shortcut();
0203     });
0204     if (it != m_shortcuts.end()) {
0205         m_shortcuts.erase(it);
0206     }
0207     m_touchscreenGestureRecognizer->registerSwipeGesture(shortcut.swipeGesture());
0208     connect(shortcut.action(), &QAction::destroyed, this, &GlobalShortcutsManager::objectDeleted);
0209     m_shortcuts.push_back(std::move(shortcut));
0210 }
0211 
0212 bool GlobalShortcutsManager::processKey(Qt::KeyboardModifiers mods, int keyQt)
0213 {
0214     if (m_kglobalAccelInterface) {
0215         if (!keyQt && !mods) {
0216             return false;
0217         }
0218         auto check = [this](Qt::KeyboardModifiers mods, int keyQt) {
0219             bool retVal = false;
0220             QMetaObject::invokeMethod(m_kglobalAccelInterface,
0221                                       "checkKeyPressed",
0222                                       Qt::DirectConnection,
0223                                       Q_RETURN_ARG(bool, retVal),
0224                                       Q_ARG(int, int(mods) | keyQt));
0225             return retVal;
0226         };
0227         if (check(mods, keyQt)) {
0228             return true;
0229         } else if (keyQt == Qt::Key_Backtab) {
0230             // KGlobalAccel on X11 has some workaround for Backtab
0231             // see kglobalaccel/src/runtime/plugins/xcb/kglobalccel_x11.cpp method x11KeyPress
0232             // Apparently KKeySequenceWidget captures Shift+Tab instead of Backtab
0233             // thus if the key is backtab we should adjust to add shift again and use tab
0234             // in addition KWin registers the shortcut incorrectly as Alt+Shift+Backtab
0235             // this should be changed to either Alt+Backtab or Alt+Shift+Tab to match KKeySequenceWidget
0236             // trying the variants
0237             if (check(mods | Qt::ShiftModifier, keyQt)) {
0238                 return true;
0239             }
0240             if (check(mods | Qt::ShiftModifier, Qt::Key_Tab)) {
0241                 return true;
0242             }
0243         }
0244     }
0245     return false;
0246 }
0247 
0248 bool GlobalShortcutsManager::processKeyRelease(Qt::KeyboardModifiers mods, int keyQt)
0249 {
0250     if (m_kglobalAccelInterface) {
0251         QMetaObject::invokeMethod(m_kglobalAccelInterface,
0252                                   "checkKeyReleased",
0253                                   Qt::DirectConnection,
0254                                   Q_ARG(int, int(mods) | keyQt));
0255     }
0256     return false;
0257 }
0258 
0259 template<typename ShortcutKind, typename... Args>
0260 bool match(QVector<GlobalShortcut> &shortcuts, Args... args)
0261 {
0262     for (auto &sc : shortcuts) {
0263         if (std::holds_alternative<ShortcutKind>(sc.shortcut())) {
0264             if (std::get<ShortcutKind>(sc.shortcut()) == ShortcutKind{args...}) {
0265                 sc.invoke();
0266                 return true;
0267             }
0268         }
0269     }
0270     return false;
0271 }
0272 
0273 // TODO(C++20): use ranges for a nicer way of filtering by shortcut type
0274 bool GlobalShortcutsManager::processPointerPressed(Qt::KeyboardModifiers mods, Qt::MouseButtons pointerButtons)
0275 {
0276     return match<PointerButtonShortcut>(m_shortcuts, mods, pointerButtons);
0277 }
0278 
0279 bool GlobalShortcutsManager::processAxis(Qt::KeyboardModifiers mods, PointerAxisDirection axis)
0280 {
0281     return match<PointerAxisShortcut>(m_shortcuts, mods, axis);
0282 }
0283 
0284 void GlobalShortcutsManager::processSwipeStart(DeviceType device, uint fingerCount)
0285 {
0286     if (device == DeviceType::Touchpad) {
0287         m_touchpadGestureRecognizer->startSwipeGesture(fingerCount);
0288     } else {
0289         m_touchscreenGestureRecognizer->startSwipeGesture(fingerCount);
0290     }
0291 }
0292 
0293 void GlobalShortcutsManager::processSwipeUpdate(DeviceType device, const QPointF &delta)
0294 {
0295     if (device == DeviceType::Touchpad) {
0296         m_touchpadGestureRecognizer->updateSwipeGesture(delta);
0297     } else {
0298         m_touchscreenGestureRecognizer->updateSwipeGesture(delta);
0299     }
0300 }
0301 
0302 void GlobalShortcutsManager::processSwipeCancel(DeviceType device)
0303 {
0304     if (device == DeviceType::Touchpad) {
0305         m_touchpadGestureRecognizer->cancelSwipeGesture();
0306     } else {
0307         m_touchscreenGestureRecognizer->cancelSwipeGesture();
0308     }
0309 }
0310 
0311 void GlobalShortcutsManager::processSwipeEnd(DeviceType device)
0312 {
0313     if (device == DeviceType::Touchpad) {
0314         m_touchpadGestureRecognizer->endSwipeGesture();
0315     } else {
0316         m_touchscreenGestureRecognizer->endSwipeGesture();
0317     }
0318     // TODO: cancel on Wayland Seat if one triggered
0319 }
0320 
0321 void GlobalShortcutsManager::processPinchStart(uint fingerCount)
0322 {
0323     m_touchpadGestureRecognizer->startPinchGesture(fingerCount);
0324 }
0325 
0326 void GlobalShortcutsManager::processPinchUpdate(qreal scale, qreal angleDelta, const QPointF &delta)
0327 {
0328     m_touchpadGestureRecognizer->updatePinchGesture(scale, angleDelta, delta);
0329 }
0330 
0331 void GlobalShortcutsManager::processPinchCancel()
0332 {
0333     m_touchpadGestureRecognizer->cancelPinchGesture();
0334 }
0335 
0336 void GlobalShortcutsManager::processPinchEnd()
0337 {
0338     m_touchpadGestureRecognizer->endPinchGesture();
0339 }
0340 
0341 } // namespace