Introduce support for keyboard layout switching policies

Summary:
This change introduces the initial support for keyboard layout switching
policies like in the X11 session. This first change only adds support for
Global and Virtual Desktop policy. This means the current layout is
stored in context to the current virtual desktop. Whenever one changes
the virtual desktop the previous layout is restored. If the user has not
yet navigated to this virtual desktop a switch to default layout is
performed.

This is the first code interacting with the new Virtual Desktop API which
is not based on integer ids. To fully support this the API is slightly
extended.

Test Plan: Added test case

Reviewers: #kwin, #plasma

Subscribers: plasma-devel, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D5301
This commit is contained in:
Martin Gräßlin 2017-04-04 18:56:10 +02:00
parent a5735e19b9
commit bf99d9ffdd
8 changed files with 314 additions and 3 deletions

View file

@ -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

View file

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "keyboard_input.h"
#include "keyboard_layout.h"
#include "platform.h"
#include "virtualdesktops.h"
#include "wayland_server.h"
#include <KConfigGroup>
@ -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"

View file

@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#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();

View file

@ -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<QAction*> m_layoutShortcuts;
KeyboardLayoutDBusInterface *m_dbusInterface = nullptr;
KeyboardLayoutSwitching::Policy *m_policy = nullptr;
};
class KeyboardLayoutDBusInterface : public QObject

View file

@ -0,0 +1,119 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2017 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#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;
}
}
}
}

View file

@ -0,0 +1,98 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2017 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_KEYBOARD_LAYOUT_SWITCHING_H
#define KWIN_KEYBOARD_LAYOUT_SWITCHING_H
#include <QObject>
#include <QHash>
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<VirtualDesktop *, quint32> m_layouts;
};
}
}
#endif

View file

@ -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()) {

View file

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define KWIN_VIRTUAL_DESKTOPS_H
// KWin
#include <kwinglobals.h>
#include <kwin_export.h>
// Qt includes
#include <QObject>
#include <QPoint>
@ -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