1 /*
2     SPDX-FileCopyrightText: 2013-2014 Weng Xuetian <wengxt@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6 
7 #include "app.h"
8 #include "gdkkeysyms_p.h"
9 #include "gtkaccelparse_p.h"
10 #include <QDBusConnection>
11 #include <QDBusServiceWatcher>
12 #include <QDebug>
13 #include <QTimer>
14 #include <QX11Info>
15 
16 #define USED_MASK (XCB_MOD_MASK_SHIFT | XCB_MOD_MASK_CONTROL | XCB_MOD_MASK_1 | XCB_MOD_MASK_4)
17 
nativeEventFilter(const QByteArray & eventType,void * message,long int * result)18 bool XcbEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long int *result)
19 {
20     Q_UNUSED(result);
21     if (eventType != "xcb_generic_event_t") {
22         return false;
23     }
24 
25     return qobject_cast<App *>(qApp)->nativeEvent(static_cast<xcb_generic_event_t *>(message));
26 }
27 
28 // callback functions from glib code
name_acquired_cb(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer self)29 static void name_acquired_cb(GDBusConnection *connection,
30                              const gchar *sender_name,
31                              const gchar *object_path,
32                              const gchar *interface_name,
33                              const gchar *signal_name,
34                              GVariant *parameters,
35                              gpointer self)
36 {
37     Q_UNUSED(connection);
38     Q_UNUSED(sender_name);
39     Q_UNUSED(object_path);
40     Q_UNUSED(interface_name);
41     Q_UNUSED(signal_name);
42     Q_UNUSED(parameters);
43     App *app = (App *)self;
44     app->nameAcquired();
45 }
46 
name_lost_cb(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer self)47 static void name_lost_cb(GDBusConnection *connection,
48                          const gchar *sender_name,
49                          const gchar *object_path,
50                          const gchar *interface_name,
51                          const gchar *signal_name,
52                          GVariant *parameters,
53                          gpointer self)
54 {
55     Q_UNUSED(connection);
56     Q_UNUSED(sender_name);
57     Q_UNUSED(object_path);
58     Q_UNUSED(interface_name);
59     Q_UNUSED(signal_name);
60     Q_UNUSED(parameters);
61     App *app = (App *)self;
62     app->nameLost();
63 }
64 
ibus_connected_cb(IBusBus * m_bus,gpointer user_data)65 static void ibus_connected_cb(IBusBus *m_bus, gpointer user_data)
66 {
67     Q_UNUSED(m_bus);
68     App *app = (App *)user_data;
69     app->init();
70 }
71 
ibus_disconnected_cb(IBusBus * m_bus,gpointer user_data)72 static void ibus_disconnected_cb(IBusBus *m_bus, gpointer user_data)
73 {
74     Q_UNUSED(m_bus);
75     App *app = (App *)user_data;
76     app->finalize();
77 }
78 
initIconMap(QMap<QByteArray,QByteArray> & iconMap)79 static void initIconMap(QMap<QByteArray, QByteArray> &iconMap)
80 {
81     iconMap["gtk-about"] = "help-about";
82     iconMap["gtk-add"] = "list-add";
83     iconMap["gtk-bold"] = "format-text-bold";
84     iconMap["gtk-cdrom"] = "media-optical";
85     iconMap["gtk-clear"] = "edit-clear";
86     iconMap["gtk-close"] = "window-close";
87     iconMap["gtk-copy"] = "edit-copy";
88     iconMap["gtk-cut"] = "edit-cut";
89     iconMap["gtk-delete"] = "edit-delete";
90     iconMap["gtk-dialog-authentication"] = "dialog-password";
91     iconMap["gtk-dialog-info"] = "dialog-information";
92     iconMap["gtk-dialog-warning"] = "dialog-warning";
93     iconMap["gtk-dialog-error"] = "dialog-error";
94     iconMap["gtk-dialog-question"] = "dialog-question";
95     iconMap["gtk-directory"] = "folder";
96     iconMap["gtk-execute"] = "system-run";
97     iconMap["gtk-file"] = "text-x-generic";
98     iconMap["gtk-find"] = "edit-find";
99     iconMap["gtk-find-and-replace"] = "edit-find-replace";
100     iconMap["gtk-floppy"] = "media-floppy";
101     iconMap["gtk-fullscreen"] = "view-fullscreen";
102     iconMap["gtk-goto-bottom"] = "go-bottom";
103     iconMap["gtk-goto-first"] = "go-first";
104     iconMap["gtk-goto-last"] = "go-last";
105     iconMap["gtk-goto-top"] = "go-top";
106     iconMap["gtk-go-back"] = "go-previous";
107     iconMap["gtk-go-down"] = "go-down";
108     iconMap["gtk-go-forward"] = "go-next";
109     iconMap["gtk-go-up"] = "go-up";
110     iconMap["gtk-harddisk"] = "drive-harddisk";
111     iconMap["gtk-help"] = "help-browser";
112     iconMap["gtk-home"] = "go-home";
113     iconMap["gtk-indent"] = "format-indent-more";
114     iconMap["gtk-info"] = "dialog-information";
115     iconMap["gtk-italic"] = "format-text-italic";
116     iconMap["gtk-jump-to"] = "go-jump";
117     iconMap["gtk-justify-center"] = "format-justify-center";
118     iconMap["gtk-justify-fill"] = "format-justify-fill";
119     iconMap["gtk-justify-left"] = "format-justify-left";
120     iconMap["gtk-justify-right"] = "format-justify-right";
121     iconMap["gtk-leave-fullscreen"] = "view-restore";
122     iconMap["gtk-missing-image"] = "image-missing";
123     iconMap["gtk-media-forward"] = "media-seek-forward";
124     iconMap["gtk-media-next"] = "media-skip-forward";
125     iconMap["gtk-media-pause"] = "media-playback-pause";
126     iconMap["gtk-media-play"] = "media-playback-start";
127     iconMap["gtk-media-previous"] = "media-skip-backward";
128     iconMap["gtk-media-record"] = "media-record";
129     iconMap["gtk-media-rewind"] = "media-seek-backward";
130     iconMap["gtk-media-stop"] = "media-playback-stop";
131     iconMap["gtk-network"] = "network-workgroup";
132     iconMap["gtk-new"] = "document-new";
133     iconMap["gtk-open"] = "document-open";
134     iconMap["gtk-page-setup"] = "document-page-setup";
135     iconMap["gtk-paste"] = "edit-paste";
136     iconMap["gtk-preferences"] = "preferences-system";
137     iconMap["gtk-print"] = "document-print";
138     iconMap["gtk-print-error"] = "printer-error";
139     iconMap["gtk-properties"] = "document-properties";
140     iconMap["gtk-quit"] = "application-exit";
141     iconMap["gtk-redo"] = "edit-redo";
142     iconMap["gtk-refresh"] = "view-refresh";
143     iconMap["gtk-remove"] = "list-remove";
144     iconMap["gtk-revert-to-saved"] = "document-revert";
145     iconMap["gtk-save"] = "document-save";
146     iconMap["gtk-save-as"] = "document-save-as";
147     iconMap["gtk-select-all"] = "edit-select-all";
148     iconMap["gtk-sort-ascending"] = "view-sort-ascending";
149     iconMap["gtk-sort-descending"] = "view-sort-descending";
150     iconMap["gtk-spell-check"] = "tools-check-spelling";
151     iconMap["gtk-stop"] = "process-stop";
152     iconMap["gtk-strikethrough"] = "format-text-strikethrough";
153     iconMap["gtk-underline"] = "format-text-underline";
154     iconMap["gtk-undo"] = "edit-undo";
155     iconMap["gtk-unindent"] = "format-indent-less";
156     iconMap["gtk-zoom-100"] = "zoom-original";
157     iconMap["gtk-zoom-fit"] = "zoom-fit-best";
158     iconMap["gtk-zoom-in"] = "zoom-in";
159     iconMap["gtk-zoom-out"] = "zoom-out";
160 }
161 
App(int & argc,char * argv[])162 App::App(int &argc, char *argv[])
163     : QGuiApplication(argc, argv)
164     , m_eventFilter(new XcbEventFilter)
165     , m_init(false)
166     , m_bus(ibus_bus_new())
167     , m_impanel(nullptr)
168     , m_keyboardGrabbed(false)
169     , m_doGrab(false)
170     , m_syms(nullptr)
171     , m_watcher(new QDBusServiceWatcher(this))
172 {
173     m_syms = xcb_key_symbols_alloc(QX11Info::connection());
174     installNativeEventFilter(m_eventFilter.data());
175 
176     initIconMap(m_iconMap);
177     m_watcher->setConnection(QDBusConnection::sessionBus());
178     m_watcher->addWatchedService(QStringLiteral("org.kde.impanel"));
179     init();
180 }
181 
getPrimaryModifier(uint state)182 uint App::getPrimaryModifier(uint state)
183 {
184     const GdkModifierType masks[] = {GDK_MOD5_MASK, GDK_MOD4_MASK, GDK_MOD3_MASK, GDK_MOD2_MASK, GDK_MOD1_MASK, GDK_CONTROL_MASK, GDK_LOCK_MASK, GDK_LOCK_MASK};
185     for (size_t i = 0; i < sizeof(masks) / sizeof(masks[0]); i++) {
186         GdkModifierType mask = masks[i];
187         if ((state & mask) == mask)
188             return mask;
189     }
190     return 0;
191 }
192 
nativeEvent(xcb_generic_event_t * event)193 bool App::nativeEvent(xcb_generic_event_t *event)
194 {
195     if ((event->response_type & ~0x80) == XCB_KEY_PRESS) {
196         auto keypress = reinterpret_cast<xcb_key_press_event_t *>(event);
197         if (keypress->event == QX11Info::appRootWindow()) {
198             auto sym = xcb_key_press_lookup_keysym(m_syms, keypress, 0);
199             uint state = keypress->state & USED_MASK;
200             bool forward;
201             if ((forward = m_triggersList.contains(qMakePair<uint, uint>(sym, state)))
202                 || m_triggersList.contains(qMakePair<uint, uint>(sym, state & (~XCB_MOD_MASK_SHIFT)))) {
203                 if (m_keyboardGrabbed) {
204                     ibus_panel_impanel_navigate(m_impanel, false, forward);
205                 } else {
206                     if (grabXKeyboard()) {
207                         ibus_panel_impanel_navigate(m_impanel, true, forward);
208                     } else {
209                         ibus_panel_impanel_move_next(m_impanel);
210                     }
211                 }
212             }
213         }
214     } else if ((event->response_type & ~0x80) == XCB_KEY_RELEASE) {
215         auto keyrelease = reinterpret_cast<xcb_key_release_event_t *>(event);
216         if (keyrelease->event == QX11Info::appRootWindow()) {
217             keyRelease(keyrelease);
218         }
219     }
220     return false;
221 }
222 
keyRelease(const xcb_key_release_event_t * event)223 void App::keyRelease(const xcb_key_release_event_t *event)
224 {
225     unsigned int mk = event->state & USED_MASK;
226     // ev.state is state before the key release, so just checking mk being 0 isn't enough
227     // using XQueryPointer() also doesn't seem to work well, so the check that all
228     // modifiers are released: only one modifier is active and the currently released
229     // key is this modifier - if yes, release the grab
230     int mod_index = -1;
231     for (int i = XCB_MAP_INDEX_SHIFT; i <= XCB_MAP_INDEX_5; ++i)
232         if ((mk & (1 << i)) != 0) {
233             if (mod_index >= 0)
234                 return;
235             mod_index = i;
236         }
237     bool release = false;
238     if (mod_index == -1)
239         release = true;
240     else {
241         auto cookie = xcb_get_modifier_mapping(QX11Info::connection());
242         auto reply = xcb_get_modifier_mapping_reply(QX11Info::connection(), cookie, nullptr);
243         if (reply) {
244             auto keycodes = xcb_get_modifier_mapping_keycodes(reply);
245             for (int i = 0; i < reply->keycodes_per_modifier; i++) {
246                 if (keycodes[reply->keycodes_per_modifier * mod_index + i] == event->detail) {
247                     release = true;
248                 }
249             }
250         }
251         free(reply);
252     }
253     if (!release) {
254         return;
255     }
256     if (m_keyboardGrabbed) {
257         accept();
258     }
259 }
260 
init()261 void App::init()
262 {
263     // only init once
264     if (m_init) {
265         return;
266     }
267     if (!ibus_bus_is_connected(m_bus)) {
268         return;
269     }
270     g_signal_connect(m_bus, "connected", G_CALLBACK(ibus_connected_cb), this);
271     g_signal_connect(m_bus, "disconnected", G_CALLBACK(ibus_disconnected_cb), this);
272     connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, &App::finalize);
273     GDBusConnection *connection = ibus_bus_get_connection(m_bus);
274     g_dbus_connection_signal_subscribe(connection,
275                                        "org.freedesktop.DBus",
276                                        "org.freedesktop.DBus",
277                                        "NameAcquired",
278                                        "/org/freedesktop/DBus",
279                                        IBUS_SERVICE_PANEL,
280                                        G_DBUS_SIGNAL_FLAGS_NONE,
281                                        name_acquired_cb,
282                                        this,
283                                        nullptr);
284 
285     g_dbus_connection_signal_subscribe(connection,
286                                        "org.freedesktop.DBus",
287                                        "org.freedesktop.DBus",
288                                        "NameLost",
289                                        "/org/freedesktop/DBus",
290                                        IBUS_SERVICE_PANEL,
291                                        G_DBUS_SIGNAL_FLAGS_NONE,
292                                        name_lost_cb,
293                                        this,
294                                        nullptr);
295 
296     ibus_bus_request_name(m_bus, IBUS_SERVICE_PANEL, IBUS_BUS_NAME_FLAG_ALLOW_REPLACEMENT | IBUS_BUS_NAME_FLAG_REPLACE_EXISTING);
297     m_init = true;
298 }
299 
nameAcquired()300 void App::nameAcquired()
301 {
302     if (m_impanel) {
303         g_object_unref(m_impanel);
304     }
305     m_impanel = ibus_panel_impanel_new(ibus_bus_get_connection(m_bus));
306     ibus_panel_impanel_set_bus(m_impanel, m_bus);
307     ibus_panel_impanel_set_app(m_impanel, this);
308 }
309 
nameLost()310 void App::nameLost()
311 {
312     setDoGrab(false);
313     if (m_impanel) {
314         g_object_unref(m_impanel);
315     }
316     m_impanel = nullptr;
317 }
318 
normalizeIconName(const QByteArray & icon) const319 QByteArray App::normalizeIconName(const QByteArray &icon) const
320 {
321     if (m_iconMap.contains(icon)) {
322         return m_iconMap[icon];
323     }
324 
325     return icon;
326 }
327 
setTriggerKeys(QList<TriggerKey> triggersList)328 void App::setTriggerKeys(QList<TriggerKey> triggersList)
329 {
330     if (m_doGrab) {
331         ungrabKey();
332     }
333     m_triggersList = triggersList;
334 
335     if (m_doGrab) {
336         grabKey();
337     }
338 }
339 
setDoGrab(bool doGrab)340 void App::setDoGrab(bool doGrab)
341 {
342     if (m_doGrab != doGrab) {
343         ;
344         if (doGrab) {
345             grabKey();
346         } else {
347             ungrabKey();
348         }
349         m_doGrab = doGrab;
350     }
351 }
352 
grabKey()353 void App::grabKey()
354 {
355     Q_FOREACH (const TriggerKey &key, m_triggersList) {
356         xcb_keysym_t sym = key.first;
357         uint modifiers = key.second;
358         xcb_keycode_t *keycode = xcb_key_symbols_get_keycode(m_syms, sym);
359         if (!keycode) {
360             g_warning("Can not convert keyval=%u to keycode!", sym);
361         } else {
362             xcb_grab_key(QX11Info::connection(), true, QX11Info::appRootWindow(), modifiers, keycode[0], XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
363             if ((modifiers & XCB_MOD_MASK_SHIFT) == 0) {
364                 xcb_grab_key(QX11Info::connection(),
365                              true,
366                              QX11Info::appRootWindow(),
367                              modifiers | XCB_MOD_MASK_SHIFT,
368                              keycode[0],
369                              XCB_GRAB_MODE_ASYNC,
370                              XCB_GRAB_MODE_ASYNC);
371             }
372         }
373         free(keycode);
374     }
375 }
376 
ungrabKey()377 void App::ungrabKey()
378 {
379     Q_FOREACH (const TriggerKey &key, m_triggersList) {
380         xcb_keysym_t sym = key.first;
381         uint modifiers = key.second;
382         xcb_keycode_t *keycode = xcb_key_symbols_get_keycode(m_syms, sym);
383         if (!keycode) {
384             g_warning("Can not convert keyval=%u to keycode!", sym);
385         } else {
386             xcb_ungrab_key(QX11Info::connection(), keycode[0], QX11Info::appRootWindow(), modifiers);
387             if ((modifiers & XCB_MOD_MASK_SHIFT) == 0) {
388                 xcb_ungrab_key(QX11Info::connection(), keycode[0], QX11Info::appRootWindow(), modifiers | XCB_MOD_MASK_SHIFT);
389             }
390         }
391         free(keycode);
392     }
393 }
394 
grabXKeyboard()395 bool App::grabXKeyboard()
396 {
397     if (m_keyboardGrabbed)
398         return false;
399     auto w = QX11Info::appRootWindow();
400     auto cookie = xcb_grab_keyboard(QX11Info::connection(), false, w, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
401     auto reply = xcb_grab_keyboard_reply(QX11Info::connection(), cookie, nullptr);
402 
403     if (reply && reply->status == XCB_GRAB_STATUS_SUCCESS) {
404         m_keyboardGrabbed = true;
405     }
406     free(reply);
407     return m_keyboardGrabbed;
408 }
409 
ungrabXKeyboard()410 void App::ungrabXKeyboard()
411 {
412     if (!m_keyboardGrabbed) {
413         // grabXKeyboard() may fail sometimes, so don't fail, but at least warn anyway
414         qDebug() << "ungrabXKeyboard() called but keyboard not grabbed!";
415     }
416     m_keyboardGrabbed = false;
417     xcb_ungrab_keyboard(QX11Info::connection(), XCB_CURRENT_TIME);
418 }
419 
accept()420 void App::accept()
421 {
422     if (m_keyboardGrabbed) {
423         ungrabXKeyboard();
424     }
425 
426     ibus_panel_impanel_accept(m_impanel);
427 }
428 
finalize()429 void App::finalize()
430 {
431     clean();
432     App::exit(0);
433 }
434 
clean()435 void App::clean()
436 {
437     if (m_impanel) {
438         g_object_unref(m_impanel);
439         m_impanel = nullptr;
440     }
441 
442     if (m_bus) {
443         g_signal_handlers_disconnect_by_func(m_bus, (gpointer)ibus_disconnected_cb, this);
444         g_signal_handlers_disconnect_by_func(m_bus, (gpointer)ibus_connected_cb, this);
445         g_object_unref(m_bus);
446         m_bus = nullptr;
447     }
448     ungrabKey();
449 }
450 
~App()451 App::~App()
452 {
453     clean();
454     if (m_syms) {
455         xcb_key_symbols_free(m_syms);
456     }
457 }
458