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,12 +21,17 @@ #include "keyboard_input.h" #include "keyboard_layout.h" #include "platform.h" +#include "shell_client.h" #include "virtualdesktops.h" #include "wayland_server.h" +#include "workspace.h" #include #include +#include +#include + #include #include #include @@ -53,6 +58,7 @@ void testPerLayoutShortcut(); void testDBusServiceExport(); void testVirtualDesktopPolicy(); + void testWindowPolicy(); private: void reconfigureLayouts(); @@ -67,6 +73,8 @@ void KeyboardLayoutTest::initTestCase() { + qRegisterMetaType(); + qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); @@ -82,10 +90,12 @@ void KeyboardLayoutTest::init() { + QVERIFY(Test::setupWaylandConnection()); } void KeyboardLayoutTest::cleanup() { + Test::destroyWaylandConnection(); } class LayoutChangedSignalWrapper : public QObject @@ -337,5 +347,52 @@ } +void KeyboardLayoutTest::testWindowPolicy() +{ + KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); + layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)")); + layoutGroup.writeEntry("SwitchMode", QStringLiteral("Window")); + layoutGroup.sync(); + reconfigureLayouts(); + auto xkb = input()->keyboard()->xkb(); + QTRY_COMPARE(xkb->numberOfLayouts(), 3u); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + + // create a window + using namespace KWayland::Client; + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createShellSurface(surface.data())); + auto c1 = Test::renderAndWaitForShown(surface.data(), QSize(100, 100), Qt::blue); + QVERIFY(c1); + + // now switch layout + 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")); + + // create a second window + QScopedPointer surface2(Test::createSurface()); + QScopedPointer shellSurface2(Test::createShellSurface(surface2.data())); + auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 100), Qt::red); + QVERIFY(c2); + // this should have switched back to English + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + // now change to another layout + reply = changeLayout(QStringLiteral("German (Neo 2)")); + reply.waitForFinished(); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + // activate other window + workspace()->activateClient(c1); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German")); + workspace()->activateClient(c2); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); +} + WAYLANDTEST_MAIN(KeyboardLayoutTest) #include "keyboard_layout_test.moc" diff --git a/keyboard_layout_switching.h b/keyboard_layout_switching.h --- a/keyboard_layout_switching.h +++ b/keyboard_layout_switching.h @@ -26,6 +26,7 @@ namespace KWin { +class AbstractClient; class KeyboardLayout; class Xkb; class VirtualDesktop; @@ -92,6 +93,25 @@ QHash m_layouts; }; +class WindowPolicy : public Policy +{ + Q_OBJECT +public: + explicit WindowPolicy(Xkb *xkb, KeyboardLayout *layout); + ~WindowPolicy() override; + + QString name() const override { + return QStringLiteral("Window"); + } + +protected: + void clearCache() override; + void layoutChanged() override; + +private: + QHash m_layouts; +}; + } } diff --git a/keyboard_layout_switching.cpp b/keyboard_layout_switching.cpp --- a/keyboard_layout_switching.cpp +++ b/keyboard_layout_switching.cpp @@ -19,7 +19,10 @@ *********************************************************************/ #include "keyboard_layout_switching.h" #include "keyboard_layout.h" +#include "abstract_client.h" +#include "deleted.h" #include "virtualdesktops.h" +#include "workspace.h" #include "xkb.h" namespace KWin @@ -54,6 +57,9 @@ if (policy.toLower() == QStringLiteral("desktop")) { return new VirtualDesktopPolicy(xkb, layout); } + if (policy.toLower() == QStringLiteral("window")) { + return new WindowPolicy(xkb, layout); + } return new GlobalPolicy(xkb, layout); } @@ -77,19 +83,26 @@ m_layouts.clear(); } +namespace { +template +quint32 getLayout(const T &layouts, const U &reference) +{ + auto it = layouts.constFind(reference); + if (it == layouts.constEnd()) { + return 0; + } else { + return it.value(); + } +} +} + 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()); - } + setLayout(getLayout(m_layouts, d)); } void VirtualDesktopPolicy::layoutChanged() @@ -115,5 +128,59 @@ } } +WindowPolicy::WindowPolicy(KWin::Xkb* xkb, KWin::KeyboardLayout* layout) + : Policy(xkb, layout) +{ + connect(workspace(), &Workspace::clientActivated, this, + [this] (AbstractClient *c) { + if (!c) { + return; + } + // ignore some special types + if (c->isDesktop() || c->isDock()) { + return; + } + setLayout(getLayout(m_layouts, c)); + } + ); +} + +WindowPolicy::~WindowPolicy() +{ +} + +void WindowPolicy::clearCache() +{ + m_layouts.clear(); +} + +void WindowPolicy::layoutChanged() +{ + auto c = workspace()->activeClient(); + if (!c) { + return; + } + // ignore some special types + if (c->isDesktop() || c->isDock()) { + return; + } + + auto it = m_layouts.find(c); + const auto l = layout(); + if (it == m_layouts.constEnd()) { + m_layouts.insert(c, l); + connect(c, &AbstractClient::windowClosed, this, + [this, c] { + m_layouts.remove(c); + } + ); + } else { + if (it.value() == l) { + return; + } + it.value() = l; + } +} + } }