diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -367,6 +367,7 @@ 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 --- a/autotests/integration/keyboard_layout_test.cpp +++ b/autotests/integration/keyboard_layout_test.cpp @@ -21,6 +21,7 @@ #include "keyboard_input.h" #include "keyboard_layout.h" #include "platform.h" +#include "virtualdesktops.h" #include "wayland_server.h" #include @@ -51,6 +52,7 @@ void testChangeLayoutThroughDBus(); void testPerLayoutShortcut(); void testDBusServiceExport(); + void testVirtualDesktopPolicy(); private: void reconfigureLayouts(); @@ -275,5 +277,65 @@ 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.h b/keyboard_layout.h --- a/keyboard_layout.h +++ b/keyboard_layout.h @@ -35,6 +35,11 @@ class Xkb; class KeyboardLayoutDBusInterface; +namespace KeyboardLayoutSwitching +{ +class Policy; +} + class KeyboardLayout : public QObject, public InputEventSpy { Q_OBJECT @@ -76,6 +81,7 @@ KSharedConfigPtr m_config; QVector m_layoutShortcuts; KeyboardLayoutDBusInterface *m_dbusInterface = nullptr; + KeyboardLayoutSwitching::Policy *m_policy = nullptr; }; class KeyboardLayoutDBusInterface : public QObject diff --git a/keyboard_layout.cpp b/keyboard_layout.cpp --- a/keyboard_layout.cpp +++ b/keyboard_layout.cpp @@ -18,6 +18,7 @@ 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 @@ { 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_switching.h b/keyboard_layout_switching.h new file mode 100644 --- /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/keyboard_layout_switching.cpp b/keyboard_layout_switching.cpp new file mode 100644 --- /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/virtualdesktops.h b/virtualdesktops.h --- a/virtualdesktops.h +++ b/virtualdesktops.h @@ -21,6 +21,7 @@ #define KWIN_VIRTUAL_DESKTOPS_H // KWin #include +#include // Qt includes #include #include @@ -36,7 +37,7 @@ 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 @@ Q_SIGNALS: void nameChanged(); + /** + * Emitted just before the desktop gets destroyed. + **/ + void aboutToBeDestroyed(); private: QByteArray m_id; @@ -124,7 +129,7 @@ * 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 /** @@ -163,6 +168,12 @@ */ 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 * @see setCurrent diff --git a/virtualdesktops.cpp b/virtualdesktops.cpp --- a/virtualdesktops.cpp +++ b/virtualdesktops.cpp @@ -39,7 +39,10 @@ { } -VirtualDesktop::~VirtualDesktop() = default; +VirtualDesktop::~VirtualDesktop() +{ + emit aboutToBeDestroyed(); +} void VirtualDesktop::setId(const QByteArray &id) { @@ -327,6 +330,11 @@ 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()) {