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