save default keyboard layout

Implemented for Global, Virtual Desktop and Application layout policies.
Not implemented for Window policy due separate windows do not preserve
their IDs between sessions (still could be implemented the same way as for Application policy).

Layout saving/restoring happens on Session save/load.
Covered by unit tests
This commit is contained in:
Andrey Butirsky 2020-05-29 20:08:24 +03:00 committed by David Edmundson
parent cf27128877
commit 8e1018de2c
4 changed files with 326 additions and 143 deletions

View file

@ -47,6 +47,22 @@ static const QString s_socketName = QStringLiteral("wayland_test_kwin_keyboard_l
class KeyboardLayoutTest : public QObject
{
Q_OBJECT
public:
KeyboardLayoutTest()
: layoutsReconfiguredSpy(this, &KeyboardLayoutTest::layoutListChanged)
, layoutChangedSpy(this, &KeyboardLayoutTest::layoutChanged)
{
QVERIFY(layoutsReconfiguredSpy.isValid());
QVERIFY(layoutChangedSpy.isValid());
QVERIFY(QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts"), QStringLiteral("layoutListChanged"), this, SIGNAL(layoutListChanged())));
QVERIFY(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 layoutListChanged();
private Q_SLOTS:
void initTestCase();
void init();
@ -63,13 +79,64 @@ private Q_SLOTS:
private:
void reconfigureLayouts();
void resetLayouts();
auto changeLayout(const QString &layoutName);
void callSession(const QString &method);
QSignalSpy layoutsReconfiguredSpy;
QSignalSpy layoutChangedSpy;
KConfigGroup layoutGroup;
};
void KeyboardLayoutTest::reconfigureLayouts()
{
// create DBus signal to reload
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/Layouts"), QStringLiteral("org.kde.keyboard"), QStringLiteral("reloadConfig"));
QDBusConnection::sessionBus().send(message);
QVERIFY(QDBusConnection::sessionBus().send(message));
// we don't get the signal when traversing to one-layout configuration
if ( layoutGroup.readEntry("LayoutList").contains(',') ) {
QVERIFY(layoutsReconfiguredSpy.wait(1000));
QCOMPARE(layoutsReconfiguredSpy.count(), 1);
layoutsReconfiguredSpy.clear();
}
}
void KeyboardLayoutTest::resetLayouts()
{
/* Switch Policy to destroy layouts from memory.
* On return to original Policy they should reload from disk.
*/
callSession(QStringLiteral("aboutToSaveSession"));
const QString policy = layoutGroup.readEntry("SwitchMode", "Global");
if (policy == QLatin1String("Global")) {
layoutGroup.writeEntry("SwitchMode", "Desktop");
} else {
layoutGroup.deleteEntry("SwitchMode");
}
reconfigureLayouts();
layoutGroup.writeEntry("SwitchMode", policy);
reconfigureLayouts();
callSession(QStringLiteral("loadSession"));
}
auto KeyboardLayoutTest::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);
}
void KeyboardLayoutTest::callSession(const QString &method) {
QDBusMessage msg = QDBusMessage::createMethodCall(
QStringLiteral("org.kde.KWin"),
QStringLiteral("/Session"),
QStringLiteral("org.kde.KWin.Session"),
method);
msg << QLatin1String(); // session name
QVERIFY(QDBusConnection::sessionBus().call(msg).type() != QDBusMessage::ErrorMessage);
}
void KeyboardLayoutTest::initTestCase()
@ -83,9 +150,17 @@ void KeyboardLayoutTest::initTestCase()
kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
kwinApp()->setKxkbConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
layoutGroup = kwinApp()->kxkbConfig()->group("Layout");
layoutGroup.deleteGroup();
kwinApp()->start();
QVERIFY(applicationStartedSpy.wait());
waylandServer()->initWorkspace();
// don't get DBus signal on one-layout configuration
// QVERIFY(layoutsReconfiguredSpy.wait());
// QCOMPARE(layoutsReconfiguredSpy.count(), 1);
// layoutsReconfiguredSpy.clear();
}
void KeyboardLayoutTest::init()
@ -98,20 +173,6 @@ 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
@ -147,42 +208,42 @@ 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
// now we should have three 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());
// place garbage to layout entry
layoutGroup.writeEntry("LayoutDefaultFoo", "garbage");
// make sure the garbage is wiped out on saving
resetLayouts();
QVERIFY(!layoutGroup.hasKey("LayoutDefaultFoo"));
// 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);
};
// now change through DBus to English
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();
// layout should persist after reset
resetLayouts();
QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
// 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.wait(1000));
QVERIFY(layoutChangedSpy.isEmpty());
// switch to another layout should work
@ -190,16 +251,17 @@ void KeyboardLayoutTest::testChangeLayoutThroughDBus()
QVERIFY(!reply.isError());
QCOMPARE(reply.reply().arguments().first().toBool(), true);
QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
QVERIFY(layoutChangedSpy.wait());
QCOMPARE(layoutChangedSpy.count(), 1);
layoutChangedSpy.clear();
// FIXME: need to pass
// QVERIFY(layoutChangedSpy.wait(1000));
// 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.wait(1000));
QVERIFY(layoutChangedSpy.isEmpty());
}
@ -207,7 +269,6 @@ 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();
@ -224,17 +285,13 @@ void KeyboardLayoutTest::testPerLayoutShortcut()
KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>{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);
reconfigureLayouts();
QCOMPARE(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());
QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
// now switch to English through the global shortcut
quint32 timestamp = 1;
@ -261,14 +318,13 @@ 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)"));
QCOMPARE(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());
@ -289,52 +345,52 @@ void KeyboardLayoutTest::testDBusServiceExport()
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)"));
QCOMPARE(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)"));
// give desktops different layouts
uint desktop, layout;
for (desktop = 0; desktop < VirtualDesktopManager::self()->count(); ++desktop) {
// switch to another virtual desktop
VirtualDesktopManager::self()->setCurrent(desktops.at(desktop));
QCOMPARE(desktops.at(desktop), VirtualDesktopManager::self()->currentDesktop());
// should be reset to English
QTRY_COMPARE(xkb->currentLayout(), 0);
// change first desktop to German
layout = (desktop + 1) % xkb->numberOfLayouts();
changeLayout(xkb->layoutNames()[layout]).waitForFinished();
QCOMPARE(xkb->currentLayout(), layout);
}
// imitate app restart to test layouts saving feature
resetLayouts();
// check layout set on desktop switching as intended
for(--desktop;;) {
QCOMPARE(desktops.at(desktop), VirtualDesktopManager::self()->currentDesktop());
layout = (desktop + 1) % xkb->numberOfLayouts();
QCOMPARE(xkb->currentLayout(), layout);
if (--desktop >= VirtualDesktopManager::self()->count()) // overflow
break;
VirtualDesktopManager::self()->setCurrent(desktops.at(desktop));
}
// remove virtual desktops
desktop = 0;
const KWin::VirtualDesktop* deletedDesktop = desktops.last();
VirtualDesktopManager::self()->setCount(1);
QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German"));
QTRY_COMPARE(xkb->currentLayout(), layout = (desktop + 1) % xkb->numberOfLayouts());
QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
// add another desktop
VirtualDesktopManager::self()->setCount(2);
@ -345,18 +401,23 @@ void KeyboardLayoutTest::testVirtualDesktopPolicy()
VirtualDesktopManager::self()->setCurrent(desktops.last());
QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
// check there are no more layouts left in config than the last actual non-default layouts number
QSignalSpy deletedDesktopSpy(deletedDesktop, &VirtualDesktop::aboutToBeDestroyed);
QVERIFY(deletedDesktopSpy.isValid());
QVERIFY(deletedDesktopSpy.wait());
resetLayouts();
QCOMPARE(layoutGroup.keyList().filter( QStringLiteral("LayoutDefault") ).count(), 1);
}
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)"));
QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
// create a window
using namespace KWayland::Client;
@ -366,14 +427,9 @@ void KeyboardLayoutTest::testWindowPolicy()
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"));
QCOMPARE(xkb->layoutName(), QStringLiteral("German"));
// create a second window
QScopedPointer<Surface> surface2(Test::createSurface());
@ -381,11 +437,11 @@ void KeyboardLayoutTest::testWindowPolicy()
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)"));
QCOMPARE(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)"));
QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
// activate other window
workspace()->activateClient(c1);
@ -396,77 +452,71 @@ void KeyboardLayoutTest::testWindowPolicy()
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)"));
QCOMPARE(xkb->numberOfLayouts(), 3u);
QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
// create a window
using namespace KWayland::Client;
QScopedPointer<Surface> surface(Test::createSurface());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
shellSurface->setAppId(QByteArrayLiteral("org.kde.foo"));
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<Surface> surface2(Test::createSurface());
QScopedPointer<XdgShellSurface> shellSurface2(Test::createXdgShellStableSurface(surface2.data()));
shellSurface2->setAppId(QByteArrayLiteral("org.kde.foo"));
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)"));
// now switch layout
layoutChangedSpy.clear();
changeLayout(QStringLiteral("German (Neo 2)"));
QVERIFY(layoutChangedSpy.wait());
QCOMPARE(layoutChangedSpy.count(), 2);
QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
QCOMPARE(layoutChangedSpy.count(), 1);
QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
resetLayouts();
// to trigger layout apply for current client
workspace()->activateClient(c1);
workspace()->activateClient(c2);
QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
// activate other window
workspace()->activateClient(c1);
QVERIFY(!layoutChangedSpy.wait());
QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
// it is the same application and should not switch the layout
QVERIFY(!layoutChangedSpy.wait(1000));
QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
workspace()->activateClient(c2);
QVERIFY(!layoutChangedSpy.wait());
QTRY_COMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
QVERIFY(!layoutChangedSpy.wait(1000));
QCOMPARE(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)"));
QVERIFY(!layoutChangedSpy.wait(1000));
QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)"));
resetLayouts();
QCOMPARE(layoutGroup.keyList().filter( QStringLiteral("LayoutDefault") ).count(), 1);
}
void KeyboardLayoutTest::testNumLock()
{
qputenv("KWIN_FORCE_NUM_LOCK_EVALUATION", "1");
KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout");
layoutGroup.writeEntry("LayoutList", QStringLiteral("us"));
layoutGroup.sync();
reconfigureLayouts();
auto xkb = input()->keyboard()->xkb();
QTRY_COMPARE(xkb->numberOfLayouts(), 1u);
QTRY_COMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)"));
// by default not set
QVERIFY(!xkb->leds().testFlag(Xkb::LED::NumLock));
@ -504,6 +554,5 @@ void KeyboardLayoutTest::testNumLock()
QVERIFY(!xkb->leds().testFlag(Xkb::LED::NumLock));
}
WAYLANDTEST_MAIN(KeyboardLayoutTest)
#include "keyboard_layout_test.moc"

View file

@ -161,13 +161,16 @@ void KeyboardLayout::reconfigure()
{
if (m_config) {
m_config->reparseConfiguration();
const QString policyKey = m_config->group(QStringLiteral("Layout")).readEntry("SwitchMode", QStringLiteral("Global"));
const KConfigGroup layoutGroup = m_config->group("Layout");
const QString policyKey = layoutGroup.readEntry("SwitchMode", QStringLiteral("Global"));
m_xkb->reconfigure();
if (!m_policy || m_policy->name() != policyKey) {
delete m_policy;
m_policy = KeyboardLayoutSwitching::Policy::create(m_xkb, this, policyKey);
m_policy = KeyboardLayoutSwitching::Policy::create(m_xkb, this, layoutGroup, policyKey);
}
} else {
m_xkb->reconfigure();
}
m_xkb->reconfigure();
resetLayout();
}
@ -178,9 +181,9 @@ void KeyboardLayout::resetLayout()
updateNotifier();
reinitNotifierMenu();
loadShortcuts();
emit layoutsReconfigured();
initDBusInterface();
emit layoutsReconfigured();
}
void KeyboardLayout::loadShortcuts()

View file

@ -31,8 +31,9 @@ namespace KWin
namespace KeyboardLayoutSwitching
{
Policy::Policy(Xkb *xkb, KeyboardLayout *layout)
Policy::Policy(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config)
: QObject(layout)
, m_config(config)
, m_xkb(xkb)
, m_layout(layout)
{
@ -52,31 +53,108 @@ quint32 Policy::layout() const
return m_xkb->currentLayout();
}
Policy *Policy::create(Xkb *xkb, KeyboardLayout *layout, const QString &policy)
Policy *Policy::create(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config, const QString &policy)
{
if (policy.toLower() == QStringLiteral("desktop")) {
return new VirtualDesktopPolicy(xkb, layout);
return new VirtualDesktopPolicy(xkb, layout, config);
}
if (policy.toLower() == QStringLiteral("window")) {
return new WindowPolicy(xkb, layout);
}
if (policy.toLower() == QStringLiteral("winclass")) {
return new ApplicationPolicy(xkb, layout);
return new ApplicationPolicy(xkb, layout, config);
}
return new GlobalPolicy(xkb, layout);
return new GlobalPolicy(xkb, layout, config);
}
GlobalPolicy::GlobalPolicy(Xkb *xkb, KeyboardLayout *layout)
: Policy(xkb, layout)
const char Policy::defaultLayoutEntryKeyPrefix[] = "LayoutDefault";
const QString Policy::defaultLayoutEntryKey() const
{
return QLatin1String(defaultLayoutEntryKeyPrefix) % name() % QLatin1Char('_');
}
void Policy::clearLayouts()
{
const QStringList layoutEntryList = m_config.keyList().filter(defaultLayoutEntryKeyPrefix);
for (const auto &layoutEntry : layoutEntryList) {
m_config.deleteEntry(layoutEntry);
}
}
const QString GlobalPolicy::defaultLayoutEntryKey() const
{
return QLatin1String(defaultLayoutEntryKeyPrefix) % name();
}
GlobalPolicy::GlobalPolicy(Xkb *xkb, KeyboardLayout *_layout, const KConfigGroup &config)
: Policy(xkb, _layout, config)
{
connect(workspace()->sessionManager(), &SessionManager::prepareSessionSaveRequested, this,
[this] (const QString &name) {
Q_UNUSED(name)
clearLayouts();
if (layout()) {
m_config.writeEntry(defaultLayoutEntryKey(), layout());
}
}
);
connect(workspace()->sessionManager(), &SessionManager::loadSessionRequested, this,
[this, xkb] (const QString &name) {
Q_UNUSED(name)
if (xkb->numberOfLayouts() > 1) {
xkb->switchToLayout(m_config.readEntry(defaultLayoutEntryKey(), 0));
}
}
);
}
GlobalPolicy::~GlobalPolicy() = default;
VirtualDesktopPolicy::VirtualDesktopPolicy(Xkb *xkb, KeyboardLayout *layout)
: Policy(xkb, layout)
VirtualDesktopPolicy::VirtualDesktopPolicy(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config)
: Policy(xkb, layout, config)
{
connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &VirtualDesktopPolicy::desktopChanged);
connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged,
this, &VirtualDesktopPolicy::desktopChanged);
connect(workspace()->sessionManager(), &SessionManager::prepareSessionSaveRequested, this,
[this] (const QString &name) {
Q_UNUSED(name)
clearLayouts();
for (auto i = m_layouts.constBegin(); i != m_layouts.constEnd(); ++i) {
if (const uint layout = *i) {
m_config.writeEntry(
defaultLayoutEntryKey() %
QLatin1String( QByteArray::number(i.key()->x11DesktopNumber()) ),
layout);
}
}
}
);
connect(workspace()->sessionManager(), &SessionManager::loadSessionRequested, this,
[this, xkb] (const QString &name) {
Q_UNUSED(name)
if (xkb->numberOfLayouts() > 1) {
for (KWin::VirtualDesktop* const desktop : VirtualDesktopManager::self()->desktops()) {
const uint layout = m_config.readEntry(
defaultLayoutEntryKey() %
QLatin1String( QByteArray::number(desktop->x11DesktopNumber()) ),
0u);
if (layout) {
m_layouts.insert(desktop, layout);
connect(desktop, &VirtualDesktop::aboutToBeDestroyed, this,
[this, desktop] {
m_layouts.remove(desktop);
}
);
}
}
desktopChanged();
}
}
);
}
VirtualDesktopPolicy::~VirtualDesktopPolicy() = default;
@ -185,10 +263,44 @@ void WindowPolicy::layoutChanged()
}
}
ApplicationPolicy::ApplicationPolicy(KWin::Xkb* xkb, KWin::KeyboardLayout* layout)
: Policy(xkb, layout)
ApplicationPolicy::ApplicationPolicy(KWin::Xkb* xkb, KWin::KeyboardLayout* layout, const KConfigGroup &config)
: Policy(xkb, layout, config)
{
connect(workspace(), &Workspace::clientActivated, this, &ApplicationPolicy::clientActivated);
connect(workspace()->sessionManager(), &SessionManager::prepareSessionSaveRequested, this,
[this] (const QString &name) {
Q_UNUSED(name)
clearLayouts();
for (auto i = m_layouts.constBegin(); i != m_layouts.constEnd(); ++i) {
if (const uint layout = *i) {
const QByteArray desktopFileName = i.key()->desktopFileName();
if (!desktopFileName.isEmpty()) {
m_config.writeEntry(
defaultLayoutEntryKey() % QLatin1String(desktopFileName),
layout);
}
}
}
}
);
connect(workspace()->sessionManager(), &SessionManager::loadSessionRequested, this,
[this, xkb] (const QString &name) {
Q_UNUSED(name)
if (xkb->numberOfLayouts() > 1) {
const QString keyPrefix = defaultLayoutEntryKey();
const QStringList keyList = m_config.keyList().filter(keyPrefix);
for (const QString& key : keyList) {
m_layoutsRestored.insert(
key.midRef(keyPrefix.size()).toLatin1(),
m_config.readEntry(key, 0));
}
}
m_layoutsRestored.squeeze();
}
);
}
ApplicationPolicy::~ApplicationPolicy()
@ -204,14 +316,22 @@ void ApplicationPolicy::clientActivated(AbstractClient *c)
if (c->isDesktop() || c->isDock()) {
return;
}
quint32 layout = 0;
for (auto it = m_layouts.constBegin(); it != m_layouts.constEnd(); it++) {
auto it = m_layouts.constFind(c);
if(it != m_layouts.constEnd()) {
setLayout(it.value());
return;
};
for (it = m_layouts.constBegin(); it != m_layouts.constEnd(); it++) {
if (AbstractClient::belongToSameApplication(c, it.key())) {
layout = it.value();
break;
setLayout(it.value());
layoutChanged();
return;
}
}
setLayout(layout);
setLayout( m_layoutsRestored.take(c->desktopFileName()) );
if (layout()) {
layoutChanged();
}
}
void ApplicationPolicy::clearCache()

View file

@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QObject>
#include <QHash>
#include <KConfigGroup>
namespace KWin
{
@ -42,16 +43,22 @@ public:
virtual QString name() const = 0;
static Policy *create(Xkb *xkb, KeyboardLayout *layout, const QString &policy);
static Policy *create(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config, const QString &policy);
protected:
explicit Policy(Xkb *xkb, KeyboardLayout *layout);
explicit Policy(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config = KConfigGroup());
virtual void clearCache() = 0;
virtual void layoutChanged() = 0;
void setLayout(quint32 layout);
quint32 layout() const;
KConfigGroup m_config;
virtual const QString defaultLayoutEntryKey() const;
void clearLayouts();
static const char defaultLayoutEntryKeyPrefix[];
private:
Xkb *m_xkb;
KeyboardLayout *m_layout;
@ -61,7 +68,7 @@ class GlobalPolicy : public Policy
{
Q_OBJECT
public:
explicit GlobalPolicy(Xkb *xkb, KeyboardLayout *layout);
explicit GlobalPolicy(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config);
~GlobalPolicy() override;
QString name() const override {
@ -71,13 +78,16 @@ public:
protected:
void clearCache() override {}
void layoutChanged() override {}
private:
const QString defaultLayoutEntryKey() const override;
};
class VirtualDesktopPolicy : public Policy
{
Q_OBJECT
public:
explicit VirtualDesktopPolicy(Xkb *xkb, KeyboardLayout *layout);
explicit VirtualDesktopPolicy(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config);
~VirtualDesktopPolicy() override;
QString name() const override {
@ -116,7 +126,7 @@ class ApplicationPolicy : public Policy
{
Q_OBJECT
public:
explicit ApplicationPolicy(Xkb *xkb, KeyboardLayout *layout);
explicit ApplicationPolicy(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config);
~ApplicationPolicy() override;
QString name() const override {
@ -130,6 +140,7 @@ protected:
private:
void clientActivated(AbstractClient *c);
QHash<AbstractClient*, quint32> m_layouts;
QHash<QByteArray, quint32> m_layoutsRestored;
};
}