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
This commit is contained in:
Martin Gräßlin 2017-03-30 07:08:38 +02:00
parent 1193b0da77
commit 64ce6259a9
3 changed files with 248 additions and 16 deletions

View file

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "../atoms.h" #include "../atoms.h"
#include "../cursor.h" #include "../cursor.h"
#include "../input.h" #include "../input.h"
#include "../gestures.h"
#include "../main.h" #include "../main.h"
#include "../screenedge.h" #include "../screenedge.h"
#include "../screens.h" #include "../screens.h"
@ -128,6 +129,7 @@ private Q_SLOTS:
void testPushBack(); void testPushBack();
void testFullScreenBlocking(); void testFullScreenBlocking();
void testClientEdge(); void testClientEdge();
void testTouchEdge();
}; };
void TestScreenEdges::initTestCase() void TestScreenEdges::initTestCase()
@ -334,6 +336,8 @@ void TestScreenEdges::testCreatingInitialEdges()
QCOMPARE(edges.size(), 8); QCOMPARE(edges.size(), 8);
for (auto e : edges) { for (auto e : edges) {
QVERIFY(e->isReserved()); QVERIFY(e->isReserved());
QCOMPARE(e->activatesForPointer(), true);
QCOMPARE(e->activatesForTouchGesture(), false);
} }
static_cast<MockScreens*>(screens())->setGeometries(QList<QRect>{QRect{0, 0, 1024, 768}}); static_cast<MockScreens*>(screens())->setGeometries(QList<QRect>{QRect{0, 0, 1024, 768}});
@ -382,6 +386,8 @@ void TestScreenEdges::testCreatingInitialEdges()
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
auto e = edges.at(i); auto e = edges.at(i);
QVERIFY(!e->isReserved()); QVERIFY(!e->isReserved());
QCOMPARE(e->activatesForPointer(), false);
QCOMPARE(e->activatesForTouchGesture(), false);
QCOMPARE(e->approachGeometry(), expectedGeometries.at(i*2+1)); QCOMPARE(e->approachGeometry(), expectedGeometries.at(i*2+1));
} }
} }
@ -415,6 +421,8 @@ void TestScreenEdges::testCallback()
QCOMPARE(edges.size(), 10); QCOMPARE(edges.size(), 10);
for (auto e: edges) { for (auto e: edges) {
QVERIFY(e->isReserved()); QVERIFY(e->isReserved());
QCOMPARE(e->activatesForPointer(), true);
QCOMPARE(e->activatesForTouchGesture(), false);
} }
auto it = std::find_if(edges.constBegin(), edges.constEnd(), [](Edge *e) { auto it = std::find_if(edges.constBegin(), edges.constEnd(), [](Edge *e) {
return e->isScreenEdge() && e->isLeft() && e->approachGeometry().bottom() < 768; return e->isScreenEdge() && e->isLeft() && e->approachGeometry().bottom() < 768;
@ -523,6 +531,8 @@ void TestScreenEdges::testCallback()
s->unreserve(ElectricLeft, &callback); s->unreserve(ElectricLeft, &callback);
for (auto e: s->findChildren<Edge*>(QString(), Qt::FindDirectChildrenOnly)) { for (auto e: s->findChildren<Edge*>(QString(), Qt::FindDirectChildrenOnly)) {
QVERIFY(!e->isReserved()); QVERIFY(!e->isReserved());
QCOMPARE(e->activatesForPointer(), false);
QCOMPARE(e->activatesForTouchGesture(), false);
} }
} }
@ -739,6 +749,8 @@ void TestScreenEdges::testClientEdge()
QPointer<Edge> edge = s->findChildren<Edge*>().last(); QPointer<Edge> edge = s->findChildren<Edge*>().last();
QCOMPARE(edge->isReserved(), true); 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 //remove old reserves and resize to be in the middle of the screen
s->reserve(&client, KWin::ElectricNone); s->reserve(&client, KWin::ElectricNone);
@ -844,6 +856,90 @@ void TestScreenEdges::testClientEdge()
QCOMPARE(Cursor::pos(), QPoint(1, 50)); QCOMPARE(Cursor::pos(), QPoint(1, 50));
} }
void TestScreenEdges::testTouchEdge()
{
qRegisterMetaType<KWin::ElectricBorder>("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<Edge*> edges = s->findChildren<Edge*>(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<Edge*>(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) Q_CONSTRUCTOR_FUNCTION(forceXcb)
QTEST_MAIN(TestScreenEdges) QTEST_MAIN(TestScreenEdges)
#include "test_screen_edges.moc" #include "test_screen_edges.moc"

View file

@ -85,8 +85,7 @@ Edge::Edge(ScreenEdges *parent)
unreserve(); unreserve();
return; return;
} }
handleAction(); handleTouchAction();
handleByCallback();
}, Qt::QueuedConnection }, Qt::QueuedConnection
); );
connect(m_gesture, &SwipeGesture::started, this, &Edge::startApproaching); 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() 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 bool Edge::triggersFor(const QPoint &cursorPos) const
{ {
if (isBlocked()) { if (isBlocked()) {
return false; return false;
} }
if (!activatesForPointer()) {
return false;
}
if (!m_geometry.contains(cursorPos)) { if (!m_geometry.contains(cursorPos)) {
return false; return false;
} }
@ -243,7 +287,7 @@ void Edge::handle(const QPoint &cursorPos)
return; return;
} }
if (handleAction() || handleByCallback()) { if (handlePointerAction() || handleByCallback()) {
pushCursorBack(cursorPos); pushCursorBack(cursorPos);
return; 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: { case ElectricActionShowDesktop: {
Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop()); Workspace::self()->setShowingDesktop(!Workspace::self()->showingDesktop());
return true; return true;
@ -439,6 +483,11 @@ void Edge::setGeometry(const QRect &geometry)
} }
m_approachGeometry = QRect(x, y, width, height); m_approachGeometry = QRect(x, y, width, height);
doGeometryUpdate(); doGeometryUpdate();
if (isScreenEdge()) {
m_gesture->setStartGeometry(m_geometry);
m_gesture->setMinimumDelta(screens()->size(screens()->number(m_geometry.center())) * 0.2);
}
} }
void Edge::checkBlocking() void Edge::checkBlocking()
@ -463,15 +512,11 @@ void Edge::doUpdateBlocking()
void Edge::doGeometryUpdate() 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() void Edge::activate()
{ {
if (isScreenEdge() && !m_edges->isDesktopSwitching()) { if (activatesForTouchGesture()) {
m_edges->gestureRecognizer()->registerGesture(m_gesture); m_edges->gestureRecognizer()->registerGesture(m_gesture);
} }
doActivate(); 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 * ScreenEdges
*********************************************************/ *********************************************************/
@ -693,6 +755,12 @@ void ScreenEdges::reconfigure()
electricBorderAction(borderConfig.readEntry("BottomLeft", "None"))); electricBorderAction(borderConfig.readEntry("BottomLeft", "None")));
setActionForBorder(ElectricLeft, &m_actionLeft, setActionForBorder(ElectricLeft, &m_actionLeft,
electricBorderAction(borderConfig.readEntry("Left", "None"))); 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) 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() void ScreenEdges::updateLayout()
{ {
const QSize desktopMatrix = VirtualDesktopManager::self()->grid().size(); 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->reserve();
edge->setAction(action); edge->setAction(action);
} }
const ElectricBorderAction touchAction = actionForTouchEdge(edge);
if (touchAction != KWin::ElectricActionNone) {
edge->reserve();
edge->setTouchAction(touchAction);
}
} }
if (isDesktopSwitching()) { if (isDesktopSwitching()) {
if (edge->isCorner()) { if (edge->isCorner()) {
@ -1009,6 +1120,15 @@ ElectricBorderAction ScreenEdges::actionForEdge(Edge *edge) const
return ElectricActionNone; 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) void ScreenEdges::reserveDesktopSwitching(bool isToReserve, Qt::Orientations o)
{ {
if (!o) if (!o)
@ -1204,6 +1324,9 @@ bool ScreenEdges::isEntered(QMouseEvent *event)
if (!edge->isReserved()) { if (!edge->isReserved()) {
continue; continue;
} }
if (!edge->activatesForPointer()) {
continue;
}
if (edge->approachGeometry().contains(event->globalPos())) { if (edge->approachGeometry().contains(event->globalPos())) {
if (!edge->isApproaching()) { if (!edge->isApproaching()) {
edge->startApproaching(); edge->startApproaching();
@ -1245,6 +1368,9 @@ bool ScreenEdges::handleEnterNotifiy(xcb_window_t window, const QPoint &point, c
if (!edge->isReserved()) { if (!edge->isReserved()) {
continue; continue;
} }
if (!edge->activatesForPointer()) {
continue;
}
if (edge->window() == window) { if (edge->window() == window) {
if (edge->check(point, timestamp)) { if (edge->check(point, timestamp)) {
if ((*it)->client()) { if ((*it)->client()) {

View file

@ -75,6 +75,10 @@ public:
void setClient(AbstractClient *client); void setClient(AbstractClient *client);
AbstractClient *client() const; AbstractClient *client() const;
const QRect &geometry() 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. * The window id of the native window representing the edge.
@ -100,6 +104,7 @@ public Q_SLOTS:
void checkBlocking(); void checkBlocking();
Q_SIGNALS: Q_SIGNALS:
void approaching(ElectricBorder border, qreal factor, const QRect &geometry); void approaching(ElectricBorder border, qreal factor, const QRect &geometry);
void activatesForTouchGestureChanged();
protected: protected:
ScreenEdges *edges(); ScreenEdges *edges();
const ScreenEdges *edges() const; const ScreenEdges *edges() const;
@ -115,13 +120,20 @@ private:
void deactivate(); void deactivate();
bool canActivate(const QPoint &cursorPos, const QDateTime &triggerTime); bool canActivate(const QPoint &cursorPos, const QDateTime &triggerTime);
void handle(const QPoint &cursorPos); 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(); bool handleByCallback();
void switchDesktop(const QPoint &cursorPos); void switchDesktop(const QPoint &cursorPos);
void pushCursorBack(const QPoint &cursorPos); void pushCursorBack(const QPoint &cursorPos);
ScreenEdges *m_edges; ScreenEdges *m_edges;
ElectricBorder m_border; ElectricBorder m_border;
ElectricBorderAction m_action; ElectricBorderAction m_action;
ElectricBorderAction m_touchAction = ElectricActionNone;
int m_reserved; int m_reserved;
QRect m_geometry; QRect m_geometry;
QRect m_approachGeometry; QRect m_approachGeometry;
@ -345,7 +357,9 @@ private:
void createVerticalEdge(ElectricBorder border, const QRect &screen, const QRect &fullArea); 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); Edge *createEdge(ElectricBorder border, int x, int y, int width, int height, bool createAction = true);
void setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue); void setActionForBorder(ElectricBorder border, ElectricBorderAction *oldValue, ElectricBorderAction newValue);
void setActionForTouchBorder(ElectricBorder border, ElectricBorderAction newValue);
ElectricBorderAction actionForEdge(Edge *edge) const; ElectricBorderAction actionForEdge(Edge *edge) const;
ElectricBorderAction actionForTouchEdge(Edge *edge) const;
bool handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime &timestamp); bool handleEnterNotifiy(xcb_window_t window, const QPoint &point, const QDateTime &timestamp);
bool handleDndNotify(xcb_window_t window, const QPoint &point); bool handleDndNotify(xcb_window_t window, const QPoint &point);
void createEdgeForClient(AbstractClient *client, ElectricBorder border); void createEdgeForClient(AbstractClient *client, ElectricBorder border);
@ -366,6 +380,7 @@ private:
ElectricBorderAction m_actionBottom; ElectricBorderAction m_actionBottom;
ElectricBorderAction m_actionBottomLeft; ElectricBorderAction m_actionBottomLeft;
ElectricBorderAction m_actionLeft; ElectricBorderAction m_actionLeft;
QMap<ElectricBorder, ElectricBorderAction> m_touchActions;
int m_cornerOffset; int m_cornerOffset;
GestureRecognizer *m_gestureRecognizer; GestureRecognizer *m_gestureRecognizer;
@ -457,11 +472,6 @@ inline bool Edge::isBlocked() const
return m_blocked; return m_blocked;
} }
inline void Edge::setClient(AbstractClient *client)
{
m_client = client;
}
inline AbstractClient *Edge::client() const inline AbstractClient *Edge::client() const
{ {
return m_client; return m_client;