diff --git a/autotests/integration/keyboard_layout_test.cpp b/autotests/integration/keyboard_layout_test.cpp index 7ef379675..65f125189 100644 --- a/autotests/integration/keyboard_layout_test.cpp +++ b/autotests/integration/keyboard_layout_test.cpp @@ -1,398 +1,461 @@ /******************************************************************** 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 "kwin_wayland_test.h" #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 #include #include #include using namespace KWin; using namespace KWayland::Client; static const QString s_socketName = QStringLiteral("wayland_test_kwin_keyboard_laout-0"); class KeyboardLayoutTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testReconfigure(); void testChangeLayoutThroughDBus(); void testPerLayoutShortcut(); void testDBusServiceExport(); void testVirtualDesktopPolicy(); void testWindowPolicy(); + void testApplicationPolicy(); private: void reconfigureLayouts(); }; void KeyboardLayoutTest::reconfigureLayouts() { // create DBus signal to reload QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/Layouts"), QStringLiteral("org.kde.keyboard"), QStringLiteral("reloadConfig")); QDBusConnection::sessionBus().send(message); } void KeyboardLayoutTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); kwinApp()->setKxkbConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); waylandServer()->initWorkspace(); } void KeyboardLayoutTest::init() { QVERIFY(Test::setupWaylandConnection()); } void KeyboardLayoutTest::cleanup() { Test::destroyWaylandConnection(); } class LayoutChangedSignalWrapper : public QObject { Q_OBJECT public: LayoutChangedSignalWrapper() : QObject() { QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts"), QStringLiteral("currentLayoutChanged"), this, SIGNAL(layoutChanged(QString))); } Q_SIGNALS: void layoutChanged(const QString &name); }; void KeyboardLayoutTest::testReconfigure() { // verifies that we can change the keymap // default should be a keymap with only us layout auto xkb = input()->keyboard()->xkb(); QCOMPARE(xkb->numberOfLayouts(), 1u); QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); auto layouts = xkb->layoutNames(); QCOMPARE(layouts.size(), 1); QVERIFY(layouts.contains(0)); QCOMPARE(layouts[0], QStringLiteral("English (US)")); // create a new keymap KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); layoutGroup.writeEntry("LayoutList", QStringLiteral("de,us")); layoutGroup.sync(); reconfigureLayouts(); // now we should have two layouts QTRY_COMPARE(xkb->numberOfLayouts(), 2u); // default layout is German QCOMPARE(xkb->layoutName(), QStringLiteral("German")); layouts = xkb->layoutNames(); QCOMPARE(layouts.size(), 2); QVERIFY(layouts.contains(0)); QVERIFY(layouts.contains(1)); QCOMPARE(layouts[0], QStringLiteral("German")); QCOMPARE(layouts[1], QStringLiteral("English (US)")); } void KeyboardLayoutTest::testChangeLayoutThroughDBus() { // this test verifies that the layout can be changed through DBus // first configure layouts KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); layoutGroup.writeEntry("LayoutList", QStringLiteral("de,us,de(neo)")); layoutGroup.sync(); reconfigureLayouts(); // now we should have two layouts auto xkb = input()->keyboard()->xkb(); QTRY_COMPARE(xkb->numberOfLayouts(), 3u); // default layout is German xkb->switchToLayout(0); QCOMPARE(xkb->layoutName(), QStringLiteral("German")); LayoutChangedSignalWrapper wrapper; QSignalSpy layoutChangedSpy(&wrapper, &LayoutChangedSignalWrapper::layoutChanged); QVERIFY(layoutChangedSpy.isValid()); // now change through DBus to english 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("English (US)")); reply.waitForFinished(); QVERIFY(!reply.isError()); QCOMPARE(reply.reply().arguments().first().toBool(), true); QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); QVERIFY(layoutChangedSpy.wait()); QCOMPARE(layoutChangedSpy.count(), 1); layoutChangedSpy.clear(); // switch to a layout which does not exist reply = changeLayout(QStringLiteral("French")); QVERIFY(!reply.isError()); QCOMPARE(reply.reply().arguments().first().toBool(), false); QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); QVERIFY(!layoutChangedSpy.wait()); QVERIFY(layoutChangedSpy.isEmpty()); // switch to another layout should work reply = changeLayout(QStringLiteral("German")); QVERIFY(!reply.isError()); QCOMPARE(reply.reply().arguments().first().toBool(), true); QCOMPARE(xkb->layoutName(), QStringLiteral("German")); QVERIFY(layoutChangedSpy.wait()); QCOMPARE(layoutChangedSpy.count(), 1); layoutChangedSpy.clear(); // switching to same layout should also work reply = changeLayout(QStringLiteral("German")); QVERIFY(!reply.isError()); QCOMPARE(reply.reply().arguments().first().toBool(), true); QCOMPARE(xkb->layoutName(), QStringLiteral("German")); QVERIFY(!layoutChangedSpy.wait()); QVERIFY(layoutChangedSpy.isEmpty()); } void KeyboardLayoutTest::testPerLayoutShortcut() { // this test verifies that per-layout global shortcuts are working correctly. // first configure layouts KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)")); layoutGroup.sync(); // and create the global shortcuts const QString componentName = QStringLiteral("KDE Keyboard Layout Switcher"); QAction *a = new QAction(this); a->setObjectName(QStringLiteral("Switch keyboard layout to English (US)")); a->setProperty("componentName", componentName); KGlobalAccel::self()->setShortcut(a, QList{Qt::CTRL+Qt::ALT+Qt::Key_1}, KGlobalAccel::NoAutoloading); delete a; a = new QAction(this); a->setObjectName(QStringLiteral("Switch keyboard layout to German")); a->setProperty("componentName", componentName); KGlobalAccel::self()->setShortcut(a, QList{Qt::CTRL+Qt::ALT+Qt::Key_2}, KGlobalAccel::NoAutoloading); delete a; reconfigureLayouts(); // now we should have three layouts auto xkb = input()->keyboard()->xkb(); QTRY_COMPARE(xkb->numberOfLayouts(), 3u); // default layout is English xkb->switchToLayout(0); QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)")); LayoutChangedSignalWrapper wrapper; QSignalSpy layoutChangedSpy(&wrapper, &LayoutChangedSignalWrapper::layoutChanged); QVERIFY(layoutChangedSpy.isValid()); // now switch to English through the global shortcut quint32 timestamp = 1; kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyPressed(KEY_2, timestamp++); QVERIFY(layoutChangedSpy.wait()); // now layout should be German QCOMPARE(xkb->layoutName(), QStringLiteral("German")); // release keys again kwinApp()->platform()->keyboardKeyReleased(KEY_2, timestamp++); // switch back to English kwinApp()->platform()->keyboardKeyPressed(KEY_1, timestamp++); QVERIFY(layoutChangedSpy.wait()); QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); // release keys again kwinApp()->platform()->keyboardKeyReleased(KEY_1, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTALT, timestamp++); kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); } void KeyboardLayoutTest::testDBusServiceExport() { // verifies that the dbus service is only exported if there are at least two layouts // first configure layouts, with just one layout KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); layoutGroup.writeEntry("LayoutList", QStringLiteral("us")); layoutGroup.sync(); reconfigureLayouts(); auto xkb = input()->keyboard()->xkb(); QTRY_COMPARE(xkb->numberOfLayouts(), 1u); // default layout is English QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)")); // with one layout we should not have the dbus interface QTRY_VERIFY(!QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.keyboard")).value()); // reconfigure to two layouts layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de")); layoutGroup.sync(); reconfigureLayouts(); QTRY_COMPARE(xkb->numberOfLayouts(), 2u); QTRY_VERIFY(QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.keyboard")).value()); // and back to one layout layoutGroup.writeEntry("LayoutList", QStringLiteral("us")); layoutGroup.sync(); reconfigureLayouts(); QTRY_COMPARE(xkb->numberOfLayouts(), 1u); 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)")); } 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)")); } +void KeyboardLayoutTest::testApplicationPolicy() +{ + KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); + layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)")); + layoutGroup.writeEntry("SwitchMode", QStringLiteral("WinClass")); + 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); + }; + LayoutChangedSignalWrapper wrapper; + QSignalSpy layoutChangedSpy(&wrapper, &LayoutChangedSignalWrapper::layoutChanged); + QVERIFY(layoutChangedSpy.isValid()); + auto reply = changeLayout(QStringLiteral("German")); + QVERIFY(layoutChangedSpy.wait()); + QCOMPARE(layoutChangedSpy.count(), 1); + 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); + // it is the same application and should not switch the layout + QVERIFY(!layoutChangedSpy.wait()); + QCOMPARE(layoutChangedSpy.count(), 1); + // now change to another layout + reply = changeLayout(QStringLiteral("German (Neo 2)")); + QVERIFY(layoutChangedSpy.wait()); + QCOMPARE(layoutChangedSpy.count(), 2); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + // activate other window + workspace()->activateClient(c1); + QVERIFY(!layoutChangedSpy.wait()); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + workspace()->activateClient(c2); + QVERIFY(!layoutChangedSpy.wait()); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + shellSurface2.reset(); + surface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(c2)); + QVERIFY(!layoutChangedSpy.wait()); + QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); +} + WAYLANDTEST_MAIN(KeyboardLayoutTest) #include "keyboard_layout_test.moc" diff --git a/keyboard_layout_switching.cpp b/keyboard_layout_switching.cpp index c93c62459..e397b7378 100644 --- a/keyboard_layout_switching.cpp +++ b/keyboard_layout_switching.cpp @@ -1,186 +1,258 @@ /******************************************************************** 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 "abstract_client.h" #include "deleted.h" #include "virtualdesktops.h" #include "workspace.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); } if (policy.toLower() == QStringLiteral("window")) { return new WindowPolicy(xkb, layout); } + if (policy.toLower() == QStringLiteral("winclass")) { + return new ApplicationPolicy(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(); } 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; } setLayout(getLayout(m_layouts, d)); } 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; } } 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; } } +ApplicationPolicy::ApplicationPolicy(KWin::Xkb* xkb, KWin::KeyboardLayout* layout) + : Policy(xkb, layout) +{ + connect(workspace(), &Workspace::clientActivated, this, &ApplicationPolicy::clientActivated); +} + +ApplicationPolicy::~ApplicationPolicy() +{ +} + +void ApplicationPolicy::clientActivated(AbstractClient *c) +{ + if (!c) { + return; + } + // ignore some special types + if (c->isDesktop() || c->isDock()) { + return; + } + quint32 layout = 0; + for (auto it = m_layouts.constBegin(); it != m_layouts.constEnd(); it++) { + if (AbstractClient::belongToSameApplication(c, it.key())) { + layout = it.value(); + break; + } + } + setLayout(layout); +} + +void ApplicationPolicy::clearCache() +{ + m_layouts.clear(); +} + +void ApplicationPolicy::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; + } + // update all layouts for the application + for (it = m_layouts.begin(); it != m_layouts.end(); it++) { + if (!AbstractClient::belongToSameApplication(it.key(), c)) { + continue; + } + it.value() = l; + } +} + } } diff --git a/keyboard_layout_switching.h b/keyboard_layout_switching.h index 33cdd8d96..4e21fee80 100644 --- a/keyboard_layout_switching.h +++ b/keyboard_layout_switching.h @@ -1,118 +1,138 @@ /******************************************************************** 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 AbstractClient; 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; }; 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; }; +class ApplicationPolicy : public Policy +{ + Q_OBJECT +public: + explicit ApplicationPolicy(Xkb *xkb, KeyboardLayout *layout); + ~ApplicationPolicy() override; + + QString name() const override { + return QStringLiteral("WinClass"); + } + +protected: + void clearCache() override; + void layoutChanged() override; + +private: + void clientActivated(AbstractClient *c); + QHash m_layouts; +}; + } } #endif