Add support for keyboard layout switching policy "winclass"

Summary:
This is quite similar to the policy "window" - the main difference is
that windows from the same application share the layout. So only
switching to a window from another application changes the layout.

This change is the last policy to add for support of all the policies we
have on X11.

Reviewers: #kwin, #plasma

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D5365
icc-effect-5.14.5
Martin Gräßlin 2017-04-09 17:57:59 +02:00
parent c8274dbe57
commit b132fe7c24
3 changed files with 155 additions and 0 deletions

View File

@ -59,6 +59,7 @@ private Q_SLOTS:
void testDBusServiceExport();
void testVirtualDesktopPolicy();
void testWindowPolicy();
void testApplicationPolicy();
private:
void reconfigureLayouts();
@ -394,5 +395,67 @@ void KeyboardLayoutTest::testWindowPolicy()
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> surface(Test::createSurface());
QScopedPointer<ShellSurface> 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<Surface> surface2(Test::createSurface());
QScopedPointer<ShellSurface> 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"

View File

@ -60,6 +60,9 @@ Policy *Policy::create(Xkb *xkb, KeyboardLayout *layout, const QString &policy)
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);
}
@ -182,5 +185,74 @@ void WindowPolicy::layoutChanged()
}
}
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;
}
}
}
}

View File

@ -112,6 +112,26 @@ private:
QHash<AbstractClient*, quint32> 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<AbstractClient*, quint32> m_layouts;
};
}
}