From 64ce6259a92e63e610db459e7eddf9aeb6b6a159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Thu, 30 Mar 2017 07:08:38 +0200 Subject: [PATCH] Introduce dedicated actions for touch screen swipe gestures Summary: The new touch gestures activated for the same actions as configured for mouse pointer actions. This has disadvantages as the only configured default screen edge action cannot be triggered (corner) on touch. On the other hand setting a default touch gesture would be rather annoying with pointer as a default. So overall it makes sense to split the actions and have dedicated pointer and dedicated touch actions. This change introduces the first part of it and splits the handling in general. We now have: Activates for pointer and touch: * client (auto-hiding panels) Activates for pointer only: * the configured action * virtual desktop switching * callbacks Activates for touch only: * the new touch action The touch actions are implemented similar to the pointer actions which slight improvements in the code which will be backported to the pointer actions. Introducing callbacks will be the next step. I plan to do it a little bit different by using QActions as that's what KWin internally uses for everything except screen edges. Test Plan: Manual testing and improved auto tests Reviewers: #kwin, #plasma Subscribers: plasma-devel, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D5252 --- autotests/test_screen_edges.cpp | 96 +++++++++++++++++++++ screenedge.cpp | 146 +++++++++++++++++++++++++++++--- screenedge.h | 22 +++-- 3 files changed, 248 insertions(+), 16 deletions(-) diff --git a/autotests/test_screen_edges.cpp b/autotests/test_screen_edges.cpp index e60856f8da..1e91df8534 100644 --- a/autotests/test_screen_edges.cpp +++ b/autotests/test_screen_edges.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . #include "../atoms.h" #include "../cursor.h" #include "../input.h" +#include "../gestures.h" #include "../main.h" #include "../screenedge.h" #include "../screens.h" @@ -128,6 +129,7 @@ private Q_SLOTS: void testPushBack(); void testFullScreenBlocking(); void testClientEdge(); + void testTouchEdge(); }; void TestScreenEdges::initTestCase() @@ -334,6 +336,8 @@ void TestScreenEdges::testCreatingInitialEdges() QCOMPARE(edges.size(), 8); for (auto e : edges) { QVERIFY(e->isReserved()); + QCOMPARE(e->activatesForPointer(), true); + QCOMPARE(e->activatesForTouchGesture(), false); } static_cast(screens())->setGeometries(QList{QRect{0, 0, 1024, 768}}); @@ -382,6 +386,8 @@ void TestScreenEdges::testCreatingInitialEdges() for (int i = 0; i < 8; ++i) { auto e = edges.at(i); QVERIFY(!e->isReserved()); + QCOMPARE(e->activatesForPointer(), false); + QCOMPARE(e->activatesForTouchGesture(), false); QCOMPARE(e->approachGeometry(), expectedGeometries.at(i*2+1)); } } @@ -415,6 +421,8 @@ void TestScreenEdges::testCallback() QCOMPARE(edges.size(), 10); for (auto e: edges) { QVERIFY(e->isReserved()); + QCOMPARE(e->activatesForPointer(), true); + QCOMPARE(e->activatesForTouchGesture(), false); } auto it = std::find_if(edges.constBegin(), edges.constEnd(), [](Edge *e) { return e->isScreenEdge() && e->isLeft() && e->approachGeometry().bottom() < 768; @@ -523,6 +531,8 @@ void TestScreenEdges::testCallback() s->unreserve(ElectricLeft, &callback); for (auto e: s->findChildren(QString(), Qt::FindDirectChildrenOnly)) { QVERIFY(!e->isReserved()); + QCOMPARE(e->activatesForPointer(), false); + QCOMPARE(e->activatesForTouchGesture(), false); } } @@ -739,6 +749,8 @@ void TestScreenEdges::testClientEdge() QPointer edge = s->findChildren().last(); QCOMPARE(edge->isReserved(), true); + QCOMPARE(edge->activatesForPointer(), true); + QCOMPARE(edge->activatesForTouchGesture(), true); //remove old reserves and resize to be in the middle of the screen s->reserve(&client, KWin::ElectricNone); @@ -844,6 +856,90 @@ void TestScreenEdges::testClientEdge() QCOMPARE(Cursor::pos(), QPoint(1, 50)); } +void TestScreenEdges::testTouchEdge() +{ + qRegisterMetaType("ElectricBorder"); + using namespace KWin; + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + auto group = config->group("TouchEdges"); + group.writeEntry("Top", "krunner"); + group.writeEntry("Left", "krunner"); + group.writeEntry("Bottom", "krunner"); + group.writeEntry("Right", "krunner"); + config->sync(); + + auto s = ScreenEdges::self(); + s->setConfig(config); + s->init(); + // we don't have multiple desktops, so it's returning false + QCOMPARE(s->isDesktopSwitching(), false); + QCOMPARE(s->isDesktopSwitchingMovingClients(), false); + QCOMPARE(s->actionTopLeft(), ElectricBorderAction::ElectricActionNone); + QCOMPARE(s->actionTop(), ElectricBorderAction::ElectricActionNone); + QCOMPARE(s->actionTopRight(), ElectricBorderAction::ElectricActionNone); + QCOMPARE(s->actionRight(), ElectricBorderAction::ElectricActionNone); + QCOMPARE(s->actionBottomRight(), ElectricBorderAction::ElectricActionNone); + QCOMPARE(s->actionBottom(), ElectricBorderAction::ElectricActionNone); + QCOMPARE(s->actionBottomLeft(), ElectricBorderAction::ElectricActionNone); + QCOMPARE(s->actionLeft(), ElectricBorderAction::ElectricActionNone); + + QList edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); + QCOMPARE(edges.size(), 8); + for (auto e : edges) { + QCOMPARE(e->isReserved(), e->isScreenEdge()); + QCOMPARE(e->activatesForPointer(), false); + QCOMPARE(e->activatesForTouchGesture(), e->isScreenEdge()); + } + + // try to activate the edge through pointer, should not be possible + auto it = std::find_if(edges.constBegin(), edges.constEnd(), [](Edge *e) { + return e->isScreenEdge() && e->isLeft(); + }); + QVERIFY(it != edges.constEnd()); + + QSignalSpy approachingSpy(s, &ScreenEdges::approaching); + QVERIFY(approachingSpy.isValid()); + + xcb_enter_notify_event_t event; + auto setPos = [&event] (const QPoint &pos) { + Cursor::setPos(pos); + event.root_x = pos.x(); + event.root_y = pos.y(); + event.event_x = pos.x(); + event.event_y = pos.y(); + }; + event.root = XCB_WINDOW_NONE; + event.child = XCB_WINDOW_NONE; + event.event = (*it)->window(); + event.same_screen_focus = 1; + event.time = QDateTime::currentMSecsSinceEpoch(); + setPos(QPoint(0, 50)); + QCOMPARE(s->isEntered(&event), false); + QVERIFY(approachingSpy.isEmpty()); + + s->gestureRecognizer()->startSwipeGesture(QPoint(0, 50)); + QCOMPARE(approachingSpy.count(), 1); + s->gestureRecognizer()->cancelSwipeGesture(); + QCOMPARE(approachingSpy.count(), 2); + + // let's reconfigure + group.writeEntry("Top", "none"); + group.writeEntry("Left", "none"); + group.writeEntry("Bottom", "none"); + group.writeEntry("Right", "none"); + config->sync(); + s->reconfigure(); + + edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); + QCOMPARE(edges.size(), 8); + for (auto e : edges) { + QCOMPARE(e->isReserved(), false); + QCOMPARE(e->activatesForPointer(), false); + QCOMPARE(e->activatesForTouchGesture(), false); + } + +} + Q_CONSTRUCTOR_FUNCTION(forceXcb) QTEST_MAIN(TestScreenEdges) #include "test_screen_edges.moc" diff --git a/screenedge.cpp b/screenedge.cpp index 9eecbe6034..c30b5e7329 100644 --- a/screenedge.cpp +++ b/screenedge.cpp @@ -85,8 +85,7 @@ Edge::Edge(ScreenEdges *parent) unreserve(); return; } - handleAction(); - handleByCallback(); + handleTouchAction(); }, Qt::QueuedConnection ); connect(m_gesture, &SwipeGesture::started, this, &Edge::startApproaching); @@ -100,6 +99,17 @@ Edge::Edge(ScreenEdges *parent) } } ); + connect(this, &Edge::activatesForTouchGestureChanged, this, + [this] { + if (isReserved()) { + if (activatesForTouchGesture()) { + m_edges->gestureRecognizer()->registerGesture(m_gesture); + } else { + m_edges->gestureRecognizer()->unregisterGesture(m_gesture); + } + } + } + ); } Edge::~Edge() @@ -140,11 +150,45 @@ void Edge::unreserve(QObject *object) } } +bool Edge::activatesForPointer() const +{ + if (m_client) { + return true; + } + if (m_edges->isDesktopSwitching()) { + return true; + } + if (!m_callBacks.isEmpty()) { + return true; + } + if (m_action != ElectricActionNone) { + return true; + } + return false; +} + +bool Edge::activatesForTouchGesture() const +{ + if (!isScreenEdge()) { + return false; + } + if (m_client) { + return true; + } + if (m_touchAction != ElectricActionNone) { + return true; + } + return false; +} + bool Edge::triggersFor(const QPoint &cursorPos) const { if (isBlocked()) { return false; } + if (!activatesForPointer()) { + return false; + } if (!m_geometry.contains(cursorPos)) { return false; } @@ -243,7 +287,7 @@ void Edge::handle(const QPoint &cursorPos) return; } - if (handleAction() || handleByCallback()) { + if (handlePointerAction() || handleByCallback()) { pushCursorBack(cursorPos); return; } @@ -253,9 +297,9 @@ void Edge::handle(const QPoint &cursorPos) } } -bool Edge::handleAction() +bool Edge::handleAction(ElectricBorderAction action) { - switch (m_action) { + switch (action) { case ElectricActionShowDesktop: { Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop()); return true; @@ -439,6 +483,11 @@ void Edge::setGeometry(const QRect &geometry) } m_approachGeometry = QRect(x, y, width, height); doGeometryUpdate(); + + if (isScreenEdge()) { + m_gesture->setStartGeometry(m_geometry); + m_gesture->setMinimumDelta(screens()->size(screens()->number(m_geometry.center())) * 0.2); + } } void Edge::checkBlocking() @@ -463,15 +512,11 @@ void Edge::doUpdateBlocking() void Edge::doGeometryUpdate() { - if (isScreenEdge()) { - m_gesture->setStartGeometry(m_geometry); - m_gesture->setMinimumDelta(screens()->size(screens()->number(m_geometry.center())) * 0.2); - } } void Edge::activate() { - if (isScreenEdge() && !m_edges->isDesktopSwitching()) { + if (activatesForTouchGesture()) { m_edges->gestureRecognizer()->registerGesture(m_gesture); } doActivate(); @@ -597,6 +642,23 @@ void Edge::setBorder(ElectricBorder border) } } +void Edge::setTouchAction(ElectricBorderAction action) { + const bool wasTouch = activatesForTouchGesture(); + m_touchAction = action; + if (wasTouch != activatesForTouchGesture()) { + emit activatesForTouchGestureChanged(); + } +} + +void Edge::setClient(AbstractClient *client) +{ + const bool wasTouch = activatesForTouchGesture(); + m_client = client; + if (wasTouch != activatesForTouchGesture()) { + emit activatesForTouchGestureChanged(); + } +} + /********************************************************** * ScreenEdges *********************************************************/ @@ -693,6 +755,12 @@ void ScreenEdges::reconfigure() electricBorderAction(borderConfig.readEntry("BottomLeft", "None"))); setActionForBorder(ElectricLeft, &m_actionLeft, electricBorderAction(borderConfig.readEntry("Left", "None"))); + + borderConfig = m_config->group("TouchEdges"); + setActionForTouchBorder(ElectricTop, electricBorderAction(borderConfig.readEntry("Top", "None"))); + setActionForTouchBorder(ElectricRight, electricBorderAction(borderConfig.readEntry("Right", "None"))); + setActionForTouchBorder(ElectricBottom, electricBorderAction(borderConfig.readEntry("Bottom", "None"))); + setActionForTouchBorder(ElectricLeft, electricBorderAction(borderConfig.readEntry("Left", "None"))); } void ScreenEdges::setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue) @@ -725,6 +793,44 @@ void ScreenEdges::setActionForBorder(ElectricBorder border, ElectricBorderAction } } +void ScreenEdges::setActionForTouchBorder(ElectricBorder border, ElectricBorderAction newValue) +{ + auto it = m_touchActions.find(border); + ElectricBorderAction oldValue = ElectricActionNone; + if (it != m_touchActions.constEnd()) { + oldValue = it.value(); + } + if (oldValue == newValue) { + return; + } + if (oldValue == ElectricActionNone) { + // have to reserve + for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { + if ((*it)->border() == border) { + (*it)->reserve(); + } + } + } + if (newValue == ElectricActionNone) { + // have to unreserve + for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { + if ((*it)->border() == border) { + (*it)->unreserve(); + } + } + + m_touchActions.erase(it); + } else { + m_touchActions.insert(border, newValue); + } + // update action on all Edges for given border + for (auto it = m_edges.begin(); it != m_edges.end(); ++it) { + if ((*it)->border() == border) { + (*it)->setTouchAction(newValue); + } + } +} + void ScreenEdges::updateLayout() { const QSize desktopMatrix = VirtualDesktopManager::self()->grid().size(); @@ -963,6 +1069,11 @@ Edge *ScreenEdges::createEdge(ElectricBorder border, int x, int y, int width, in edge->reserve(); edge->setAction(action); } + const ElectricBorderAction touchAction = actionForTouchEdge(edge); + if (touchAction != KWin::ElectricActionNone) { + edge->reserve(); + edge->setTouchAction(touchAction); + } } if (isDesktopSwitching()) { if (edge->isCorner()) { @@ -1009,6 +1120,15 @@ ElectricBorderAction ScreenEdges::actionForEdge(Edge *edge) const return ElectricActionNone; } +ElectricBorderAction ScreenEdges::actionForTouchEdge(Edge *edge) const +{ + auto it = m_touchActions.find(edge->border()); + if (it != m_touchActions.end()) { + return it.value(); + } + return ElectricActionNone; +} + void ScreenEdges::reserveDesktopSwitching(bool isToReserve, Qt::Orientations o) { if (!o) @@ -1204,6 +1324,9 @@ bool ScreenEdges::isEntered(QMouseEvent *event) if (!edge->isReserved()) { continue; } + if (!edge->activatesForPointer()) { + continue; + } if (edge->approachGeometry().contains(event->globalPos())) { if (!edge->isApproaching()) { edge->startApproaching(); @@ -1245,6 +1368,9 @@ bool ScreenEdges::handleEnterNotifiy(xcb_window_t window, const QPoint &point, c if (!edge->isReserved()) { continue; } + if (!edge->activatesForPointer()) { + continue; + } if (edge->window() == window) { if (edge->check(point, timestamp)) { if ((*it)->client()) { diff --git a/screenedge.h b/screenedge.h index a65183b79b..6d398c35a0 100644 --- a/screenedge.h +++ b/screenedge.h @@ -75,6 +75,10 @@ public: void setClient(AbstractClient *client); AbstractClient *client() const; const QRect &geometry() const; + void setTouchAction(ElectricBorderAction action); + + bool activatesForPointer() const; + bool activatesForTouchGesture() const; /** * The window id of the native window representing the edge. @@ -100,6 +104,7 @@ public Q_SLOTS: void checkBlocking(); Q_SIGNALS: void approaching(ElectricBorder border, qreal factor, const QRect &geometry); + void activatesForTouchGestureChanged(); protected: ScreenEdges *edges(); const ScreenEdges *edges() const; @@ -115,13 +120,20 @@ private: void deactivate(); bool canActivate(const QPoint &cursorPos, const QDateTime &triggerTime); void handle(const QPoint &cursorPos); - bool handleAction(); + bool handleAction(ElectricBorderAction action); + bool handlePointerAction() { + return handleAction(m_action); + } + bool handleTouchAction() { + return handleAction(m_touchAction); + } bool handleByCallback(); void switchDesktop(const QPoint &cursorPos); void pushCursorBack(const QPoint &cursorPos); ScreenEdges *m_edges; ElectricBorder m_border; ElectricBorderAction m_action; + ElectricBorderAction m_touchAction = ElectricActionNone; int m_reserved; QRect m_geometry; QRect m_approachGeometry; @@ -345,7 +357,9 @@ private: void createVerticalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea); Edge *createEdge(ElectricBorder border, int x, int y, int width, int height, bool createAction = true); void setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue); + void setActionForTouchBorder(ElectricBorder border, ElectricBorderAction newValue); ElectricBorderAction actionForEdge(Edge *edge) const; + ElectricBorderAction actionForTouchEdge(Edge *edge) const; bool handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime ×tamp); bool handleDndNotify(xcb_window_t window, const QPoint &point); void createEdgeForClient(AbstractClient *client, ElectricBorder border); @@ -366,6 +380,7 @@ private: ElectricBorderAction m_actionBottom; ElectricBorderAction m_actionBottomLeft; ElectricBorderAction m_actionLeft; + QMap m_touchActions; int m_cornerOffset; GestureRecognizer *m_gestureRecognizer; @@ -457,11 +472,6 @@ inline bool Edge::isBlocked() const return m_blocked; } -inline void Edge::setClient(AbstractClient *client) -{ - m_client = client; -} - inline AbstractClient *Edge::client() const { return m_client;