File indexing completed on 2024-11-10 04:57:49

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2017 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "keyboard_layout_switching.h"
0010 #include "keyboard_layout.h"
0011 #include "virtualdesktops.h"
0012 #include "window.h"
0013 #include "workspace.h"
0014 #include "xkb.h"
0015 
0016 namespace KWin
0017 {
0018 
0019 namespace KeyboardLayoutSwitching
0020 {
0021 
0022 Policy::Policy(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config)
0023     : QObject(layout)
0024     , m_config(config)
0025     , m_xkb(xkb)
0026     , m_layout(layout)
0027 {
0028     connect(m_layout, &KeyboardLayout::layoutsReconfigured, this, &Policy::clearCache);
0029     connect(m_layout, &KeyboardLayout::layoutChanged, this, &Policy::layoutChanged);
0030 }
0031 
0032 Policy::~Policy() = default;
0033 
0034 void Policy::setLayout(uint index)
0035 {
0036     const uint previousLayout = m_xkb->currentLayout();
0037     m_xkb->switchToLayout(index);
0038     const uint currentLayout = m_xkb->currentLayout();
0039     if (previousLayout != currentLayout) {
0040         Q_EMIT m_layout->layoutChanged(currentLayout);
0041     }
0042 }
0043 
0044 std::unique_ptr<Policy> Policy::create(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config, const QString &policy)
0045 {
0046     if (policy.toLower() == QStringLiteral("desktop")) {
0047         return std::make_unique<VirtualDesktopPolicy>(xkb, layout, config);
0048     }
0049     if (policy.toLower() == QStringLiteral("window")) {
0050         return std::make_unique<WindowPolicy>(xkb, layout);
0051     }
0052     if (policy.toLower() == QStringLiteral("winclass")) {
0053         return std::make_unique<ApplicationPolicy>(xkb, layout, config);
0054     }
0055     return std::make_unique<GlobalPolicy>(xkb, layout, config);
0056 }
0057 
0058 const char Policy::defaultLayoutEntryKeyPrefix[] = "LayoutDefault";
0059 const QString Policy::defaultLayoutEntryKey() const
0060 {
0061     return QLatin1String(defaultLayoutEntryKeyPrefix) % name() % QLatin1Char('_');
0062 }
0063 
0064 void Policy::clearLayouts()
0065 {
0066     const QStringList layoutEntryList = m_config.keyList().filter(defaultLayoutEntryKeyPrefix);
0067     for (const auto &layoutEntry : layoutEntryList) {
0068         m_config.deleteEntry(layoutEntry);
0069     }
0070 }
0071 
0072 const QString GlobalPolicy::defaultLayoutEntryKey() const
0073 {
0074     return QLatin1String(defaultLayoutEntryKeyPrefix) % name();
0075 }
0076 
0077 GlobalPolicy::GlobalPolicy(Xkb *xkb, KeyboardLayout *_layout, const KConfigGroup &config)
0078     : Policy(xkb, _layout, config)
0079 {
0080     connect(workspace()->sessionManager(), &SessionManager::prepareSessionSaveRequested, this, [this, xkb](const QString &name) {
0081         clearLayouts();
0082         if (const uint layout = xkb->currentLayout()) {
0083             m_config.writeEntry(defaultLayoutEntryKey(), layout);
0084         }
0085     });
0086 
0087     connect(workspace()->sessionManager(), &SessionManager::loadSessionRequested, this, [this, xkb](const QString &name) {
0088         if (xkb->numberOfLayouts() > 1) {
0089             setLayout(m_config.readEntry(defaultLayoutEntryKey(), 0));
0090         }
0091     });
0092 }
0093 
0094 GlobalPolicy::~GlobalPolicy() = default;
0095 
0096 VirtualDesktopPolicy::VirtualDesktopPolicy(Xkb *xkb, KeyboardLayout *layout, const KConfigGroup &config)
0097     : Policy(xkb, layout, config)
0098 {
0099     connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged,
0100             this, &VirtualDesktopPolicy::desktopChanged);
0101 
0102     connect(workspace()->sessionManager(), &SessionManager::prepareSessionSaveRequested, this, [this](const QString &name) {
0103         clearLayouts();
0104 
0105         for (auto i = m_layouts.constBegin(); i != m_layouts.constEnd(); ++i) {
0106             if (const uint layout = *i) {
0107                 m_config.writeEntry(defaultLayoutEntryKey() % QString::number(i.key()->x11DesktopNumber()), layout);
0108             }
0109         }
0110     });
0111 
0112     connect(workspace()->sessionManager(), &SessionManager::loadSessionRequested, this, [this, xkb](const QString &name) {
0113         if (xkb->numberOfLayouts() > 1) {
0114             const auto &desktops = VirtualDesktopManager::self()->desktops();
0115             for (KWin::VirtualDesktop *const desktop : desktops) {
0116                 const uint layout = m_config.readEntry(defaultLayoutEntryKey() % QString::number(desktop->x11DesktopNumber()), 0u);
0117                 if (layout) {
0118                     m_layouts.insert(desktop, layout);
0119                     connect(desktop, &VirtualDesktop::aboutToBeDestroyed, this, [this, desktop]() {
0120                         m_layouts.remove(desktop);
0121                     });
0122                 }
0123             }
0124             desktopChanged();
0125         }
0126     });
0127 }
0128 
0129 VirtualDesktopPolicy::~VirtualDesktopPolicy() = default;
0130 
0131 void VirtualDesktopPolicy::clearCache()
0132 {
0133     m_layouts.clear();
0134 }
0135 
0136 namespace
0137 {
0138 template<typename T, typename U>
0139 quint32 getLayout(const T &layouts, const U &reference)
0140 {
0141     auto it = layouts.constFind(reference);
0142     if (it == layouts.constEnd()) {
0143         return 0;
0144     } else {
0145         return it.value();
0146     }
0147 }
0148 }
0149 
0150 void VirtualDesktopPolicy::desktopChanged()
0151 {
0152     auto d = VirtualDesktopManager::self()->currentDesktop();
0153     if (!d) {
0154         return;
0155     }
0156     setLayout(getLayout(m_layouts, d));
0157 }
0158 
0159 void VirtualDesktopPolicy::layoutChanged(uint index)
0160 {
0161     auto d = VirtualDesktopManager::self()->currentDesktop();
0162     if (!d) {
0163         return;
0164     }
0165     auto it = m_layouts.find(d);
0166     if (it == m_layouts.end()) {
0167         m_layouts.insert(d, index);
0168         connect(d, &VirtualDesktop::aboutToBeDestroyed, this, [this, d]() {
0169             m_layouts.remove(d);
0170         });
0171     } else {
0172         if (it.value() == index) {
0173             return;
0174         }
0175         it.value() = index;
0176     }
0177 }
0178 
0179 WindowPolicy::WindowPolicy(KWin::Xkb *xkb, KWin::KeyboardLayout *layout)
0180     : Policy(xkb, layout)
0181 {
0182     connect(workspace(), &Workspace::windowActivated, this, [this](Window *window) {
0183         if (!window) {
0184             return;
0185         }
0186         // ignore some special types
0187         if (window->isDesktop() || window->isDock()) {
0188             return;
0189         }
0190         setLayout(getLayout(m_layouts, window));
0191     });
0192 }
0193 
0194 WindowPolicy::~WindowPolicy()
0195 {
0196 }
0197 
0198 void WindowPolicy::clearCache()
0199 {
0200     m_layouts.clear();
0201 }
0202 
0203 void WindowPolicy::layoutChanged(uint index)
0204 {
0205     auto window = workspace()->activeWindow();
0206     if (!window) {
0207         return;
0208     }
0209     // ignore some special types
0210     if (window->isDesktop() || window->isDock()) {
0211         return;
0212     }
0213 
0214     auto it = m_layouts.find(window);
0215     if (it == m_layouts.end()) {
0216         m_layouts.insert(window, index);
0217         connect(window, &Window::closed, this, [this, window]() {
0218             m_layouts.remove(window);
0219         });
0220     } else {
0221         if (it.value() == index) {
0222             return;
0223         }
0224         it.value() = index;
0225     }
0226 }
0227 
0228 ApplicationPolicy::ApplicationPolicy(KWin::Xkb *xkb, KWin::KeyboardLayout *layout, const KConfigGroup &config)
0229     : Policy(xkb, layout, config)
0230 {
0231     connect(workspace(), &Workspace::windowActivated, this, &ApplicationPolicy::windowActivated);
0232 
0233     connect(workspace()->sessionManager(), &SessionManager::prepareSessionSaveRequested, this, [this](const QString &name) {
0234         clearLayouts();
0235 
0236         for (auto i = m_layouts.constBegin(); i != m_layouts.constEnd(); ++i) {
0237             if (const uint layout = *i) {
0238                 const QString desktopFileName = i.key()->desktopFileName();
0239                 if (!desktopFileName.isEmpty()) {
0240                     m_config.writeEntry(defaultLayoutEntryKey() % desktopFileName, layout);
0241                 }
0242             }
0243         }
0244     });
0245 
0246     connect(workspace()->sessionManager(), &SessionManager::loadSessionRequested, this, [this, xkb](const QString &name) {
0247         if (xkb->numberOfLayouts() > 1) {
0248             const QString keyPrefix = defaultLayoutEntryKey();
0249             const QStringList keyList = m_config.keyList().filter(keyPrefix);
0250             for (const QString &key : keyList) {
0251                 m_layoutsRestored.insert(
0252                     QStringView(key).mid(keyPrefix.size()).toLatin1(),
0253                     m_config.readEntry(key, 0));
0254             }
0255         }
0256         m_layoutsRestored.squeeze();
0257     });
0258 }
0259 
0260 ApplicationPolicy::~ApplicationPolicy()
0261 {
0262 }
0263 
0264 void ApplicationPolicy::windowActivated(Window *window)
0265 {
0266     if (!window) {
0267         return;
0268     }
0269     // ignore some special types
0270     if (window->isDesktop() || window->isDock()) {
0271         return;
0272     }
0273     auto it = m_layouts.constFind(window);
0274     if (it != m_layouts.constEnd()) {
0275         setLayout(it.value());
0276         return;
0277     };
0278     for (it = m_layouts.constBegin(); it != m_layouts.constEnd(); it++) {
0279         if (Window::belongToSameApplication(window, it.key())) {
0280             const uint layout = it.value();
0281             setLayout(layout);
0282             layoutChanged(layout);
0283             return;
0284         }
0285     }
0286     setLayout(m_layoutsRestored.take(window->desktopFileName()));
0287     if (const uint index = m_xkb->currentLayout()) {
0288         layoutChanged(index);
0289     }
0290 }
0291 
0292 void ApplicationPolicy::clearCache()
0293 {
0294     m_layouts.clear();
0295 }
0296 
0297 void ApplicationPolicy::layoutChanged(uint index)
0298 {
0299     auto window = workspace()->activeWindow();
0300     if (!window) {
0301         return;
0302     }
0303     // ignore some special types
0304     if (window->isDesktop() || window->isDock()) {
0305         return;
0306     }
0307 
0308     auto it = m_layouts.find(window);
0309     if (it == m_layouts.end()) {
0310         m_layouts.insert(window, index);
0311         connect(window, &Window::closed, this, [this, window]() {
0312             m_layouts.remove(window);
0313         });
0314     } else {
0315         if (it.value() == index) {
0316             return;
0317         }
0318         it.value() = index;
0319     }
0320     // update all layouts for the application
0321     for (it = m_layouts.begin(); it != m_layouts.end(); it++) {
0322         if (Window::belongToSameApplication(it.key(), window)) {
0323             it.value() = index;
0324         }
0325     }
0326 }
0327 
0328 }
0329 }
0330 
0331 #include "moc_keyboard_layout_switching.cpp"