File indexing completed on 2024-05-12 05:35:43

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_init") << QStringLiteral("kcm_touchpad_init");
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             setLastUsedLayoutValue(getLayout());
0103             switchToNextLayout();
0104 
0105             LayoutUnit newLayout = X11Helper::getCurrentLayout();
0106             QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
0107                                                               QStringLiteral("/org/kde/osdService"),
0108                                                               QStringLiteral("org.kde.osdService"),
0109                                                               QStringLiteral("kbdLayoutChanged"));
0110             msg << Flags::getLongText(newLayout, rules);
0111             QDBusConnection::sessionBus().asyncCall(msg);
0112         });
0113 
0114         QAction *lastUsedLayoutAction = actionCollection->getLastUsedLayoutAction();
0115         connect(lastUsedLayoutAction, &QAction::triggered, this, [this]() {
0116             auto layoutsList = X11Helper::getLayoutsList();
0117             if (!lastUsedLayout.has_value() || layoutsList.count() <= *lastUsedLayout) {
0118                 switchToPreviousLayout();
0119             } else {
0120                 setLayout(*lastUsedLayout);
0121             }
0122 
0123             LayoutUnit newLayout = X11Helper::getCurrentLayout();
0124             QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
0125                                                               QStringLiteral("/org/kde/osdService"),
0126                                                               QStringLiteral("org.kde.osdService"),
0127                                                               QStringLiteral("kbdLayoutChanged"));
0128             msg << Flags::getLongText(newLayout, rules);
0129             QDBusConnection::sessionBus().asyncCall(msg);
0130         });
0131 
0132         actionCollection->loadLayoutShortcuts(keyboardConfig->layouts, rules);
0133         // clang-format off
0134         connect(actionCollection, SIGNAL(actionTriggered(QAction*)), this, SLOT(setLayout(QAction*)));
0135         // clang-format on
0136     }
0137 }
0138 
0139 void KeyboardDaemon::unregisterShortcut()
0140 {
0141     // register KDE keyboard shortcut for switching layouts
0142     if (actionCollection != nullptr) {
0143         // clang-format off
0144         disconnect(actionCollection, SIGNAL(actionTriggered(QAction*)), this, SLOT(setLayout(QAction*)));
0145         // clang-format on
0146         disconnect(actionCollection->getToggleAction(), &QAction::triggered, this, &KeyboardDaemon::switchToNextLayout);
0147 
0148         delete actionCollection;
0149         actionCollection = nullptr;
0150     }
0151 }
0152 
0153 void KeyboardDaemon::registerListeners()
0154 {
0155     if (xEventNotifier == nullptr) {
0156         xEventNotifier = new XInputEventNotifier();
0157     }
0158     connect(xEventNotifier, &XInputEventNotifier::newPointerDevice, this, &KeyboardDaemon::configureInput);
0159     connect(xEventNotifier, &XInputEventNotifier::newKeyboardDevice, this, &KeyboardDaemon::configureKeyboard);
0160     connect(xEventNotifier, &XEventNotifier::layoutMapChanged, this, &KeyboardDaemon::layoutMapChanged);
0161     connect(xEventNotifier, &XEventNotifier::layoutChanged, this, &KeyboardDaemon::layoutChangedSlot);
0162     xEventNotifier->start();
0163 }
0164 
0165 void KeyboardDaemon::unregisterListeners()
0166 {
0167     if (xEventNotifier != nullptr) {
0168         xEventNotifier->stop();
0169         disconnect(xEventNotifier, &XInputEventNotifier::newPointerDevice, this, &KeyboardDaemon::configureInput);
0170         disconnect(xEventNotifier, &XInputEventNotifier::newKeyboardDevice, this, &KeyboardDaemon::configureKeyboard);
0171         disconnect(xEventNotifier, &XEventNotifier::layoutChanged, this, &KeyboardDaemon::layoutChangedSlot);
0172         disconnect(xEventNotifier, &XEventNotifier::layoutMapChanged, this, &KeyboardDaemon::layoutMapChanged);
0173     }
0174 }
0175 
0176 void KeyboardDaemon::layoutChangedSlot()
0177 {
0178     layoutMemory.layoutChanged();
0179 
0180     Q_EMIT layoutChanged(getLayout());
0181 }
0182 
0183 void KeyboardDaemon::layoutMapChanged()
0184 {
0185     keyboardConfig->load();
0186     layoutMemory.layoutMapChanged();
0187     Q_EMIT layoutListChanged();
0188 }
0189 
0190 void KeyboardDaemon::switchToNextLayout()
0191 {
0192     setLastUsedLayoutValue(getLayout());
0193     X11Helper::scrollLayouts(1);
0194 }
0195 
0196 void KeyboardDaemon::switchToPreviousLayout()
0197 {
0198     setLastUsedLayoutValue(getLayout());
0199     X11Helper::scrollLayouts(-1);
0200 }
0201 
0202 bool KeyboardDaemon::setLayout(QAction *action)
0203 {
0204     if (action == actionCollection->getToggleAction())
0205         return false;
0206 
0207     if (action == actionCollection->getLastUsedLayoutAction())
0208         return false;
0209 
0210     return setLayout(action->data().toUInt());
0211 }
0212 
0213 bool KeyboardDaemon::setLayout(uint index)
0214 {
0215     if (keyboardConfig->layoutLoopCount() != KeyboardConfig::NO_LOOPING && index >= uint(keyboardConfig->layoutLoopCount())) {
0216         QList<LayoutUnit> layouts = X11Helper::getLayoutsList();
0217         const uint indexOfLastMainLayoutInConfig = keyboardConfig->layouts.lastIndexOf(layouts.takeLast());
0218         const uint indexOfLastMainLayoutInXKB = layouts.size();
0219 
0220         // Re-calculate indexes for layout switching Actions
0221         const auto &actions = actionCollection->actions();
0222         for (const auto &action : actions) {
0223             // clang-format off
0224             if (action->data().toUInt() == indexOfLastMainLayoutInXKB) {
0225                 action->setData(indexOfLastMainLayoutInConfig < index ?
0226                                     indexOfLastMainLayoutInConfig + 1 :
0227                                     indexOfLastMainLayoutInConfig);
0228             } else if (action->data().toUInt() == index) {
0229                 action->setData(indexOfLastMainLayoutInXKB);
0230             } else if (index < indexOfLastMainLayoutInConfig
0231                        && index < action->data().toUInt() && action->data().toUInt() <= indexOfLastMainLayoutInConfig) {
0232                 action->setData(action->data().toUInt() - 1);
0233             } else if (indexOfLastMainLayoutInConfig < index
0234                        && indexOfLastMainLayoutInConfig < action->data().toUInt() && action->data().toUInt() < index) {
0235                 action->setData(action->data().toUInt() + 1);
0236             }
0237             // clang-format on
0238         }
0239 
0240         if (index <= indexOfLastMainLayoutInConfig) {
0241             // got to a shifted diapason due to previously selected spare layout, so adjusting the index accordingly
0242             --index;
0243         }
0244         // spare layout preempts last one in the loop
0245         layouts.append(keyboardConfig->layouts.at(index));
0246         XkbHelper::initializeKeyboardLayouts(layouts);
0247         index = indexOfLastMainLayoutInXKB;
0248     }
0249     setLastUsedLayoutValue(getLayout());
0250     return X11Helper::setGroup(index);
0251 }
0252 
0253 uint KeyboardDaemon::getLayout() const
0254 {
0255     return X11Helper::getGroup();
0256 }
0257 
0258 QList<LayoutNames> KeyboardDaemon::getLayoutsList() const
0259 {
0260     QList<LayoutNames> ret;
0261 
0262     auto layoutsList = X11Helper::getLayoutsList();
0263     if (keyboardConfig->layoutLoopCount() != KeyboardConfig::NO_LOOPING) {
0264         // extra layouts list overlaps with the main layouts loop initially by 1 position
0265         auto extraLayouts = keyboardConfig->layouts.mid(keyboardConfig->layoutLoopCount() - 1);
0266         // spare layout currently placed in the loop is removed from the extra layouts
0267         // as it was already "moved" to the last loop position
0268         extraLayouts.removeOne(layoutsList.last());
0269         layoutsList.append(extraLayouts);
0270     }
0271     for (auto &layoutUnit : std::as_const(layoutsList)) {
0272         QString displayName = layoutUnit.getDisplayName();
0273         const auto configDefaultLayouts = keyboardConfig->getDefaultLayouts();
0274         auto it = std::find(configDefaultLayouts.begin(), configDefaultLayouts.end(), layoutUnit);
0275         if (it != configDefaultLayouts.end()) {
0276             displayName = it->getDisplayName();
0277         } else {
0278             const auto configExtraLayouts = keyboardConfig->getExtraLayouts();
0279             it = std::find(configExtraLayouts.begin(), configExtraLayouts.end(), layoutUnit);
0280             if (it != configExtraLayouts.end()) {
0281                 displayName = it->getDisplayName();
0282             }
0283         }
0284         ret.append({layoutUnit.layout(), displayName, Flags::getLongText(layoutUnit, rules)});
0285     }
0286     return ret;
0287 }
0288 
0289 void KeyboardDaemon::setLastUsedLayoutValue(uint newValue)
0290 {
0291     auto layoutsList = X11Helper::getLayoutsList();
0292     if (layoutsList.count() > 1) {
0293         lastUsedLayout = std::optional<uint>{newValue};
0294     }
0295 }
0296 
0297 #include "keyboard_daemon.moc"