File indexing completed on 2024-04-28 16:48:52

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