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
This commit is contained in:
Martin Flöser 2017-07-21 20:22:56 +02:00
parent ddd957f0a6
commit c29d6093ba
8 changed files with 81 additions and 41 deletions

View file

@ -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<TabBox::TabBoxClientImpl> m_tabBoxClient;
@ -1065,6 +1069,8 @@ private:
bool m_unresponsive = false;
QKeySequence _shortcut;
static bool s_haveResizeEffect;
};

View file

@ -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> surface(Test::createSurface());
QScopedPointer<ShellSurface> 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"

View file

@ -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);

View file

@ -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())) {

View file

@ -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;

View file

@ -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<QAction*>(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;

View file

@ -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();

View file

@ -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);