add global touchscreen gestures

Swipe with three fingers
- left to switch to the previous virtual desktop
- right to switch to the next virtual desktop
- up and down to toggle the overview

CCBUG: 439925
This commit is contained in:
Xaver Hugl 2022-03-16 19:00:24 +01:00
parent 0aab6e3ee5
commit 8914a6c50e
11 changed files with 213 additions and 34 deletions

View file

@ -38,6 +38,10 @@ void InputRedirection::registerTouchpadSwipeShortcut(SwipeDirection, uint finger
{
}
void InputRedirection::registerTouchscreenSwipeShortcut(SwipeDirection, uint fingerCount, QAction*)
{
}
}
Q_DECLARE_METATYPE(Qt::Orientation)

View file

@ -792,6 +792,11 @@ void EffectsHandlerImpl::registerTouchpadPinchShortcut(PinchDirection direction,
input()->registerTouchpadPinchShortcut(direction, fingerCount, action);
}
void EffectsHandlerImpl::registerTouchscreenSwipeShortcut(SwipeDirection direction, uint fingerCount, QAction *action)
{
input()->registerTouchscreenSwipeShortcut(direction, fingerCount, action);
}
void *EffectsHandlerImpl::getProxy(QString name)
{
for (QVector<EffectPair>::const_iterator it = loaded_effects.constBegin(); it != loaded_effects.constEnd(); ++it)

View file

@ -117,6 +117,7 @@ public:
void registerTouchpadSwipeShortcut(SwipeDirection direction, uint fingerCount, QAction *action) override;
void registerRealtimeTouchpadPinchShortcut(PinchDirection dir, uint fingerCount, QAction *onUp, std::function<void(qreal)> progressCallback) override;
void registerTouchpadPinchShortcut(PinchDirection direction, uint fingerCount, QAction *action) override;
void registerTouchscreenSwipeShortcut(SwipeDirection direction, uint fingerCount, QAction *action) override;
void *getProxy(QString name) override;
void startMousePolling() override;
void stopMousePolling() override;

View file

@ -40,6 +40,8 @@ OverviewEffect::OverviewEffect()
m_toggleShortcut = KGlobalAccel::self()->shortcut(m_toggleAction);
effects->registerGlobalShortcut({defaultToggleShortcut}, m_toggleAction);
effects->registerTouchpadSwipeShortcut(SwipeDirection::Up, 4, m_toggleAction);
effects->registerTouchpadSwipeShortcut(SwipeDirection::Up, 3, m_toggleAction);
effects->registerTouchpadSwipeShortcut(SwipeDirection::Down, 3, m_toggleAction);
connect(effects, &EffectsHandler::screenAboutToLock, this, &OverviewEffect::realDeactivate);

View file

@ -106,7 +106,8 @@ PinchGesture *GlobalShortcut::pinchGesture() const
GlobalShortcutsManager::GlobalShortcutsManager(QObject *parent)
: QObject(parent)
, m_gestureRecognizer(new GestureRecognizer(this))
, m_touchpadGestureRecognizer(new GestureRecognizer(this))
, m_touchscreenGestureRecognizer(new GestureRecognizer(this))
{
}
@ -141,7 +142,7 @@ void GlobalShortcutsManager::objectDeleted(QObject *object)
}
}
bool GlobalShortcutsManager::addIfNotExists(GlobalShortcut sc)
bool GlobalShortcutsManager::addIfNotExists(GlobalShortcut sc, DeviceType device)
{
for (const auto &cs : qAsConst(m_shortcuts)) {
if (sc.shortcut() == cs.shortcut()) {
@ -149,10 +150,11 @@ bool GlobalShortcutsManager::addIfNotExists(GlobalShortcut sc)
}
}
const auto &recognizer = device == DeviceType::Touchpad ? m_touchpadGestureRecognizer : m_touchscreenGestureRecognizer;
if (std::holds_alternative<SwipeShortcut>(sc.shortcut()) || std::holds_alternative<RealtimeFeedbackSwipeShortcut>(sc.shortcut())) {
m_gestureRecognizer->registerSwipeGesture(sc.swipeGesture());
recognizer->registerSwipeGesture(sc.swipeGesture());
} else if (std::holds_alternative<PinchShortcut>(sc.shortcut()) || std::holds_alternative<RealtimeFeedbackPinchShortcut>(sc.shortcut())) {
m_gestureRecognizer->registerPinchGesture(sc.pinchGesture());
recognizer->registerPinchGesture(sc.pinchGesture());
}
connect(sc.action(), &QAction::destroyed, this, &GlobalShortcutsManager::objectDeleted);
m_shortcuts.push_back(std::move(sc));
@ -171,22 +173,27 @@ void GlobalShortcutsManager::registerAxisShortcut(QAction *action, Qt::KeyboardM
void GlobalShortcutsManager::registerTouchpadSwipe(QAction *action, SwipeDirection direction, uint fingerCount)
{
addIfNotExists(GlobalShortcut(SwipeShortcut{direction, fingerCount}, action));
addIfNotExists(GlobalShortcut(SwipeShortcut{DeviceType::Touchpad, direction, fingerCount}, action), DeviceType::Touchpad);
}
void GlobalShortcutsManager::registerRealtimeTouchpadSwipe(QAction *action, std::function<void(qreal)> progressCallback, SwipeDirection direction, uint fingerCount)
{
addIfNotExists(GlobalShortcut(RealtimeFeedbackSwipeShortcut{direction, progressCallback, fingerCount}, action));
addIfNotExists(GlobalShortcut(RealtimeFeedbackSwipeShortcut{DeviceType::Touchpad, direction, progressCallback, fingerCount}, action)), DeviceType::Touchpad;
}
void GlobalShortcutsManager::registerTouchpadPinch(QAction *action, PinchDirection direction, uint fingerCount)
{
addIfNotExists(GlobalShortcut(PinchShortcut{direction, fingerCount}, action));
addIfNotExists(GlobalShortcut(PinchShortcut{direction, fingerCount}, action), DeviceType::Touchpad);
}
void GlobalShortcutsManager::registerRealtimeTouchpadPinch(QAction *onUp, std::function<void(qreal)> progressCallback, PinchDirection direction, uint fingerCount)
{
addIfNotExists(GlobalShortcut(RealtimeFeedbackPinchShortcut{direction, progressCallback, fingerCount}, onUp));
addIfNotExists(GlobalShortcut(RealtimeFeedbackPinchShortcut{direction, progressCallback, fingerCount}, onUp), DeviceType::Touchpad);
}
void GlobalShortcutsManager::registerTouchscreenSwipe(QAction *action, SwipeDirection direction, uint fingerCount)
{
addIfNotExists(GlobalShortcut(SwipeShortcut{DeviceType::Touchscreen, direction, fingerCount}, action), DeviceType::Touchscreen);
}
bool GlobalShortcutsManager::processKey(Qt::KeyboardModifiers mods, int keyQt)
@ -250,45 +257,61 @@ bool GlobalShortcutsManager::processAxis(Qt::KeyboardModifiers mods, PointerAxis
return match<PointerAxisShortcut>(m_shortcuts, mods, axis);
}
void GlobalShortcutsManager::processSwipeStart(uint fingerCount)
void GlobalShortcutsManager::processSwipeStart(DeviceType device, uint fingerCount)
{
m_gestureRecognizer->startSwipeGesture(fingerCount);
if (device == DeviceType::Touchpad) {
m_touchpadGestureRecognizer->startSwipeGesture(fingerCount);
} else {
m_touchscreenGestureRecognizer->startSwipeGesture(fingerCount);
}
}
void GlobalShortcutsManager::processSwipeUpdate(const QSizeF &delta)
void GlobalShortcutsManager::processSwipeUpdate(DeviceType device, const QSizeF &delta)
{
m_gestureRecognizer->updateSwipeGesture(delta);
if (device == DeviceType::Touchpad) {
m_touchpadGestureRecognizer->updateSwipeGesture(delta);
} else {
m_touchscreenGestureRecognizer->updateSwipeGesture(delta);
}
}
void GlobalShortcutsManager::processSwipeCancel()
void GlobalShortcutsManager::processSwipeCancel(DeviceType device)
{
m_gestureRecognizer->cancelSwipeGesture();
if (device == DeviceType::Touchpad) {
m_touchpadGestureRecognizer->cancelSwipeGesture();
} else {
m_touchscreenGestureRecognizer->cancelSwipeGesture();
}
}
void GlobalShortcutsManager::processSwipeEnd()
void GlobalShortcutsManager::processSwipeEnd(DeviceType device)
{
m_gestureRecognizer->endSwipeGesture();
if (device == DeviceType::Touchpad) {
m_touchpadGestureRecognizer->endSwipeGesture();
} else {
m_touchscreenGestureRecognizer->endSwipeGesture();
}
// TODO: cancel on Wayland Seat if one triggered
}
void GlobalShortcutsManager::processPinchStart(uint fingerCount)
{
m_gestureRecognizer->startPinchGesture(fingerCount);
m_touchpadGestureRecognizer->startPinchGesture(fingerCount);
}
void GlobalShortcutsManager::processPinchUpdate(qreal scale, qreal angleDelta, const QSizeF &delta)
{
m_gestureRecognizer->updatePinchGesture(scale, angleDelta, delta);
m_touchpadGestureRecognizer->updatePinchGesture(scale, angleDelta, delta);
}
void GlobalShortcutsManager::processPinchCancel()
{
m_gestureRecognizer->cancelPinchGesture();
m_touchpadGestureRecognizer->cancelPinchGesture();
}
void GlobalShortcutsManager::processPinchEnd()
{
m_gestureRecognizer->endPinchGesture();
m_touchpadGestureRecognizer->endPinchGesture();
}
} // namespace

View file

@ -25,6 +25,11 @@ class SwipeGesture;
class PinchGesture;
class GestureRecognizer;
enum class DeviceType {
Touchpad,
Touchscreen
};
/**
* @brief Manager for the global shortcut system inside KWin.
*
@ -67,6 +72,8 @@ public:
void registerRealtimeTouchpadPinch(QAction *onUp, std::function<void(qreal)> progressCallback, PinchDirection direction, uint fingerCount = 4);
void registerTouchscreenSwipe(QAction *action, SwipeDirection direction, uint fingerCount);
/**
* @brief Processes a key event to decide whether a shortcut needs to be triggered.
*
@ -93,10 +100,10 @@ public:
*/
bool processAxis(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis);
void processSwipeStart(uint fingerCount);
void processSwipeUpdate(const QSizeF &delta);
void processSwipeCancel();
void processSwipeEnd();
void processSwipeStart(DeviceType device, uint fingerCount);
void processSwipeUpdate(DeviceType device, const QSizeF &delta);
void processSwipeCancel(DeviceType device);
void processSwipeEnd(DeviceType device);
void processPinchStart(uint fingerCount);
void processPinchUpdate(qreal scale, qreal angleDelta, const QSizeF &delta);
@ -110,13 +117,14 @@ public:
private:
void objectDeleted(QObject *object);
bool addIfNotExists(GlobalShortcut sc);
bool addIfNotExists(GlobalShortcut sc, DeviceType device = DeviceType::Touchpad);
QVector<GlobalShortcut> m_shortcuts;
KGlobalAccelD *m_kglobalAccel = nullptr;
KGlobalAccelInterface *m_kglobalAccelInterface = nullptr;
GestureRecognizer *m_gestureRecognizer;
QScopedPointer<GestureRecognizer> m_touchpadGestureRecognizer;
QScopedPointer<GestureRecognizer> m_touchscreenGestureRecognizer;
};
struct KeyboardShortcut
@ -147,15 +155,17 @@ struct PointerAxisShortcut
};
struct SwipeShortcut
{
DeviceType device;
SwipeDirection direction;
uint fingerCount;
bool operator==(const SwipeShortcut &rhs) const
{
return direction == rhs.direction && fingerCount == rhs.fingerCount;
return direction == rhs.direction && fingerCount == rhs.fingerCount && device == rhs.device;
}
};
struct RealtimeFeedbackSwipeShortcut
{
DeviceType device;
SwipeDirection direction;
std::function<void(qreal)> progressCallback;
uint fingerCount;
@ -163,7 +173,7 @@ struct RealtimeFeedbackSwipeShortcut
template<typename T>
bool operator==(const T &rhs) const
{
return direction == rhs.direction && fingerCount == rhs.fingerCount;
return direction == rhs.direction && fingerCount == rhs.fingerCount && device == rhs.device;
}
};

View file

@ -42,6 +42,8 @@
#include "wayland_server.h"
#include "workspace.h"
#include "xwl/xwayland_interface.h"
#include "abstract_output.h"
#include <KDecoration2/Decoration>
#include <KGlobalAccel>
#include <KLocalizedString>
@ -138,6 +140,16 @@ bool InputEventFilter::touchUp(qint32 id, quint32 time)
return false;
}
bool InputEventFilter::touchCancel()
{
return false;
}
bool InputEventFilter::touchFrame()
{
return false;
}
bool InputEventFilter::pinchGestureBegin(int fingerCount, quint32 time)
{
Q_UNUSED(fingerCount)
@ -995,25 +1007,25 @@ public:
bool swipeGestureBegin(int fingerCount, quint32 time) override
{
Q_UNUSED(time)
input()->shortcuts()->processSwipeStart(fingerCount);
input()->shortcuts()->processSwipeStart(DeviceType::Touchpad, fingerCount);
return false;
}
bool swipeGestureUpdate(const QSizeF &delta, quint32 time) override
{
Q_UNUSED(time)
input()->shortcuts()->processSwipeUpdate(delta);
input()->shortcuts()->processSwipeUpdate(DeviceType::Touchpad, delta);
return false;
}
bool swipeGestureCancelled(quint32 time) override
{
Q_UNUSED(time)
input()->shortcuts()->processSwipeCancel();
input()->shortcuts()->processSwipeCancel(DeviceType::Touchpad);
return false;
}
bool swipeGestureEnd(quint32 time) override
{
Q_UNUSED(time)
input()->shortcuts()->processSwipeEnd();
input()->shortcuts()->processSwipeEnd(DeviceType::Touchpad);
return false;
}
bool pinchGestureBegin(int fingerCount, quint32 time) override
@ -1042,8 +1054,99 @@ public:
input()->shortcuts()->processPinchCancel();
return false;
}
bool touchDown(qint32 id, const QPointF &pos, quint32 time) override {
if (m_gestureTaken) {
input()->shortcuts()->processSwipeCancel(DeviceType::Touchscreen);
m_gestureCancelled = true;
return true;
} else {
m_touchPoints.insert(id, {pos, QSize()});
if (m_touchPoints.count() == 1) {
m_lastTouchDownTime = time;
} else {
if (time - m_lastTouchDownTime > 250) {
m_gestureCancelled = true;
return false;
}
m_lastTouchDownTime = time;
auto output = kwinApp()->platform()->outputAt(pos.toPoint());
float xfactor = output->physicalSize().width() / (float)output->geometry().width();
float yfactor = output->physicalSize().height() / (float)output->geometry().height();
bool distanceMatch = std::any_of(m_touchPoints.constBegin(), m_touchPoints.constEnd(), [pos, xfactor, yfactor](const auto &point) {
QPointF p = pos - point.pos;
return std::abs(xfactor * p.x()) + std::abs(yfactor * p.y()) < 50;
});
if (!distanceMatch) {
m_gestureCancelled = true;
return false;
}
}
if (m_touchPoints.count() >= 3 && !m_gestureCancelled) {
m_gestureTaken = true;
input()->processFilters(std::bind(&InputEventFilter::touchCancel, std::placeholders::_1));
input()->shortcuts()->processSwipeStart(DeviceType::Touchscreen, m_touchPoints.count());
return true;
}
}
return false;
}
bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override {
Q_UNUSED(time);
if (m_gestureTaken) {
if (m_gestureCancelled) {
return true;
}
// only track one finger for simplicity
if (id != m_touchPoints.begin().key()) {
return true;
}
auto &point = m_touchPoints[id];
QPointF dist = pos - point.pos;
auto output = kwinApp()->platform()->outputAt(pos.toPoint());
float xfactor = output->physicalSize().width() / (float)output->geometry().width();
float yfactor = output->physicalSize().height() / (float)output->geometry().height();
QSize delta = QSize(xfactor * dist.x(), yfactor * dist.y()) * 10;
point.distance += delta;
point.pos = pos;
input()->shortcuts()->processSwipeUpdate(DeviceType::Touchscreen, delta);
return true;
}
return false;
}
bool touchUp(qint32 id, quint32 time) override {
Q_UNUSED(time);
m_touchPoints.remove(id);
if (m_gestureTaken) {
if (!m_gestureCancelled) {
input()->shortcuts()->processSwipeEnd(DeviceType::Touchscreen);
m_gestureCancelled = true;
}
m_gestureTaken &= m_touchPoints.count() > 0;
m_gestureCancelled &= m_gestureTaken;
m_touchGestureCancelSent &= m_gestureTaken;
return true;
}
return false;
}
bool touchFrame() override {
return m_gestureTaken;
}
private:
struct TouchPoint {
QPointF pos;
QSize distance;
};
bool m_gestureTaken = false;
bool m_gestureCancelled = false;
bool m_touchGestureCancelSent = false;
uint32_t m_lastTouchDownTime = 0;
QPointF m_lastAverageDistance;
QMap<int32_t, TouchPoint> m_touchPoints;
QTimer *m_powerDown = nullptr;
};
@ -1753,6 +1856,16 @@ public:
seat->notifyTouchUp(id);
return true;
}
bool touchCancel() override
{
waylandServer()->seat()->notifyTouchCancel();
return true;
}
bool touchFrame() override
{
waylandServer()->seat()->notifyTouchFrame();
return true;
}
bool pinchGestureBegin(int fingerCount, quint32 time) override
{
auto seat = waylandServer()->seat();
@ -2681,7 +2794,6 @@ void InputRedirection::setupInputFilters()
if (hasGlobalShortcutSupport) {
installInputEventFilter(new ScreenEdgeInputFilter);
}
installInputEventFilter(new EffectsFilter);
installInputEventFilter(new MoveResizeFilter);
#if KWIN_BUILD_TABBOX
installInputEventFilter(new TabBoxInputFilter);
@ -2689,6 +2801,7 @@ void InputRedirection::setupInputFilters()
if (hasGlobalShortcutSupport) {
installInputEventFilter(new GlobalShortcutFilter);
}
installInputEventFilter(new EffectsFilter);
installInputEventFilter(new PopupInputFilter);
installInputEventFilter(new DecorationEventFilter);
installInputEventFilter(new WindowActionInputFilter);
@ -3068,6 +3181,11 @@ void InputRedirection::registerGlobalAccel(KGlobalAccelInterface *interface)
m_shortcuts->setKGlobalAccelInterface(interface);
}
void InputRedirection::registerTouchscreenSwipeShortcut(SwipeDirection direction, uint fingerCount, QAction *action)
{
m_shortcuts->registerTouchscreenSwipe(action, direction, fingerCount);
}
void InputRedirection::warpPointer(const QPointF &pos)
{
m_pointer->warp(pos);

View file

@ -139,6 +139,7 @@ public:
void registerRealtimeTouchpadSwipeShortcut(SwipeDirection direction, uint fingerCount, QAction *onUp, std::function<void(qreal)> progressCallback);
void registerTouchpadPinchShortcut(PinchDirection direction, uint fingerCount, QAction *action);
void registerRealtimeTouchpadPinchShortcut(PinchDirection direction, uint fingerCount, QAction *onUp, std::function<void(qreal)> progressCallback);
void registerTouchscreenSwipeShortcut(SwipeDirection direction, uint fingerCount, QAction *action);
void registerGlobalAccel(KGlobalAccelInterface *interface);
bool supportsPointerWarping() const;
@ -398,6 +399,8 @@ public:
virtual bool touchDown(qint32 id, const QPointF &pos, quint32 time);
virtual bool touchMotion(qint32 id, const QPointF &pos, quint32 time);
virtual bool touchUp(qint32 id, quint32 time);
virtual bool touchCancel();
virtual bool touchFrame();
virtual bool pinchGestureBegin(int fingerCount, quint32 time);
virtual bool pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time);

View file

@ -956,6 +956,16 @@ public:
virtual void registerRealtimeTouchpadPinchShortcut(PinchDirection dir, uint fingerCount, QAction *onUp, std::function<void(qreal)> progressCallback) = 0;
virtual void registerTouchpadPinchShortcut(PinchDirection direction, uint fingerCount, QAction *action) = 0;
/**
* @brief Registers a global touchscreen swipe gesture shortcut with the provided @p action.
*
* @param direction The direction for the swipe
* @param action The action which gets triggered when the gesture triggers
* @since 5.25
*/
virtual void registerTouchscreenSwipeShortcut(SwipeDirection direction, uint fingerCount, QAction *action) = 0;
/**
* Retrieve the proxy class for an effect if it has one. Will return NULL if
* the effect isn't loaded or doesn't have a proxy class.

View file

@ -207,7 +207,7 @@ void TouchInputRedirection::frame()
if (!inited() || !waylandServer()->seat()->hasTouch()) {
return;
}
waylandServer()->seat()->notifyTouchFrame();
input()->processFilters(std::bind(&InputEventFilter::touchFrame, std::placeholders::_1));
}
}

View file

@ -798,6 +798,9 @@ void VirtualDesktopManager::initShortcuts()
findChild<QAction *>(QStringLiteral("Switch to Next Desktop")));
input()->registerAxisShortcut(Qt::ControlModifier | Qt::AltModifier, PointerAxisUp,
findChild<QAction *>(QStringLiteral("Switch to Previous Desktop")));
input()->registerTouchscreenSwipeShortcut(SwipeDirection::Left, 3, nextAction);
input()->registerTouchscreenSwipeShortcut(SwipeDirection::Right, 3, previousAction);
}
void VirtualDesktopManager::initSwitchToShortcuts()