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