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"