Changeset View
Changeset View
Standalone View
Standalone View
kcms/keyboard/daemon/keyboard_daemon.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright (C) 2010 Andriy Rysin (rysin@kde.org) | ||||
3 | * | ||||
4 | * This program is free software; you can redistribute it and/or modify | ||||
5 | * it under the terms of the GNU General Public License as published by | ||||
6 | * the Free Software Foundation; either version 2 of the License, or | ||||
7 | * (at your option) any later version. | ||||
8 | * | ||||
9 | * This program is distributed in the hope that it will be useful, | ||||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
12 | * GNU General Public License for more details. | ||||
13 | * | ||||
14 | * You should have received a copy of the GNU General Public License | ||||
15 | * along with this program; if not, write to the Free Software | ||||
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
17 | */ | ||||
18 | | ||||
19 | #include "keyboard_daemon.h" | ||||
20 | #include "debug.h" | ||||
21 | | ||||
22 | #include <KGlobalAccel> | ||||
23 | #include <QAction> | ||||
24 | #include <QDBusConnection> | ||||
25 | #include <QDBusMessage> | ||||
26 | #include <QDBusPendingCall> | ||||
27 | #include <QProcess> | ||||
28 | #include <QTimer> | ||||
29 | #include <QX11Info> | ||||
30 | | ||||
31 | #include <KConfigGroup> | ||||
32 | #include <KPluginFactory> | ||||
33 | #include <KSharedConfig> | ||||
34 | | ||||
35 | #include "input_sources.h" | ||||
36 | #include "../keyboard_dbus.h" | ||||
37 | #include "../xkb_rules.h" | ||||
38 | #include "bindings.h" | ||||
39 | #include "keyboard_hardware.h" | ||||
40 | #include "x11_helper.h" | ||||
41 | #include "xinput_helper.h" | ||||
42 | #include "xkb_helper.h" | ||||
43 | | ||||
44 | // TODO: implement switching policy | ||||
45 | | ||||
46 | K_PLUGIN_FACTORY_WITH_JSON(KeyboardFactory, | ||||
47 | "keyboard.json", | ||||
48 | registerPlugin<KeyboardDaemon>();) | ||||
49 | | ||||
50 | KeyboardDaemon::KeyboardDaemon(QObject* parent, const QList<QVariant>&) | ||||
51 | : KDEDModule(parent) | ||||
52 | , m_layoutListModels(new LayoutListModels(this)) | ||||
53 | , m_currentLayoutIndex(0) | ||||
54 | , actionCollection(nullptr) | ||||
55 | , xEventNotifier(nullptr) | ||||
56 | , m_fcitxDBusProxy("org.fcitx.Fcitx", "/inputmethod", QDBusConnection::sessionBus()) | ||||
57 | { | ||||
58 | if (!X11Helper::xkbSupported(nullptr)) | ||||
59 | return; //TODO: shut down the daemon? | ||||
60 | | ||||
61 | FcitxQtInputMethodItem::registerMetaType(); | ||||
62 | | ||||
63 | // Register DBus Service | ||||
64 | QDBusConnection dbus = QDBusConnection::sessionBus(); | ||||
65 | dbus.registerService(KEYBOARD_DBUS_SERVICE_NAME); | ||||
66 | dbus.registerObject(KEYBOARD_DBUS_OBJECT_PATH, this, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableSignals); | ||||
67 | dbus.connect(QString(), KEYBOARD_DBUS_OBJECT_PATH, KEYBOARD_DBUS_SERVICE_NAME, KEYBOARD_DBUS_CONFIG_RELOAD_MESSAGE, this, SLOT(configureKeyboard())); | ||||
68 | dbus.connect(QString(), KEYBOARD_DBUS_OBJECT_PATH, KEYBOARD_DBUS_SERVICE_NAME, KEYBOARD_DBUS_CONFIG_RELOAD_MESSAGE, this, SIGNAL(configChanged())); | ||||
69 | | ||||
70 | // Setup timers for applying multiple events at once | ||||
71 | configureKeyboardTimer.setSingleShot(true); | ||||
72 | configureKeyboardTimer.setInterval(50); | ||||
73 | connect(&configureKeyboardTimer, &QTimer::timeout, this, &KeyboardDaemon::configureKeyboard); | ||||
74 | configureMouseTimer.setSingleShot(true); | ||||
75 | configureMouseTimer.setInterval(50); | ||||
76 | connect(&configureMouseTimer, &QTimer::timeout, this, &KeyboardDaemon::configureMouse); | ||||
77 | | ||||
78 | configureKeyboard(); | ||||
79 | registerListeners(); | ||||
80 | connect(InputSources::self(), &InputSources::currentSourceChanged, this, &KeyboardDaemon::registerListeners); | ||||
81 | connect(InputSources::self(), &InputSources::currentSourceChanged, [this]() { configureMouseTimer.start(); }); | ||||
82 | connect(InputSources::self(), &InputSources::currentSourceChanged, [this]() { configureKeyboardTimer.start(); }); | ||||
83 | } | ||||
84 | | ||||
85 | KeyboardDaemon::~KeyboardDaemon() | ||||
86 | { | ||||
87 | QDBusConnection dbus = QDBusConnection::sessionBus(); | ||||
88 | dbus.disconnect(QString(), KEYBOARD_DBUS_OBJECT_PATH, KEYBOARD_DBUS_SERVICE_NAME, KEYBOARD_DBUS_CONFIG_RELOAD_MESSAGE, this, SLOT(configureKeyboard())); | ||||
89 | dbus.unregisterObject(KEYBOARD_DBUS_OBJECT_PATH); | ||||
90 | dbus.unregisterService(KEYBOARD_DBUS_SERVICE_NAME); | ||||
91 | | ||||
92 | unregisterListeners(); | ||||
93 | unregisterShortcut(); | ||||
94 | | ||||
95 | delete xEventNotifier; | ||||
96 | } | ||||
97 | | ||||
98 | QString KeyboardDaemon::updateCurrentLayout(int newLayoutIndex) | ||||
99 | { | ||||
100 | m_currentLayoutIndex = newLayoutIndex; | ||||
101 | | ||||
102 | QString newLayoutSaveName = m_layoutListModels->currentLayoutListModel()->data( | ||||
103 | m_layoutListModels->currentLayoutListModel()->index(m_currentLayoutIndex, 0), | ||||
104 | LayoutListModelBase::Roles::SaveNameRole).toString(); | ||||
105 | QString newLayoutName = m_layoutListModels->currentLayoutListModel()->data( | ||||
106 | m_layoutListModels->currentLayoutListModel()->index(m_currentLayoutIndex, 0), | ||||
107 | LayoutListModelBase::Roles::NameRole).toString(); | ||||
108 | QString newLayoutDesc = m_layoutListModels->currentLayoutListModel()->data( | ||||
109 | m_layoutListModels->currentLayoutListModel()->index(m_currentLayoutIndex, 0), | ||||
110 | LayoutListModelBase::Roles::DescriptionRole).toString(); | ||||
111 | | ||||
112 | if (InputSources::self()->currentSource() == InputSources::Sources::FcitxSource) { | ||||
113 | FcitxQtInputMethodItemList list = m_fcitxDBusProxy.iMList(); | ||||
114 | bool isLatinModeEnabled = m_layoutListModels->currentLayoutListModel()->data( | ||||
115 | m_layoutListModels->currentLayoutListModel()->index(m_currentLayoutIndex, 0), | ||||
116 | LayoutListModelBase::Roles::IsLatinModeEnabledRole).toBool(); | ||||
117 | QString latinModeLayout = m_layoutListModels->currentLayoutListModel()->data( | ||||
118 | m_layoutListModels->currentLayoutListModel()->index(m_currentLayoutIndex, 0), | ||||
119 | LayoutListModelBase::Roles::LatinModeLayoutRole).toString(); | ||||
120 | | ||||
121 | qDebug(KCM_KEYBOARD) << newLayoutName << isLatinModeEnabled << latinModeLayout; | ||||
122 | | ||||
123 | for (int i = 0; i < list.size(); ++i) { | ||||
124 | list[i].setEnabled(false); | ||||
125 | if (list[i].uniqueName() == newLayoutName) { | ||||
126 | list[i].setEnabled(true); | ||||
127 | if (isLatinModeEnabled) { | ||||
128 | list.move(i, 1); | ||||
129 | } else { | ||||
130 | list.move(i, 0); | ||||
131 | } | ||||
132 | } | ||||
133 | if (list[i].uniqueName() == latinModeLayout) { | ||||
134 | list[i].setEnabled(true); | ||||
135 | list.move(i, 0); | ||||
136 | } | ||||
137 | } | ||||
138 | | ||||
139 | m_fcitxDBusProxy.setIMList(list); | ||||
140 | } | ||||
141 | else if (InputSources::self()->currentSource() == InputSources::Sources::XkbSource) { | ||||
142 | // Set xkb layouts and variants | ||||
143 | QStringList setxkbmapCommandArguments; | ||||
144 | QString layout; | ||||
145 | QString variant = ""; | ||||
146 | | ||||
147 | int source = m_layoutListModels->currentLayoutListModel()->data( | ||||
148 | m_layoutListModels->currentLayoutListModel()->index(m_currentLayoutIndex, 0), | ||||
149 | LayoutListModelBase::Roles::SourceRole).toInt(); | ||||
150 | if (source == InputSources::Sources::XkbSource) { | ||||
151 | QStringList lv = newLayoutName.split('('); | ||||
152 | layout = lv[0]; | ||||
153 | if (lv.size() >= 2) { | ||||
154 | variant = lv[1].left(lv[1].size() - 1); | ||||
155 | } | ||||
156 | } | ||||
157 | | ||||
158 | setxkbmapCommandArguments.append(QStringLiteral("-layout")); | ||||
159 | setxkbmapCommandArguments.append(layout); | ||||
160 | if (!variant.isEmpty()) { | ||||
161 | setxkbmapCommandArguments.append(QStringLiteral("-variant")); | ||||
162 | setxkbmapCommandArguments.append(variant); | ||||
163 | } | ||||
164 | XkbHelper::runConfigLayoutCommand(setxkbmapCommandArguments); | ||||
165 | } | ||||
166 | | ||||
167 | qDebug() << "currentLayoutChanged emitted"; | ||||
168 | emit currentLayoutChanged(newLayoutSaveName); | ||||
169 | | ||||
170 | return newLayoutDesc; | ||||
171 | } | ||||
172 | | ||||
173 | void KeyboardDaemon::configureKeyboard() | ||||
174 | { | ||||
175 | qCDebug(KCM_KEYBOARD) << "Configuring keyboard"; | ||||
176 | | ||||
177 | KConfigGroup config( | ||||
178 | KSharedConfig::openConfig(QStringLiteral("kxkbrc"), KConfig::NoGlobals), | ||||
179 | QStringLiteral("Layout")); | ||||
180 | | ||||
181 | m_layoutListModels->loadConfig(); | ||||
182 | init_keyboard_hardware(); | ||||
183 | | ||||
184 | // Reset options | ||||
185 | XkbHelper::runConfigLayoutCommand(QStringList() << "-option"); | ||||
186 | | ||||
187 | // Set keyboard model | ||||
188 | QStringList setxkbmapCommandArguments; | ||||
189 | QString keyboardModel = config.readEntry<QString>("Model", ""); | ||||
190 | if (!keyboardModel.isEmpty()) { | ||||
191 | setxkbmapCommandArguments.append(QStringLiteral("-model")); | ||||
192 | setxkbmapCommandArguments.append(keyboardModel); | ||||
193 | } | ||||
194 | | ||||
195 | // Set advanced options | ||||
196 | QString options = config.readEntry<QString>("Options", ""); | ||||
197 | setxkbmapCommandArguments.append(QStringLiteral("-option")); | ||||
198 | setxkbmapCommandArguments.append(options); | ||||
199 | | ||||
200 | XkbHelper::runConfigLayoutCommand(setxkbmapCommandArguments); | ||||
201 | | ||||
202 | // Reset index | ||||
203 | updateCurrentLayout(0); | ||||
204 | | ||||
205 | // Re-register shortcut keys | ||||
206 | unregisterShortcut(); | ||||
207 | registerShortcut(); | ||||
208 | } | ||||
209 | | ||||
210 | void KeyboardDaemon::configureMouse() | ||||
211 | { | ||||
212 | QStringList modules; | ||||
213 | modules << QStringLiteral("mouse"); | ||||
214 | QProcess::startDetached(QStringLiteral("kcminit"), modules); | ||||
215 | } | ||||
216 | | ||||
217 | void KeyboardDaemon::registerShortcut() | ||||
218 | { | ||||
219 | if (actionCollection == nullptr) { | ||||
220 | actionCollection = new KeyboardLayoutActionCollection(this, false); | ||||
221 | | ||||
222 | QAction* toggleLayoutAction = actionCollection->getToggleAction(); | ||||
223 | connect(toggleLayoutAction, &QAction::triggered, this, &KeyboardDaemon::switchToNextLayout); | ||||
224 | qCDebug(KCM_KEYBOARD) << KGlobalAccel::self()->shortcut(toggleLayoutAction); | ||||
225 | //actionCollection->loadLayoutShortcuts(keyboardConfig.layouts, rules.data()); | ||||
226 | //connect(actionCollection, SIGNAL(actionTriggered(QAction*)), this, SLOT(setLayout(QAction*))); | ||||
227 | } | ||||
228 | } | ||||
229 | | ||||
230 | void KeyboardDaemon::unregisterShortcut() | ||||
231 | { | ||||
232 | // register KDE keyboard shortcut for switching layouts | ||||
233 | if (actionCollection != nullptr) { | ||||
234 | disconnect(actionCollection, SIGNAL(actionTriggered(QAction*)), this, SLOT(setLayout(QAction*))); | ||||
235 | disconnect(actionCollection->getToggleAction(), &QAction::triggered, this, &KeyboardDaemon::switchToNextLayout); | ||||
236 | | ||||
237 | delete actionCollection; | ||||
238 | actionCollection = nullptr; | ||||
239 | } | ||||
240 | } | ||||
241 | | ||||
242 | void KeyboardDaemon::registerListeners() | ||||
243 | { | ||||
244 | if (InputSources::self()->currentSource() == InputSources::Sources::XkbSource) { | ||||
245 | if (!isXEventRegistered) { | ||||
246 | if (xEventNotifier == nullptr) { | ||||
247 | xEventNotifier = new XInputEventNotifier(); | ||||
248 | } | ||||
249 | connect(xEventNotifier, &XInputEventNotifier::newPointerDevice, [this]() { configureMouseTimer.start(); }); | ||||
250 | connect(xEventNotifier, &XInputEventNotifier::newKeyboardDevice, [this]() { configureKeyboardTimer.start(); }); | ||||
251 | connect(xEventNotifier, &XEventNotifier::layoutMapChanged, this, &KeyboardDaemon::layoutMapChanged); | ||||
252 | connect(xEventNotifier, &XEventNotifier::layoutChanged, this, &KeyboardDaemon::layoutChanged); | ||||
253 | xEventNotifier->start(); | ||||
254 | isXEventRegistered = true; | ||||
255 | } | ||||
256 | } | ||||
257 | else { | ||||
258 | unregisterListeners(); | ||||
259 | } | ||||
260 | } | ||||
261 | | ||||
262 | void KeyboardDaemon::unregisterListeners() | ||||
263 | { | ||||
264 | if (xEventNotifier != nullptr && isXEventRegistered) { | ||||
265 | xEventNotifier->stop(); | ||||
266 | disconnect(xEventNotifier, &XEventNotifier::layoutChanged, this, &KeyboardDaemon::layoutChanged); | ||||
267 | disconnect(xEventNotifier, &XEventNotifier::layoutMapChanged, this, &KeyboardDaemon::layoutMapChanged); | ||||
268 | } | ||||
269 | } | ||||
270 | | ||||
271 | void KeyboardDaemon::layoutChanged() | ||||
272 | { | ||||
273 | // TODO | ||||
274 | } | ||||
275 | | ||||
276 | void KeyboardDaemon::layoutMapChanged() | ||||
277 | { | ||||
278 | //configureKeyboard(); | ||||
279 | } | ||||
280 | | ||||
281 | void KeyboardDaemon::switchToNextLayout() | ||||
282 | { | ||||
283 | const int origIdx = m_currentLayoutIndex; | ||||
284 | const int layoutCount = m_layoutListModels->currentLayoutListModel()->rowCount(); | ||||
285 | if (layoutCount <= 1) { | ||||
286 | qWarning(KCM_KEYBOARD) << "Cannot find next layout"; | ||||
287 | return; | ||||
288 | } | ||||
289 | int newIdx = (origIdx + 1) % layoutCount; | ||||
290 | | ||||
291 | QString layoutName = updateCurrentLayout(newIdx); | ||||
292 | | ||||
293 | qCDebug(KCM_KEYBOARD) << "Toggling layout " << layoutName; | ||||
294 | | ||||
295 | // Show a box on the screen indicating the layout change | ||||
296 | QDBusMessage msg = QDBusMessage::createMethodCall( | ||||
297 | QStringLiteral("org.kde.plasmashell"), | ||||
298 | QStringLiteral("/org/kde/osdService"), | ||||
299 | QStringLiteral("org.kde.osdService"), | ||||
300 | QStringLiteral("kbdLayoutChanged")); | ||||
301 | | ||||
302 | msg << layoutName; | ||||
303 | | ||||
304 | QDBusConnection::sessionBus().asyncCall(msg); | ||||
305 | } | ||||
306 | | ||||
307 | bool KeyboardDaemon::setLayout(QAction* action) | ||||
308 | { | ||||
309 | if (action == actionCollection->getToggleAction()) | ||||
310 | return false; | ||||
311 | | ||||
312 | QString layoutName = action->data().toString(); | ||||
313 | return setLayout(layoutName); | ||||
314 | } | ||||
315 | | ||||
316 | bool KeyboardDaemon::setLayout(const QString& layout) | ||||
317 | { | ||||
318 | qDebug() << layout; | ||||
319 | const int layoutCount = m_layoutListModels->currentLayoutListModel()->rowCount(); | ||||
320 | for (int i = 0; i < layoutCount; ++i) { | ||||
321 | QString saveName = m_layoutListModels->currentLayoutListModel()->data( | ||||
322 | m_layoutListModels->currentLayoutListModel()->index(i, 0), | ||||
323 | LayoutListModelBase::Roles::SaveNameRole).toString(); | ||||
324 | | ||||
325 | if (saveName == layout) { | ||||
326 | updateCurrentLayout(i); | ||||
327 | return true; | ||||
328 | } | ||||
329 | } | ||||
330 | return false; | ||||
331 | } | ||||
332 | | ||||
333 | QString KeyboardDaemon::getCurrentLayout() | ||||
334 | { | ||||
335 | return m_layoutListModels->currentLayoutListModel()->data( | ||||
336 | m_layoutListModels->currentLayoutListModel()->index(m_currentLayoutIndex, 0), | ||||
337 | LayoutListModelBase::Roles::SaveNameRole).toString(); | ||||
338 | } | ||||
339 | | ||||
340 | QStringList KeyboardDaemon::getLayoutsList() | ||||
341 | { | ||||
342 | QStringList list; | ||||
343 | | ||||
344 | for (int i = 0; i < m_layoutListModels->currentLayoutListModel()->rowCount(); ++i) { | ||||
345 | QString layout = m_layoutListModels->currentLayoutListModel()->data( | ||||
346 | m_layoutListModels->currentLayoutListModel()->index(i, 0), | ||||
347 | LayoutListModelBase::Roles::SaveNameRole).toString(); | ||||
348 | list << layout; | ||||
349 | } | ||||
350 | return list; | ||||
351 | } | ||||
352 | | ||||
353 | QString KeyboardDaemon::getLayoutDisplayName(const QString &layout) | ||||
354 | { | ||||
355 | const int layoutCount = m_layoutListModels->currentLayoutListModel()->rowCount(); | ||||
356 | for (int i = 0; i < layoutCount; ++i) { | ||||
357 | QString saveName = m_layoutListModels->currentLayoutListModel()->data( | ||||
358 | m_layoutListModels->currentLayoutListModel()->index(i, 0), | ||||
359 | LayoutListModelBase::Roles::SaveNameRole).toString(); | ||||
360 | | ||||
361 | if (saveName == layout) { | ||||
362 | return m_layoutListModels->currentLayoutListModel()->data( | ||||
363 | m_layoutListModels->currentLayoutListModel()->index(i, 0), | ||||
364 | LayoutListModelBase::Roles::DescriptionRole).toString(); | ||||
365 | } | ||||
366 | } | ||||
367 | return ""; | ||||
368 | } | ||||
369 | | ||||
370 | #include "keyboard_daemon.moc" |