From c29d6093ba9afc2ec93d24a6854c81bac1cd3563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Fl=C3=B6ser?= Date: Fri, 21 Jul 2017 20:22:56 +0200 Subject: [PATCH] Implement support for window shortcuts for Wayland windows Summary: Moves most of the implementation from Client to AbstractClient, so that it can be used for both Client and ShellClient. Only the X11 specific code is kept in Client. Not yet implemented is updating the window caption. Unfortunately the testing of this feature showed that setting a window shortcut is not working on Wayland at all (the Qt widget doesn't properly catch the shortcut). So this feature is currently only of erm theoretical use. Test Plan: Added new test case. No testing in real world as explained. Reviewers: #kwin, #plasma Subscribers: plasma-devel, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D6818 --- abstract_client.h | 10 ++++- .../integration/globalshortcuts_test.cpp | 39 ++++++++++++++++++ client.h | 10 +---- shell_client.cpp | 11 ----- shell_client.h | 2 - useractions.cpp | 40 ++++++++++++------- workspace.cpp | 6 +++ workspace.h | 4 +- 8 files changed, 81 insertions(+), 41 deletions(-) diff --git a/abstract_client.h b/abstract_client.h index e34dbd1118..24249c2871 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -384,8 +384,10 @@ public: */ bool isSpecialWindow() const; void sendToScreen(int screen); - virtual const QKeySequence &shortcut() const = 0; - virtual void setShortcut(const QString &cut) = 0; + const QKeySequence &shortcut() const { + return _shortcut; + } + void setShortcut(const QString &cut); virtual bool performMouseCommand(Options::MouseCommand, const QPoint &globalPos); void setOnAllDesktops(bool set); void setDesktop(int); @@ -991,6 +993,8 @@ protected: void setUnresponsive(bool unresponsive); + virtual void setShortcutInternal(); + private: void handlePaletteChange(); QSharedPointer m_tabBoxClient; @@ -1065,6 +1069,8 @@ private: bool m_unresponsive = false; + QKeySequence _shortcut; + static bool s_haveResizeEffect; }; diff --git a/autotests/integration/globalshortcuts_test.cpp b/autotests/integration/globalshortcuts_test.cpp index f3a7fd5ffd..0c1893bc1e 100644 --- a/autotests/integration/globalshortcuts_test.cpp +++ b/autotests/integration/globalshortcuts_test.cpp @@ -56,6 +56,7 @@ private Q_SLOTS: void testUserActionsMenu(); void testMetaShiftW(); void testX11ClientShortcut(); + void testWaylandClientShortcut(); }; void GlobalShortcutsTest::initTestCase() @@ -277,5 +278,43 @@ void GlobalShortcutsTest::testX11ClientShortcut() QVERIFY(windowClosedSpy.wait()); } +void GlobalShortcutsTest::testWaylandClientShortcut() +{ + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createShellSurface(surface.data())); + auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + + QCOMPARE(workspace()->activeClient(), client); + QVERIFY(client->isActive()); + QCOMPARE(client->shortcut(), QKeySequence()); + const QKeySequence seq(Qt::META + Qt::SHIFT + Qt::Key_Y); + QVERIFY(workspace()->shortcutAvailable(seq)); + client->setShortcut(seq.toString()); + QCOMPARE(client->shortcut(), seq); + QVERIFY(!workspace()->shortcutAvailable(seq)); + QEXPECT_FAIL("", "Caption adjustment not yet implemented", Continue); + QCOMPARE(client->caption(), QStringLiteral(" {Meta+Shift+Y}")); + + workspace()->activateClient(nullptr); + QVERIFY(!workspace()->activeClient()); + QVERIFY(!client->isActive()); + + // now let's trigger the shortcut + quint32 timestamp = 0; + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_Y, timestamp++); + QTRY_COMPARE(workspace()->activeClient(), client); + kwinApp()->platform()->keyboardKeyReleased(KEY_Y, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); + QVERIFY(workspace()->shortcutAvailable(seq)); +} + + WAYLANDTEST_MAIN(GlobalShortcutsTest) #include "globalshortcuts_test.moc" diff --git a/client.h b/client.h index 71d492e0b2..063a2d2dd9 100644 --- a/client.h +++ b/client.h @@ -200,8 +200,6 @@ public: QSize sizeForClientSize(const QSize&, Sizemode mode = SizemodeAny, bool noframe = false) const override; bool providesContextHelp() const override; - const QKeySequence &shortcut() const override; - void setShortcut(const QString& cut) override; Options::WindowOperation mouseButtonToWindowOperation(Qt::MouseButtons button); bool performMouseCommand(Options::MouseCommand, const QPoint& globalPos) override; @@ -451,7 +449,7 @@ private: void setCaption(const QString& s, bool force = false); bool hasTransientInternal(const Client* c, bool indirect, ConstClientList& set) const; void finishWindowRules(); - void setShortcutInternal(const QKeySequence &cut = QKeySequence()); + void setShortcutInternal() override; void configureRequest(int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool); NETExtendedStrut strut() const; @@ -579,7 +577,6 @@ private: bool isPending; } syncRequest; static bool check_active_modal; ///< \see Client::checkActiveModal() - QKeySequence _shortcut; int sm_stacking_order; friend struct ResetupRulesProcedure; @@ -738,11 +735,6 @@ inline xcb_window_t Client::moveResizeGrabWindow() const return m_moveResizeGrabWindow; } -inline const QKeySequence &Client::shortcut() const -{ - return _shortcut; -} - inline void Client::removeRule(Rules* rule) { client_rules.remove(rule); diff --git a/shell_client.cpp b/shell_client.cpp index 8ff3172e8d..a23da45c6d 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -820,17 +820,6 @@ void ShellClient::setOnAllActivities(bool set) Q_UNUSED(set) } -void ShellClient::setShortcut(const QString &cut) -{ - Q_UNUSED(cut) -} - -const QKeySequence &ShellClient::shortcut() const -{ - static QKeySequence seq; - return seq; -} - void ShellClient::takeFocus() { if (rules()->checkAcceptFocus(wantsInput())) { diff --git a/shell_client.h b/shell_client.h index eba74cfb7c..2afb4b6fe3 100644 --- a/shell_client.h +++ b/shell_client.h @@ -89,8 +89,6 @@ public: void setNoBorder(bool set) override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void setOnAllActivities(bool set) override; - void setShortcut(const QString &cut) override; - const QKeySequence &shortcut() const override; void takeFocus() override; void updateWindowRules(Rules::Types selection) override; bool userCanSetFullScreen() const override; diff --git a/useractions.cpp b/useractions.cpp index 2066318ddb..c6c8643d90 100644 --- a/useractions.cpp +++ b/useractions.cpp @@ -1044,7 +1044,7 @@ void Workspace::setupWindowShortcutDone(bool ok) active_client->takeFocus(); } -void Workspace::clientShortcutUpdated(Client* c) +void Workspace::clientShortcutUpdated(AbstractClient* c) { QString key = QStringLiteral("_k_session:%1").arg(c->window()); QAction* action = findChild(key); @@ -1794,11 +1794,19 @@ void Workspace::slotInvertScreen() #undef USABLE_ACTIVE_CLIENT -void Client::setShortcut(const QString& _cut) +void AbstractClient::setShortcut(const QString& _cut) { QString cut = rules()->checkShortcut(_cut); - if (cut.isEmpty()) - return setShortcutInternal(); + auto updateShortcut = [this](const QKeySequence &cut = QKeySequence()) { + if (_shortcut == cut) + return; + _shortcut = cut; + setShortcutInternal(); + }; + if (cut.isEmpty()) { + updateShortcut(); + return; + } if (cut == shortcut().toString()) { return; // no change } @@ -1807,9 +1815,9 @@ void Client::setShortcut(const QString& _cut) // E.g. Alt+Ctrl+(ABCDEF);Meta+X,Meta+(ABCDEF) if (!cut.contains(QLatin1Char('(')) && !cut.contains(QLatin1Char(')')) && !cut.contains(QLatin1String(" - "))) { if (workspace()->shortcutAvailable(cut, this)) - setShortcutInternal(QKeySequence(cut)); + updateShortcut(QKeySequence(cut)); else - setShortcutInternal(); + updateShortcut(); return; } QList< QKeySequence > keys; @@ -1846,18 +1854,20 @@ void Client::setShortcut(const QString& _cut) it != keys.constEnd(); ++it) { if (workspace()->shortcutAvailable(*it, this)) { - setShortcutInternal(*it); + updateShortcut(*it); return; } } - setShortcutInternal(); + updateShortcut(); } -void Client::setShortcutInternal(const QKeySequence &cut) +void AbstractClient::setShortcutInternal() +{ + workspace()->clientShortcutUpdated(this); +} + +void Client::setShortcutInternal() { - if (_shortcut == cut) - return; - _shortcut = cut; updateCaption(); #if 0 workspace()->clientShortcutUpdated(this); @@ -1874,7 +1884,7 @@ void Client::delayedSetShortcut() workspace()->clientShortcutUpdated(this); } -bool Workspace::shortcutAvailable(const QKeySequence &cut, Client* ignore) const +bool Workspace::shortcutAvailable(const QKeySequence &cut, AbstractClient* ignore) const { if (ignore && cut == ignore->shortcut()) return true; @@ -1882,8 +1892,8 @@ bool Workspace::shortcutAvailable(const QKeySequence &cut, Client* ignore) const if (!KGlobalAccel::getGlobalShortcutsByKey(cut).isEmpty()) { return false; } - for (ClientList::ConstIterator it = clients.constBegin(); - it != clients.constEnd(); + for (auto it = m_allClients.constBegin(); + it != m_allClients.constEnd(); ++it) { if ((*it) != ignore && (*it)->shortcut() == cut) return false; diff --git a/workspace.cpp b/workspace.cpp index 801d6cd011..e83f159cd4 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -434,6 +434,12 @@ void Workspace::init() if (c == last_active_client) { last_active_client = nullptr; } + if (client_keys_client == c) { + setupWindowShortcutDone(false); + } + if (!c->shortcut().isEmpty()) { + c->setShortcut(QString()); // Remove from client_keys + } clientHidden(c); emit clientRemoved(c); markXStackingOrderAsDirty(); diff --git a/workspace.h b/workspace.h index e6a983496d..79177108ed 100644 --- a/workspace.h +++ b/workspace.h @@ -326,8 +326,8 @@ public: void focusToNull(); // SELI TODO: Public? - void clientShortcutUpdated(Client* c); - bool shortcutAvailable(const QKeySequence &cut, Client* ignore = NULL) const; + void clientShortcutUpdated(AbstractClient* c); + bool shortcutAvailable(const QKeySequence &cut, AbstractClient* ignore = NULL) const; bool globalShortcutsDisabled() const; void disableGlobalShortcutsForClient(bool disable);