File indexing completed on 2024-05-19 16:38:55

0001 /*
0002     SPDX-FileCopyrightText: 2010 Andriy Rysin <rysin@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "keyboard_daemon.h"
0008 #include "debug.h"
0009 
0010 #include <QAction>
0011 #include <QDBusConnection>
0012 #include <QDBusMessage>
0013 #include <QDBusPendingCall>
0014 #include <QProcess>
0015 
0016 #include <KPluginFactory>
0017 
0018 #include "keyboard_hardware.h"
0019 #include "layout_memory_persister.h"
0020 #include "x11_helper.h"
0021 #include "xinput_helper.h"
0022 #include "xkb_helper.h"
0023 #include "xkb_rules.h"
0024 #include "flags.h"
0025 
0026 K_PLUGIN_CLASS_WITH_JSON(KeyboardDaemon, "kded_keyboard.json")
0027 
0028 KeyboardDaemon::KeyboardDaemon(QObject *parent, const QList<QVariant> &)
0029     : KDEDModule(parent)
0030     , keyboardConfig(new KeyboardConfig(this))
0031     , actionCollection(nullptr)
0032     , xEventNotifier(nullptr)
0033     , layoutMemory(*keyboardConfig)
0034     , rules(Rules::readRules(Rules::READ_EXTRAS))
0035 {
0036     if (!X11Helper::xkbSupported(nullptr))
0037         return; // TODO: shut down the daemon?
0038 
0039     QDBusConnection dbus = QDBusConnection::sessionBus();
0040     dbus.registerService(KEYBOARD_DBUS_SERVICE_NAME);
0041     dbus.registerObject(KEYBOARD_DBUS_OBJECT_PATH, this, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableSignals);
0042     dbus.connect(QString(), KEYBOARD_DBUS_OBJECT_PATH, KEYBOARD_DBUS_SERVICE_NAME, KEYBOARD_DBUS_CONFIG_RELOAD_MESSAGE, this, SLOT(configureKeyboard()));
0043 
0044     LayoutNames::registerMetaType();
0045 
0046     configureKeyboard();
0047     registerListeners();
0048 
0049     LayoutMemoryPersister layoutMemoryPersister(layoutMemory);
0050     if (layoutMemoryPersister.restore()) {
0051         if (layoutMemoryPersister.getGlobalLayout().isValid()) {
0052             X11Helper::setLayout(layoutMemoryPersister.getGlobalLayout());
0053         }
0054     }
0055 }
0056 
0057 KeyboardDaemon::~KeyboardDaemon()
0058 {
0059     LayoutMemoryPersister layoutMemoryPersister(layoutMemory);
0060     layoutMemoryPersister.setGlobalLayout(X11Helper::getCurrentLayout());
0061     layoutMemoryPersister.save();
0062 
0063     QDBusConnection dbus = QDBusConnection::sessionBus();
0064     dbus.disconnect(QString(), KEYBOARD_DBUS_OBJECT_PATH, KEYBOARD_DBUS_SERVICE_NAME, KEYBOARD_DBUS_CONFIG_RELOAD_MESSAGE, this, SLOT(configureKeyboard()));
0065     dbus.unregisterObject(KEYBOARD_DBUS_OBJECT_PATH);
0066     dbus.unregisterService(KEYBOARD_DBUS_SERVICE_NAME);
0067 
0068     unregisterListeners();
0069     unregisterShortcut();
0070 
0071     delete xEventNotifier;
0072     delete rules;
0073 }
0074 
0075 void KeyboardDaemon::configureKeyboard()
0076 {
0077     qCDebug(KCM_KEYBOARD) << "Configuring keyboard";
0078     init_keyboard_hardware();
0079 
0080     keyboardConfig->load();
0081     XkbHelper::initializeKeyboardLayouts(*keyboardConfig);
0082     layoutMemory.configChanged();
0083 
0084     unregisterShortcut();
0085     registerShortcut();
0086 }
0087 
0088 void KeyboardDaemon::configureInput()
0089 {
0090     QStringList modules;
0091     modules << QStringLiteral("kcm_mouse") << QStringLiteral("kcm_touchpad");
0092     QProcess::startDetached(QStringLiteral("kcminit"), modules);
0093 }
0094 
0095 void KeyboardDaemon::registerShortcut()
0096 {
0097     if (actionCollection == nullptr) {
0098         actionCollection = new KeyboardLayoutActionCollection(this, false);
0099 
0100         QAction *toggleLayoutAction = actionCollection->getToggleAction();
0101         connect(toggleLayoutAction, &QAction::triggered, this, [this]() {
0102             switchToNextLayout();
0103 
0104             LayoutUnit newLayout = X11Helper::getCurrentLayout();
0105             QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
0106                                                               QStringLiteral("/org/kde/osdService"),
0107                                                               QStringLiteral("org.kde.osdService"),
0108                                                               QStringLiteral("kbdLayoutChanged"));
0109             msg << Flags::getLongText(newLayout, rules);
0110             QDBusConnection::sessionBus().asyncCall(msg);
0111         });
0112 
0113         actionCollection->loadLayoutShortcuts(keyboardConfig->layouts, rules);
0114         // clang-format off
0115         connect(actionCollection, SIGNAL(actionTriggered(QAction*)), this, SLOT(setLayout(QAction*)));
0116         // clang-format on
0117     }
0118 }
0119 
0120 void KeyboardDaemon::unregisterShortcut()
0121 {
0122     // register KDE keyboard shortcut for switching layouts
0123     if (actionCollection != nullptr) {
0124         // clang-format off
0125         disconnect(actionCollection, SIGNAL(actionTriggered(QAction*)), this, SLOT(setLayout(QAction*)));
0126         // clang-format on
0127         disconnect(actionCollection->getToggleAction(), &QAction::triggered, this, &KeyboardDaemon::switchToNextLayout);
0128 
0129         delete actionCollection;
0130         actionCollection = nullptr;
0131     }
0132 }
0133 
0134 void KeyboardDaemon::registerListeners()
0135 {
0136     if (xEventNotifier == nullptr) {
0137         xEventNotifier = new XInputEventNotifier();
0138     }
0139     connect(xEventNotifier, &XInputEventNotifier::newPointerDevice, this, &KeyboardDaemon::configureInput);
0140     connect(xEventNotifier, &XInputEventNotifier::newKeyboardDevice, this, &KeyboardDaemon::configureKeyboard);
0141     connect(xEventNotifier, &XEventNotifier::layoutMapChanged, this, &KeyboardDaemon::layoutMapChanged);
0142     connect(xEventNotifier, &XEventNotifier::layoutChanged, this, &KeyboardDaemon::layoutChangedSlot);
0143     xEventNotifier->start();
0144 }
0145 
0146 void KeyboardDaemon::unregisterListeners()
0147 {
0148     if (xEventNotifier != nullptr) {
0149         xEventNotifier->stop();
0150         disconnect(xEventNotifier, &XInputEventNotifier::newPointerDevice, this, &KeyboardDaemon::configureInput);
0151         disconnect(xEventNotifier, &XInputEventNotifier::newKeyboardDevice, this, &KeyboardDaemon::configureKeyboard);
0152         disconnect(xEventNotifier, &XEventNotifier::layoutChanged, this, &KeyboardDaemon::layoutChangedSlot);
0153         disconnect(xEventNotifier, &XEventNotifier::layoutMapChanged, this, &KeyboardDaemon::layoutMapChanged);
0154     }
0155 }
0156 
0157 void KeyboardDaemon::layoutChangedSlot()
0158 {
0159     layoutMemory.layoutChanged();
0160 
0161     Q_EMIT layoutChanged(getLayout());
0162 }
0163 
0164 void KeyboardDaemon::layoutMapChanged()
0165 {
0166     keyboardConfig->load();
0167     layoutMemory.layoutMapChanged();
0168     Q_EMIT layoutListChanged();
0169 }
0170 
0171 void KeyboardDaemon::switchToNextLayout()
0172 {
0173     X11Helper::scrollLayouts(1);
0174 }
0175 
0176 void KeyboardDaemon::switchToPreviousLayout()
0177 {
0178     X11Helper::scrollLayouts(-1);
0179 }
0180 
0181 bool KeyboardDaemon::setLayout(QAction *action)
0182 {
0183     if (action == actionCollection->getToggleAction())
0184         return false;
0185 
0186     return setLayout(action->data().toUInt());
0187 }
0188 
0189 bool KeyboardDaemon::setLayout(uint index)
0190 {
0191     if (keyboardConfig->layoutLoopCount() != KeyboardConfig::NO_LOOPING && index >= uint(keyboardConfig->layoutLoopCount())) {
0192         QList<LayoutUnit> layouts = X11Helper::getLayoutsList();
0193         const uint indexOfLastMainLayoutInConfig = keyboardConfig->layouts.lastIndexOf(layouts.takeLast());
0194         const uint indexOfLastMainLayoutInXKB = layouts.size();
0195 
0196         // Re-calculate indexes for layout switching Actions
0197         const auto &actions = actionCollection->actions();
0198         for (const auto &action : actions) {
0199             // clang-format off
0200             if (action->data().toUInt() == indexOfLastMainLayoutInXKB) {
0201                 action->setData(indexOfLastMainLayoutInConfig < index ?
0202                                     indexOfLastMainLayoutInConfig + 1 :
0203                                     indexOfLastMainLayoutInConfig);
0204             } else if (action->data().toUInt() == index) {
0205                 action->setData(indexOfLastMainLayoutInXKB);
0206             } else if (index < indexOfLastMainLayoutInConfig
0207                        && index < action->data().toUInt() && action->data().toUInt() <= indexOfLastMainLayoutInConfig) {
0208                 action->setData(action->data().toUInt() - 1);
0209             } else if (indexOfLastMainLayoutInConfig < index
0210                        && indexOfLastMainLayoutInConfig < action->data().toUInt() && action->data().toUInt() < index) {
0211                 action->setData(action->data().toUInt() + 1);
0212             }
0213             // clang-format on
0214         }
0215 
0216         if (index <= indexOfLastMainLayoutInConfig) {
0217             // got to a shifted diapason due to previously selected spare layout, so adjusting the index accordingly
0218             --index;
0219         }
0220         // spare layout preempts last one in the loop
0221         layouts.append(keyboardConfig->layouts.at(index));
0222         XkbHelper::initializeKeyboardLayouts(layouts);
0223         index = indexOfLastMainLayoutInXKB;
0224     }
0225     return X11Helper::setGroup(index);
0226 }
0227 
0228 uint KeyboardDaemon::getLayout() const
0229 {
0230     return X11Helper::getGroup();
0231 }
0232 
0233 QVector<LayoutNames> KeyboardDaemon::getLayoutsList() const
0234 {
0235     QVector<LayoutNames> ret;
0236 
0237     auto layoutsList = X11Helper::getLayoutsList();
0238     if (keyboardConfig->layoutLoopCount() != KeyboardConfig::NO_LOOPING) {
0239         // extra layouts list overlaps with the main layouts loop initially by 1 position
0240         auto extraLayouts = keyboardConfig->layouts.mid(keyboardConfig->layoutLoopCount() - 1);
0241         // spare layout currently placed in the loop is removed from the extra layouts
0242         // as it was already "moved" to the last loop position
0243         extraLayouts.removeOne(layoutsList.last());
0244         layoutsList.append(extraLayouts);
0245     }
0246     for (auto &layoutUnit : std::as_const(layoutsList)) {
0247         QString displayName = layoutUnit.getDisplayName();
0248         const auto configDefaultLayouts = keyboardConfig->getDefaultLayouts();
0249         auto it = std::find(configDefaultLayouts.begin(), configDefaultLayouts.end(), layoutUnit);
0250         if (it != configDefaultLayouts.end()) {
0251             displayName = it->getDisplayName();
0252         } else {
0253             const auto configExtraLayouts = keyboardConfig->getExtraLayouts();
0254             it = std::find(configExtraLayouts.begin(), configExtraLayouts.end(), layoutUnit);
0255             if (it != configExtraLayouts.end()) {
0256                 displayName = it->getDisplayName();
0257             }
0258         }
0259         ret.append({layoutUnit.layout(), displayName, Flags::getLongText(layoutUnit, rules)});
0260     }
0261     return ret;
0262 }
0263 
0264 #include "keyboard_daemon.moc"