File indexing completed on 2024-04-28 05:30:17

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