File indexing completed on 2024-04-28 05:30:21

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(QStringLiteral("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     QAction *switchLastUsedKeyboardAction = new QAction(this);
0053     switchLastUsedKeyboardAction->setObjectName(QStringLiteral("Switch to Last-Used Keyboard Layout"));
0054     switchLastUsedKeyboardAction->setProperty("componentName", QStringLiteral("KDE Keyboard Layout Switcher"));
0055     switchLastUsedKeyboardAction->setProperty("componentDisplayName", i18n("Keyboard Layout Switcher"));
0056     const QKeySequence sequenceLastUsed = QKeySequence(Qt::META | Qt::ALT | Qt::Key_L);
0057     KGlobalAccel::self()->setDefaultShortcut(switchLastUsedKeyboardAction, QList<QKeySequence>({sequenceLastUsed}));
0058     KGlobalAccel::self()->setShortcut(switchLastUsedKeyboardAction, QList<QKeySequence>({sequenceLastUsed}));
0059 
0060     connect(switchLastUsedKeyboardAction, &QAction::triggered, this, &KeyboardLayout::switchToLastUsedLayout);
0061 
0062     QDBusConnection::sessionBus().connect(QString(),
0063                                           QStringLiteral("/Layouts"),
0064                                           QStringLiteral("org.kde.keyboard"),
0065                                           QStringLiteral("reloadConfig"),
0066                                           this,
0067                                           SLOT(reconfigure()));
0068 
0069     reconfigure();
0070 }
0071 
0072 void KeyboardLayout::initDBusInterface()
0073 {
0074     if (m_xkb->numberOfLayouts() <= 1) {
0075         if (m_dbusInterface) {
0076             m_dbusInterface->deleteLater();
0077             m_dbusInterface = nullptr;
0078         }
0079         return;
0080     }
0081     if (m_dbusInterface) {
0082         return;
0083     }
0084     m_dbusInterface = new KeyboardLayoutDBusInterface(m_xkb, m_configGroup, this);
0085     connect(this, &KeyboardLayout::layoutChanged,
0086             m_dbusInterface, &KeyboardLayoutDBusInterface::layoutChanged);
0087     // TODO: the signal might be emitted even if the list didn't change
0088     connect(this, &KeyboardLayout::layoutsReconfigured, m_dbusInterface, &KeyboardLayoutDBusInterface::layoutListChanged);
0089 }
0090 
0091 void KeyboardLayout::switchToNextLayout()
0092 {
0093     const quint32 previousLayout = m_xkb->currentLayout();
0094     m_xkb->switchToNextLayout();
0095     checkLayoutChange(previousLayout);
0096 }
0097 
0098 void KeyboardLayout::switchToPreviousLayout()
0099 {
0100     const quint32 previousLayout = m_xkb->currentLayout();
0101     m_xkb->switchToPreviousLayout();
0102     checkLayoutChange(previousLayout);
0103 }
0104 
0105 void KeyboardLayout::switchToLayout(xkb_layout_index_t index)
0106 {
0107     const quint32 previousLayout = m_xkb->currentLayout();
0108     m_xkb->switchToLayout(index);
0109     checkLayoutChange(previousLayout);
0110 }
0111 
0112 void KeyboardLayout::switchToLastUsedLayout()
0113 {
0114     const quint32 count = m_xkb->numberOfLayouts();
0115     if (!m_lastUsedLayout.has_value() || *m_lastUsedLayout >= count) {
0116         switchToPreviousLayout();
0117     } else {
0118         switchToLayout(*m_lastUsedLayout);
0119     }
0120 }
0121 
0122 void KeyboardLayout::reconfigure()
0123 {
0124     if (m_configGroup.isValid()) {
0125         m_configGroup.config()->reparseConfiguration();
0126         const QString policyKey = m_configGroup.readEntry("SwitchMode", QStringLiteral("Global"));
0127         m_xkb->reconfigure();
0128         if (!m_policy || m_policy->name() != policyKey) {
0129             m_policy = KeyboardLayoutSwitching::Policy::create(m_xkb, this, m_configGroup, policyKey);
0130         }
0131     } else {
0132         m_xkb->reconfigure();
0133     }
0134     resetLayout();
0135 }
0136 
0137 void KeyboardLayout::resetLayout()
0138 {
0139     m_layout = m_xkb->currentLayout();
0140     loadShortcuts();
0141 
0142     initDBusInterface();
0143     Q_EMIT layoutsReconfigured();
0144 }
0145 
0146 void KeyboardLayout::loadShortcuts()
0147 {
0148     qDeleteAll(m_layoutShortcuts);
0149     m_layoutShortcuts.clear();
0150     const QString componentName = QStringLiteral("KDE Keyboard Layout Switcher");
0151     const quint32 count = m_xkb->numberOfLayouts();
0152     for (uint i = 0; i < count; ++i) {
0153         // layout name is translated in the action name in keyboard kcm!
0154         const QString action = QStringLiteral("Switch keyboard layout to %1").arg(translatedLayout(m_xkb->layoutName(i)));
0155         const auto shortcuts = KGlobalAccel::self()->globalShortcut(componentName, action);
0156         if (shortcuts.isEmpty()) {
0157             continue;
0158         }
0159         QAction *a = new QAction(this);
0160         a->setObjectName(action);
0161         a->setProperty("componentName", componentName);
0162         connect(a, &QAction::triggered, this,
0163                 std::bind(&KeyboardLayout::switchToLayout, this, i));
0164         KGlobalAccel::self()->setShortcut(a, shortcuts, KGlobalAccel::Autoloading);
0165         m_layoutShortcuts << a;
0166     }
0167 }
0168 
0169 void KeyboardLayout::checkLayoutChange(uint previousLayout)
0170 {
0171     // Get here on key event or DBus call.
0172     // m_layout - layout saved last time OSD occurred
0173     // previousLayout - actual layout just before potential layout change
0174     // We need OSD if current layout deviates from any of these
0175     const uint currentLayout = m_xkb->currentLayout();
0176     if (m_layout != currentLayout || previousLayout != currentLayout) {
0177         m_lastUsedLayout = std::optional<uint>{previousLayout};
0178         m_layout = currentLayout;
0179         notifyLayoutChange();
0180         Q_EMIT layoutChanged(currentLayout);
0181     }
0182 }
0183 
0184 void KeyboardLayout::notifyLayoutChange()
0185 {
0186     // notify OSD service about the new layout
0187     QDBusMessage msg = QDBusMessage::createMethodCall(
0188         QStringLiteral("org.kde.plasmashell"),
0189         QStringLiteral("/org/kde/osdService"),
0190         QStringLiteral("org.kde.osdService"),
0191         QStringLiteral("kbdLayoutChanged"));
0192 
0193     msg << translatedLayout(m_xkb->layoutName());
0194 
0195     QDBusConnection::sessionBus().asyncCall(msg);
0196 }
0197 
0198 static const QString s_keyboardService = QStringLiteral("org.kde.keyboard");
0199 static const QString s_keyboardObject = QStringLiteral("/Layouts");
0200 
0201 KeyboardLayoutDBusInterface::KeyboardLayoutDBusInterface(Xkb *xkb, const KConfigGroup &configGroup, KeyboardLayout *parent)
0202     : QObject(parent)
0203     , m_xkb(xkb)
0204     , m_configGroup(configGroup)
0205     , m_keyboardLayout(parent)
0206 {
0207     qRegisterMetaType<QList<LayoutNames>>("QList<LayoutNames>");
0208     qDBusRegisterMetaType<LayoutNames>();
0209     qDBusRegisterMetaType<QList<LayoutNames>>();
0210 
0211     QDBusConnection::sessionBus().registerObject(s_keyboardObject, this, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
0212     QDBusConnection::sessionBus().registerService(s_keyboardService);
0213 }
0214 
0215 KeyboardLayoutDBusInterface::~KeyboardLayoutDBusInterface()
0216 {
0217     QDBusConnection::sessionBus().unregisterService(s_keyboardService);
0218 }
0219 
0220 void KeyboardLayoutDBusInterface::switchToNextLayout()
0221 {
0222     m_keyboardLayout->switchToNextLayout();
0223 }
0224 
0225 void KeyboardLayoutDBusInterface::switchToPreviousLayout()
0226 {
0227     m_keyboardLayout->switchToPreviousLayout();
0228 }
0229 
0230 bool KeyboardLayoutDBusInterface::setLayout(uint index)
0231 {
0232     const quint32 previousLayout = m_xkb->currentLayout();
0233     if (!m_xkb->switchToLayout(index)) {
0234         return false;
0235     }
0236     m_keyboardLayout->checkLayoutChange(previousLayout);
0237     return true;
0238 }
0239 
0240 uint KeyboardLayoutDBusInterface::getLayout() const
0241 {
0242     return m_xkb->currentLayout();
0243 }
0244 
0245 QList<KeyboardLayoutDBusInterface::LayoutNames> KeyboardLayoutDBusInterface::getLayoutsList() const
0246 {
0247     // TODO: - should be handled by layout applet itself, it has nothing to do with KWin
0248     const QStringList displayNames = m_configGroup.readEntry("DisplayNames", QStringList());
0249 
0250     QList<LayoutNames> ret;
0251     const int layoutsSize = m_xkb->numberOfLayouts();
0252     const int displayNamesSize = displayNames.size();
0253     for (int i = 0; i < layoutsSize; ++i) {
0254         ret.append({m_xkb->layoutShortName(i), i < displayNamesSize ? displayNames.at(i) : QString(), translatedLayout(m_xkb->layoutName(i))});
0255     }
0256     return ret;
0257 }
0258 
0259 QDBusArgument &operator<<(QDBusArgument &argument, const KeyboardLayoutDBusInterface::LayoutNames &layoutNames)
0260 {
0261     argument.beginStructure();
0262     argument << layoutNames.shortName << layoutNames.displayName << layoutNames.longName;
0263     argument.endStructure();
0264     return argument;
0265 }
0266 
0267 const QDBusArgument &operator>>(const QDBusArgument &argument, KeyboardLayoutDBusInterface::LayoutNames &layoutNames)
0268 {
0269     argument.beginStructure();
0270     argument >> layoutNames.shortName >> layoutNames.displayName >> layoutNames.longName;
0271     argument.endStructure();
0272     return argument;
0273 }
0274 
0275 }
0276 
0277 #include "moc_keyboard_layout.cpp"