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"