File indexing completed on 2024-12-01 13:37:31

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2016, 2017 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "keyboard_layout.h"
0010 #include "input_event.h"
0011 #include "keyboard_input.h"
0012 #include "keyboard_layout_switching.h"
0013 #include "xkb.h"
0014 
0015 #include <KGlobalAccel>
0016 #include <KLocalizedString>
0017 #include <QAction>
0018 #include <QDBusConnection>
0019 #include <QDBusMessage>
0020 #include <QDBusMetaType>
0021 #include <QDBusPendingCall>
0022 
0023 namespace KWin
0024 {
0025 
0026 KeyboardLayout::KeyboardLayout(Xkb *xkb, const KSharedConfigPtr &config)
0027     : QObject()
0028     , m_xkb(xkb)
0029     , m_configGroup(config->group("Layout"))
0030 {
0031 }
0032 
0033 KeyboardLayout::~KeyboardLayout() = default;
0034 
0035 static QString translatedLayout(const QString &layout)
0036 {
0037     return i18nd("xkeyboard-config", layout.toUtf8().constData());
0038 }
0039 
0040 void KeyboardLayout::init()
0041 {
0042     QAction *switchKeyboardAction = new QAction(this);
0043     switchKeyboardAction->setObjectName(QStringLiteral("Switch to Next Keyboard Layout"));
0044     switchKeyboardAction->setProperty("componentName", QStringLiteral("KDE Keyboard Layout Switcher"));
0045     switchKeyboardAction->setProperty("componentDisplayName", i18n("Keyboard Layout Switcher"));
0046     const QKeySequence sequence = QKeySequence(Qt::META | Qt::ALT | Qt::Key_K);
0047     KGlobalAccel::self()->setDefaultShortcut(switchKeyboardAction, QList<QKeySequence>({sequence}));
0048     KGlobalAccel::self()->setShortcut(switchKeyboardAction, QList<QKeySequence>({sequence}));
0049 
0050     connect(switchKeyboardAction, &QAction::triggered, this, &KeyboardLayout::switchToNextLayout);
0051 
0052     QDBusConnection::sessionBus().connect(QString(),
0053                                           QStringLiteral("/Layouts"),
0054                                           QStringLiteral("org.kde.keyboard"),
0055                                           QStringLiteral("reloadConfig"),
0056                                           this,
0057                                           SLOT(reconfigure()));
0058 
0059     reconfigure();
0060 }
0061 
0062 void KeyboardLayout::initDBusInterface()
0063 {
0064     if (m_xkb->numberOfLayouts() <= 1) {
0065         if (m_dbusInterface) {
0066             m_dbusInterface->deleteLater();
0067             m_dbusInterface = nullptr;
0068         }
0069         return;
0070     }
0071     if (m_dbusInterface) {
0072         return;
0073     }
0074     m_dbusInterface = new KeyboardLayoutDBusInterface(m_xkb, m_configGroup, this);
0075     connect(this, &KeyboardLayout::layoutChanged,
0076             m_dbusInterface, &KeyboardLayoutDBusInterface::layoutChanged);
0077     // TODO: the signal might be emitted even if the list didn't change
0078     connect(this, &KeyboardLayout::layoutsReconfigured, m_dbusInterface, &KeyboardLayoutDBusInterface::layoutListChanged);
0079 }
0080 
0081 void KeyboardLayout::switchToNextLayout()
0082 {
0083     const quint32 previousLayout = m_xkb->currentLayout();
0084     m_xkb->switchToNextLayout();
0085     checkLayoutChange(previousLayout);
0086 }
0087 
0088 void KeyboardLayout::switchToPreviousLayout()
0089 {
0090     const quint32 previousLayout = m_xkb->currentLayout();
0091     m_xkb->switchToPreviousLayout();
0092     checkLayoutChange(previousLayout);
0093 }
0094 
0095 void KeyboardLayout::switchToLayout(xkb_layout_index_t index)
0096 {
0097     const quint32 previousLayout = m_xkb->currentLayout();
0098     m_xkb->switchToLayout(index);
0099     checkLayoutChange(previousLayout);
0100 }
0101 
0102 void KeyboardLayout::reconfigure()
0103 {
0104     if (m_configGroup.isValid()) {
0105         m_configGroup.config()->reparseConfiguration();
0106         const QString policyKey = m_configGroup.readEntry("SwitchMode", QStringLiteral("Global"));
0107         m_xkb->reconfigure();
0108         if (!m_policy || m_policy->name() != policyKey) {
0109             m_policy = KeyboardLayoutSwitching::Policy::create(m_xkb, this, m_configGroup, policyKey);
0110         }
0111     } else {
0112         m_xkb->reconfigure();
0113     }
0114     resetLayout();
0115 }
0116 
0117 void KeyboardLayout::resetLayout()
0118 {
0119     m_layout = m_xkb->currentLayout();
0120     loadShortcuts();
0121 
0122     initDBusInterface();
0123     Q_EMIT layoutsReconfigured();
0124 }
0125 
0126 void KeyboardLayout::loadShortcuts()
0127 {
0128     qDeleteAll(m_layoutShortcuts);
0129     m_layoutShortcuts.clear();
0130     const QString componentName = QStringLiteral("KDE Keyboard Layout Switcher");
0131     const quint32 count = m_xkb->numberOfLayouts();
0132     for (uint i = 0; i < count; ++i) {
0133         // layout name is translated in the action name in keyboard kcm!
0134         const QString action = QStringLiteral("Switch keyboard layout to %1").arg(translatedLayout(m_xkb->layoutName(i)));
0135         const auto shortcuts = KGlobalAccel::self()->globalShortcut(componentName, action);
0136         if (shortcuts.isEmpty()) {
0137             continue;
0138         }
0139         QAction *a = new QAction(this);
0140         a->setObjectName(action);
0141         a->setProperty("componentName", componentName);
0142         connect(a, &QAction::triggered, this,
0143                 std::bind(&KeyboardLayout::switchToLayout, this, i));
0144         KGlobalAccel::self()->setShortcut(a, shortcuts, KGlobalAccel::Autoloading);
0145         m_layoutShortcuts << a;
0146     }
0147 }
0148 
0149 void KeyboardLayout::checkLayoutChange(uint previousLayout)
0150 {
0151     // Get here on key event or DBus call.
0152     // m_layout - layout saved last time OSD occurred
0153     // previousLayout - actual layout just before potential layout change
0154     // We need OSD if current layout deviates from any of these
0155     const uint currentLayout = m_xkb->currentLayout();
0156     if (m_layout != currentLayout || previousLayout != currentLayout) {
0157         m_layout = currentLayout;
0158         notifyLayoutChange();
0159         Q_EMIT layoutChanged(currentLayout);
0160     }
0161 }
0162 
0163 void KeyboardLayout::notifyLayoutChange()
0164 {
0165     // notify OSD service about the new layout
0166     QDBusMessage msg = QDBusMessage::createMethodCall(
0167         QStringLiteral("org.kde.plasmashell"),
0168         QStringLiteral("/org/kde/osdService"),
0169         QStringLiteral("org.kde.osdService"),
0170         QStringLiteral("kbdLayoutChanged"));
0171 
0172     msg << translatedLayout(m_xkb->layoutName());
0173 
0174     QDBusConnection::sessionBus().asyncCall(msg);
0175 }
0176 
0177 static const QString s_keyboardService = QStringLiteral("org.kde.keyboard");
0178 static const QString s_keyboardObject = QStringLiteral("/Layouts");
0179 
0180 KeyboardLayoutDBusInterface::KeyboardLayoutDBusInterface(Xkb *xkb, const KConfigGroup &configGroup, KeyboardLayout *parent)
0181     : QObject(parent)
0182     , m_xkb(xkb)
0183     , m_configGroup(configGroup)
0184     , m_keyboardLayout(parent)
0185 {
0186     qRegisterMetaType<QVector<LayoutNames>>("QVector<LayoutNames>");
0187     qDBusRegisterMetaType<LayoutNames>();
0188     qDBusRegisterMetaType<QVector<LayoutNames>>();
0189 
0190     QDBusConnection::sessionBus().registerObject(s_keyboardObject, this, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
0191     QDBusConnection::sessionBus().registerService(s_keyboardService);
0192 }
0193 
0194 KeyboardLayoutDBusInterface::~KeyboardLayoutDBusInterface()
0195 {
0196     QDBusConnection::sessionBus().unregisterService(s_keyboardService);
0197 }
0198 
0199 void KeyboardLayoutDBusInterface::switchToNextLayout()
0200 {
0201     m_keyboardLayout->switchToNextLayout();
0202 }
0203 
0204 void KeyboardLayoutDBusInterface::switchToPreviousLayout()
0205 {
0206     m_keyboardLayout->switchToPreviousLayout();
0207 }
0208 
0209 bool KeyboardLayoutDBusInterface::setLayout(uint index)
0210 {
0211     const quint32 previousLayout = m_xkb->currentLayout();
0212     if (!m_xkb->switchToLayout(index)) {
0213         return false;
0214     }
0215     m_keyboardLayout->checkLayoutChange(previousLayout);
0216     return true;
0217 }
0218 
0219 uint KeyboardLayoutDBusInterface::getLayout() const
0220 {
0221     return m_xkb->currentLayout();
0222 }
0223 
0224 QVector<KeyboardLayoutDBusInterface::LayoutNames> KeyboardLayoutDBusInterface::getLayoutsList() const
0225 {
0226     // TODO: - should be handled by layout applet itself, it has nothing to do with KWin
0227     const QStringList displayNames = m_configGroup.readEntry("DisplayNames", QStringList());
0228 
0229     QVector<LayoutNames> ret;
0230     const int layoutsSize = m_xkb->numberOfLayouts();
0231     const int displayNamesSize = displayNames.size();
0232     for (int i = 0; i < layoutsSize; ++i) {
0233         ret.append({m_xkb->layoutShortName(i), i < displayNamesSize ? displayNames.at(i) : QString(), translatedLayout(m_xkb->layoutName(i))});
0234     }
0235     return ret;
0236 }
0237 
0238 QDBusArgument &operator<<(QDBusArgument &argument, const KeyboardLayoutDBusInterface::LayoutNames &layoutNames)
0239 {
0240     argument.beginStructure();
0241     argument << layoutNames.shortName << layoutNames.displayName << layoutNames.longName;
0242     argument.endStructure();
0243     return argument;
0244 }
0245 
0246 const QDBusArgument &operator>>(const QDBusArgument &argument, KeyboardLayoutDBusInterface::LayoutNames &layoutNames)
0247 {
0248     argument.beginStructure();
0249     argument >> layoutNames.shortName >> layoutNames.displayName >> layoutNames.longName;
0250     argument.endStructure();
0251     return argument;
0252 }
0253 
0254 }