kwin/keyboard_layout_switching.cpp
Andrey Butirsky 8e1018de2c 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
2020-07-17 12:42:28 +00:00

378 lines
11 KiB
C++

/********************************************************************
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 "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, const KConfigGroup &config)
: QObject(layout)
, m_config(config)
, 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 KConfigGroup &config, const QString &policy)
{
if (policy.toLower() == QStringLiteral("desktop")) {
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, config);
}
return new GlobalPolicy(xkb, layout, config);
}
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, const KConfigGroup &config)
: Policy(xkb, layout, config)
{
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;
void VirtualDesktopPolicy::clearCache()
{
m_layouts.clear();
}
namespace {
template <typename T, typename U>
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.end()) {
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.end()) {
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, 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()
{
}
void ApplicationPolicy::clientActivated(AbstractClient *c)
{
if (!c) {
return;
}
// ignore some special types
if (c->isDesktop() || c->isDock()) {
return;
}
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())) {
setLayout(it.value());
layoutChanged();
return;
}
}
setLayout( m_layoutsRestored.take(c->desktopFileName()) );
if (layout()) {
layoutChanged();
}
}
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.end()) {
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;
}
}
}
}