From bf99d9ffdd91bc7a99dcace2672af839fa53e5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Tue, 4 Apr 2017 18:56:10 +0200 Subject: [PATCH] Introduce support for keyboard layout switching policies Summary: This change introduces the initial support for keyboard layout switching policies like in the X11 session. This first change only adds support for Global and Virtual Desktop policy. This means the current layout is stored in context to the current virtual desktop. Whenever one changes the virtual desktop the previous layout is restored. If the user has not yet navigated to this virtual desktop a switch to default layout is performed. This is the first code interacting with the new Virtual Desktop API which is not based on integer ids. To fully support this the API is slightly extended. Test Plan: Added test case Reviewers: #kwin, #plasma Subscribers: plasma-devel, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D5301 --- CMakeLists.txt | 1 + .../integration/keyboard_layout_test.cpp | 62 +++++++++ keyboard_layout.cpp | 6 + keyboard_layout.h | 6 + keyboard_layout_switching.cpp | 119 ++++++++++++++++++ keyboard_layout_switching.h | 98 +++++++++++++++ virtualdesktops.cpp | 10 +- virtualdesktops.h | 15 ++- 8 files changed, 314 insertions(+), 3 deletions(-) create mode 100644 keyboard_layout_switching.cpp create mode 100644 keyboard_layout_switching.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c1ed0dfba..6c57542527 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -367,6 +367,7 @@ set(kwin_KDEINIT_SRCS input_event_spy.cpp keyboard_input.cpp keyboard_layout.cpp + keyboard_layout_switching.cpp keyboard_repeat.cpp pointer_input.cpp touch_input.cpp diff --git a/autotests/integration/keyboard_layout_test.cpp b/autotests/integration/keyboard_layout_test.cpp index 0152edb27b..db7232c76e 100644 --- a/autotests/integration/keyboard_layout_test.cpp +++ b/autotests/integration/keyboard_layout_test.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . #include "keyboard_input.h" #include "keyboard_layout.h" #include "platform.h" +#include "virtualdesktops.h" #include "wayland_server.h" #include @@ -51,6 +52,7 @@ private Q_SLOTS: void testChangeLayoutThroughDBus(); void testPerLayoutShortcut(); void testDBusServiceExport(); + void testVirtualDesktopPolicy(); private: void reconfigureLayouts(); @@ -275,5 +277,65 @@ void KeyboardLayoutTest::testDBusServiceExport() QTRY_VERIFY(!QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.keyboard")).value()); } +void KeyboardLayoutTest::testVirtualDesktopPolicy() +{ + KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); + layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)")); + layoutGroup.writeEntry("SwitchMode", QStringLiteral("Desktop")); + layoutGroup.sync(); + reconfigureLayouts(); + auto xkb = input()->keyboard()->xkb(); + QTRY_COMPARE(xkb->numberOfLayouts(), 3u); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + + VirtualDesktopManager::self()->setCount(4); + QCOMPARE(VirtualDesktopManager::self()->count(), 4u); + + auto changeLayout = [] (const QString &layoutName) { + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts"), QStringLiteral("setLayout")); + msg << layoutName; + return QDBusConnection::sessionBus().asyncCall(msg); + }; + auto reply = changeLayout(QStringLiteral("German")); + reply.waitForFinished(); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German")); + + // switch to another virtual desktop + auto desktops = VirtualDesktopManager::self()->desktops(); + QCOMPARE(desktops.count(), 4); + QCOMPARE(desktops.first(), VirtualDesktopManager::self()->currentDesktop()); + VirtualDesktopManager::self()->setCurrent(desktops.at(1)); + QCOMPARE(desktops.at(1), VirtualDesktopManager::self()->currentDesktop()); + // should be reset to English + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)"));reply = changeLayout(QStringLiteral("German (Neo 2)")); + reply.waitForFinished(); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + // back to desktop 0 -> German + VirtualDesktopManager::self()->setCurrent(desktops.at(0)); + QCOMPARE(desktops.first(), VirtualDesktopManager::self()->currentDesktop()); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German")); + // desktop 2 -> English + VirtualDesktopManager::self()->setCurrent(desktops.at(2)); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + // desktop 1 -> Neo + VirtualDesktopManager::self()->setCurrent(desktops.at(1)); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + // remove virtual desktops + VirtualDesktopManager::self()->setCount(1); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German")); + + // add another desktop + VirtualDesktopManager::self()->setCount(2); + // switching to it should result in going to default + desktops = VirtualDesktopManager::self()->desktops(); + QCOMPARE(desktops.count(), 2); + QCOMPARE(desktops.first(), VirtualDesktopManager::self()->currentDesktop()); + VirtualDesktopManager::self()->setCurrent(desktops.last()); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + +} + WAYLANDTEST_MAIN(KeyboardLayoutTest) #include "keyboard_layout_test.moc" diff --git a/keyboard_layout.cpp b/keyboard_layout.cpp index ee052d8862..810dd5d9e7 100644 --- a/keyboard_layout.cpp +++ b/keyboard_layout.cpp @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "keyboard_layout.h" +#include "keyboard_layout_switching.h" #include "keyboard_input.h" #include "input_event.h" #include "main.h" @@ -162,6 +163,11 @@ void KeyboardLayout::reconfigure() { if (m_config) { m_config->reparseConfiguration(); + const QString policyKey = m_config->group(QStringLiteral("Layout")).readEntry("SwitchMode", QStringLiteral("Global")); + if (!m_policy || m_policy->name() != policyKey) { + delete m_policy; + m_policy = KeyboardLayoutSwitching::Policy::create(m_xkb, this, policyKey); + } } m_xkb->reconfigure(); resetLayout(); diff --git a/keyboard_layout.h b/keyboard_layout.h index 5e42d97b34..643bdbf416 100644 --- a/keyboard_layout.h +++ b/keyboard_layout.h @@ -35,6 +35,11 @@ namespace KWin class Xkb; class KeyboardLayoutDBusInterface; +namespace KeyboardLayoutSwitching +{ +class Policy; +} + class KeyboardLayout : public QObject, public InputEventSpy { Q_OBJECT @@ -76,6 +81,7 @@ private: KSharedConfigPtr m_config; QVector m_layoutShortcuts; KeyboardLayoutDBusInterface *m_dbusInterface = nullptr; + KeyboardLayoutSwitching::Policy *m_policy = nullptr; }; class KeyboardLayoutDBusInterface : public QObject diff --git a/keyboard_layout_switching.cpp b/keyboard_layout_switching.cpp new file mode 100644 index 0000000000..198da98826 --- /dev/null +++ b/keyboard_layout_switching.cpp @@ -0,0 +1,119 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "keyboard_layout_switching.h" +#include "keyboard_layout.h" +#include "virtualdesktops.h" +#include "xkb.h" + +namespace KWin +{ + +namespace KeyboardLayoutSwitching +{ + +Policy::Policy(Xkb *xkb, KeyboardLayout *layout) + : QObject(layout) + , m_xkb(xkb) + , m_layout(layout) +{ + connect(m_layout, &KeyboardLayout::layoutsReconfigured, this, &Policy::clearCache); + connect(m_layout, &KeyboardLayout::layoutChanged, this, &Policy::layoutChanged); +} + +Policy::~Policy() = default; + +void Policy::setLayout(quint32 layout) +{ + m_xkb->switchToLayout(layout); +} + +quint32 Policy::layout() const +{ + return m_xkb->currentLayout(); +} + +Policy *Policy::create(Xkb *xkb, KeyboardLayout *layout, const QString &policy) +{ + if (policy.toLower() == QStringLiteral("desktop")) { + return new VirtualDesktopPolicy(xkb, layout); + } + return new GlobalPolicy(xkb, layout); +} + +GlobalPolicy::GlobalPolicy(Xkb *xkb, KeyboardLayout *layout) + : Policy(xkb, layout) +{ +} + +GlobalPolicy::~GlobalPolicy() = default; + +VirtualDesktopPolicy::VirtualDesktopPolicy(Xkb *xkb, KeyboardLayout *layout) + : Policy(xkb, layout) +{ + connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &VirtualDesktopPolicy::desktopChanged); +} + +VirtualDesktopPolicy::~VirtualDesktopPolicy() = default; + +void VirtualDesktopPolicy::clearCache() +{ + m_layouts.clear(); +} + +void VirtualDesktopPolicy::desktopChanged() +{ + auto d = VirtualDesktopManager::self()->currentDesktop(); + if (!d) { + return; + } + auto it = m_layouts.constFind(d); + if (it == m_layouts.constEnd()) { + // new desktop - go to default; + setLayout(0); + } else { + setLayout(it.value()); + } +} + +void VirtualDesktopPolicy::layoutChanged() +{ + auto d = VirtualDesktopManager::self()->currentDesktop(); + if (!d) { + return; + } + auto it = m_layouts.find(d); + const auto l = layout(); + if (it == m_layouts.constEnd()) { + m_layouts.insert(d, l); + connect(d, &VirtualDesktop::aboutToBeDestroyed, this, + [this, d] { + m_layouts.remove(d); + } + ); + } else { + if (it.value() == l) { + return; + } + it.value() = l; + } +} + +} +} diff --git a/keyboard_layout_switching.h b/keyboard_layout_switching.h new file mode 100644 index 0000000000..abb20cd8f7 --- /dev/null +++ b/keyboard_layout_switching.h @@ -0,0 +1,98 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2017 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_KEYBOARD_LAYOUT_SWITCHING_H +#define KWIN_KEYBOARD_LAYOUT_SWITCHING_H + +#include +#include + +namespace KWin +{ + +class KeyboardLayout; +class Xkb; +class VirtualDesktop; + +namespace KeyboardLayoutSwitching +{ + +class Policy : public QObject +{ + Q_OBJECT +public: + virtual ~Policy(); + + virtual QString name() const = 0; + + static Policy *create(Xkb *xkb, KeyboardLayout *layout, const QString &policy); + +protected: + explicit Policy(Xkb *xkb, KeyboardLayout *layout); + virtual void clearCache() = 0; + virtual void layoutChanged() = 0; + + void setLayout(quint32 layout); + quint32 layout() const; + +private: + Xkb *m_xkb; + KeyboardLayout *m_layout; +}; + +class GlobalPolicy : public Policy +{ + Q_OBJECT +public: + explicit GlobalPolicy(Xkb *xkb, KeyboardLayout *layout); + ~GlobalPolicy() override; + + QString name() const override { + return QStringLiteral("Global"); + } + +protected: + void clearCache() override {} + void layoutChanged() override {} +}; + +class VirtualDesktopPolicy : public Policy +{ + Q_OBJECT +public: + explicit VirtualDesktopPolicy(Xkb *xkb, KeyboardLayout *layout); + ~VirtualDesktopPolicy() override; + + QString name() const override { + return QStringLiteral("Desktop"); + } + +protected: + void clearCache() override; + void layoutChanged() override; + +private: + void desktopChanged(); + QHash m_layouts; +}; + +} +} + +#endif diff --git a/virtualdesktops.cpp b/virtualdesktops.cpp index 53cdaaab59..6b52d5ec47 100644 --- a/virtualdesktops.cpp +++ b/virtualdesktops.cpp @@ -39,7 +39,10 @@ VirtualDesktop::VirtualDesktop(QObject *parent) { } -VirtualDesktop::~VirtualDesktop() = default; +VirtualDesktop::~VirtualDesktop() +{ + emit aboutToBeDestroyed(); +} void VirtualDesktop::setId(const QByteArray &id) { @@ -327,6 +330,11 @@ uint VirtualDesktopManager::current() const return m_current ? m_current->x11DesktopNumber() : 0; } +VirtualDesktop *VirtualDesktopManager::currentDesktop() const +{ + return m_current; +} + bool VirtualDesktopManager::setCurrent(uint newDesktop) { if (newDesktop < 1 || newDesktop > count() || newDesktop == current()) { diff --git a/virtualdesktops.h b/virtualdesktops.h index da80e007f6..3fca27a223 100644 --- a/virtualdesktops.h +++ b/virtualdesktops.h @@ -21,6 +21,7 @@ along with this program. If not, see . #define KWIN_VIRTUAL_DESKTOPS_H // KWin #include +#include // Qt includes #include #include @@ -36,7 +37,7 @@ class QAction; namespace KWin { -class VirtualDesktop : public QObject +class KWIN_EXPORT VirtualDesktop : public QObject { Q_OBJECT Q_PROPERTY(QByteArray id READ id CONSTANT) @@ -63,6 +64,10 @@ public: Q_SIGNALS: void nameChanged(); + /** + * Emitted just before the desktop gets destroyed. + **/ + void aboutToBeDestroyed(); private: QByteArray m_id; @@ -124,7 +129,7 @@ private: * of an adjacent desktop or to switch to an adjacent desktop. Interested parties should make use of * these methods and not replicate the logic to switch to the next desktop. **/ -class VirtualDesktopManager : public QObject +class KWIN_EXPORT VirtualDesktopManager : public QObject { Q_OBJECT /** @@ -162,6 +167,12 @@ public: * @see currentChanged */ uint current() const; + /** + * @returns The current desktop + * @see setCurrent + * @see currentChanged + **/ + VirtualDesktop *currentDesktop() const; /** * Moves to the desktop through the algorithm described by Direction. * @param wrap If @c true wraps around to the other side of the layout