File indexing completed on 2024-11-24 05:00:21

0001 /*
0002     SPDX-FileCopyrightText: 2013-2014 Weng Xuetian <wengxt@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "app.h"
0008 #include "gdkkeysyms_p.h"
0009 #include "gtkaccelparse_p.h"
0010 #include <QDBusConnection>
0011 #include <QDBusServiceWatcher>
0012 #include <QDebug>
0013 #include <QTimer>
0014 #include <private/qtx11extras_p.h>
0015 
0016 #define USED_MASK (XCB_MOD_MASK_SHIFT | XCB_MOD_MASK_CONTROL | XCB_MOD_MASK_1 | XCB_MOD_MASK_4)
0017 
0018 bool XcbEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
0019 {
0020     Q_UNUSED(result);
0021     if (eventType != "xcb_generic_event_t") {
0022         return false;
0023     }
0024 
0025     return qobject_cast<App *>(qApp)->nativeEvent(static_cast<xcb_generic_event_t *>(message));
0026 }
0027 
0028 // callback functions from glib code
0029 static void name_acquired_cb(GDBusConnection *connection,
0030                              const gchar *sender_name,
0031                              const gchar *object_path,
0032                              const gchar *interface_name,
0033                              const gchar *signal_name,
0034                              GVariant *parameters,
0035                              gpointer self)
0036 {
0037     Q_UNUSED(connection);
0038     Q_UNUSED(sender_name);
0039     Q_UNUSED(object_path);
0040     Q_UNUSED(interface_name);
0041     Q_UNUSED(signal_name);
0042     Q_UNUSED(parameters);
0043     App *app = (App *)self;
0044     app->nameAcquired();
0045 }
0046 
0047 static void name_lost_cb(GDBusConnection *connection,
0048                          const gchar *sender_name,
0049                          const gchar *object_path,
0050                          const gchar *interface_name,
0051                          const gchar *signal_name,
0052                          GVariant *parameters,
0053                          gpointer self)
0054 {
0055     Q_UNUSED(connection);
0056     Q_UNUSED(sender_name);
0057     Q_UNUSED(object_path);
0058     Q_UNUSED(interface_name);
0059     Q_UNUSED(signal_name);
0060     Q_UNUSED(parameters);
0061     App *app = (App *)self;
0062     app->nameLost();
0063 }
0064 
0065 static void ibus_connected_cb(IBusBus *m_bus, gpointer user_data)
0066 {
0067     Q_UNUSED(m_bus);
0068     App *app = (App *)user_data;
0069     app->init();
0070 }
0071 
0072 static void ibus_disconnected_cb(IBusBus *m_bus, gpointer user_data)
0073 {
0074     Q_UNUSED(m_bus);
0075     App *app = (App *)user_data;
0076     app->finalize();
0077 }
0078 
0079 static void initIconMap(QMap<QByteArray, QByteArray> &iconMap)
0080 {
0081     iconMap["gtk-about"] = "help-about";
0082     iconMap["gtk-add"] = "list-add";
0083     iconMap["gtk-bold"] = "format-text-bold";
0084     iconMap["gtk-cdrom"] = "media-optical";
0085     iconMap["gtk-clear"] = "edit-clear";
0086     iconMap["gtk-close"] = "window-close";
0087     iconMap["gtk-copy"] = "edit-copy";
0088     iconMap["gtk-cut"] = "edit-cut";
0089     iconMap["gtk-delete"] = "edit-delete";
0090     iconMap["gtk-dialog-authentication"] = "dialog-password";
0091     iconMap["gtk-dialog-info"] = "dialog-information";
0092     iconMap["gtk-dialog-warning"] = "dialog-warning";
0093     iconMap["gtk-dialog-error"] = "dialog-error";
0094     iconMap["gtk-dialog-question"] = "dialog-question";
0095     iconMap["gtk-directory"] = "folder";
0096     iconMap["gtk-execute"] = "system-run";
0097     iconMap["gtk-file"] = "text-x-generic";
0098     iconMap["gtk-find"] = "edit-find";
0099     iconMap["gtk-find-and-replace"] = "edit-find-replace";
0100     iconMap["gtk-floppy"] = "media-floppy";
0101     iconMap["gtk-fullscreen"] = "view-fullscreen";
0102     iconMap["gtk-goto-bottom"] = "go-bottom";
0103     iconMap["gtk-goto-first"] = "go-first";
0104     iconMap["gtk-goto-last"] = "go-last";
0105     iconMap["gtk-goto-top"] = "go-top";
0106     iconMap["gtk-go-back"] = "go-previous";
0107     iconMap["gtk-go-down"] = "go-down";
0108     iconMap["gtk-go-forward"] = "go-next";
0109     iconMap["gtk-go-up"] = "go-up";
0110     iconMap["gtk-harddisk"] = "drive-harddisk";
0111     iconMap["gtk-help"] = "help-browser";
0112     iconMap["gtk-home"] = "go-home";
0113     iconMap["gtk-indent"] = "format-indent-more";
0114     iconMap["gtk-info"] = "dialog-information";
0115     iconMap["gtk-italic"] = "format-text-italic";
0116     iconMap["gtk-jump-to"] = "go-jump";
0117     iconMap["gtk-justify-center"] = "format-justify-center";
0118     iconMap["gtk-justify-fill"] = "format-justify-fill";
0119     iconMap["gtk-justify-left"] = "format-justify-left";
0120     iconMap["gtk-justify-right"] = "format-justify-right";
0121     iconMap["gtk-leave-fullscreen"] = "view-restore";
0122     iconMap["gtk-missing-image"] = "image-missing";
0123     iconMap["gtk-media-forward"] = "media-seek-forward";
0124     iconMap["gtk-media-next"] = "media-skip-forward";
0125     iconMap["gtk-media-pause"] = "media-playback-pause";
0126     iconMap["gtk-media-play"] = "media-playback-start";
0127     iconMap["gtk-media-previous"] = "media-skip-backward";
0128     iconMap["gtk-media-record"] = "media-record";
0129     iconMap["gtk-media-rewind"] = "media-seek-backward";
0130     iconMap["gtk-media-stop"] = "media-playback-stop";
0131     iconMap["gtk-network"] = "network-workgroup";
0132     iconMap["gtk-new"] = "document-new";
0133     iconMap["gtk-open"] = "document-open";
0134     iconMap["gtk-page-setup"] = "document-page-setup";
0135     iconMap["gtk-paste"] = "edit-paste";
0136     iconMap["gtk-preferences"] = "preferences-system";
0137     iconMap["gtk-print"] = "document-print";
0138     iconMap["gtk-print-error"] = "printer-error";
0139     iconMap["gtk-properties"] = "document-properties";
0140     iconMap["gtk-quit"] = "application-exit";
0141     iconMap["gtk-redo"] = "edit-redo";
0142     iconMap["gtk-refresh"] = "view-refresh";
0143     iconMap["gtk-remove"] = "list-remove";
0144     iconMap["gtk-revert-to-saved"] = "document-revert";
0145     iconMap["gtk-save"] = "document-save";
0146     iconMap["gtk-save-as"] = "document-save-as";
0147     iconMap["gtk-select-all"] = "edit-select-all";
0148     iconMap["gtk-sort-ascending"] = "view-sort-ascending";
0149     iconMap["gtk-sort-descending"] = "view-sort-descending";
0150     iconMap["gtk-spell-check"] = "tools-check-spelling";
0151     iconMap["gtk-stop"] = "process-stop";
0152     iconMap["gtk-strikethrough"] = "format-text-strikethrough";
0153     iconMap["gtk-underline"] = "format-text-underline";
0154     iconMap["gtk-undo"] = "edit-undo";
0155     iconMap["gtk-unindent"] = "format-indent-less";
0156     iconMap["gtk-zoom-100"] = "zoom-original";
0157     iconMap["gtk-zoom-fit"] = "zoom-fit-best";
0158     iconMap["gtk-zoom-in"] = "zoom-in";
0159     iconMap["gtk-zoom-out"] = "zoom-out";
0160 }
0161 
0162 App::App(int &argc, char *argv[])
0163     : QGuiApplication(argc, argv)
0164     , m_eventFilter(new XcbEventFilter)
0165     , m_init(false)
0166     , m_bus(ibus_bus_new())
0167     , m_impanel(nullptr)
0168     , m_keyboardGrabbed(false)
0169     , m_doGrab(false)
0170     , m_syms(nullptr)
0171     , m_watcher(new QDBusServiceWatcher(this))
0172 {
0173     m_syms = xcb_key_symbols_alloc(QX11Info::connection());
0174     installNativeEventFilter(m_eventFilter.get());
0175 
0176     initIconMap(m_iconMap);
0177     m_watcher->setConnection(QDBusConnection::sessionBus());
0178     m_watcher->addWatchedService(QStringLiteral("org.kde.impanel"));
0179     init();
0180 }
0181 
0182 uint App::getPrimaryModifier(uint state)
0183 {
0184     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};
0185     for (size_t i = 0; i < sizeof(masks) / sizeof(masks[0]); i++) {
0186         GdkModifierType mask = masks[i];
0187         if ((state & mask) == mask)
0188             return mask;
0189     }
0190     return 0;
0191 }
0192 
0193 bool App::nativeEvent(xcb_generic_event_t *event)
0194 {
0195     if ((event->response_type & ~0x80) == XCB_KEY_PRESS) {
0196         auto keypress = reinterpret_cast<xcb_key_press_event_t *>(event);
0197         if (keypress->event == QX11Info::appRootWindow()) {
0198             auto sym = xcb_key_press_lookup_keysym(m_syms, keypress, 0);
0199             uint state = keypress->state & USED_MASK;
0200             bool forward;
0201             if ((forward = m_triggersList.contains(std::make_pair(sym, state)))
0202                 || m_triggersList.contains(std::make_pair(sym, state & (~XCB_MOD_MASK_SHIFT)))) {
0203                 if (m_keyboardGrabbed) {
0204                     ibus_panel_impanel_navigate(m_impanel, false, forward);
0205                 } else {
0206                     if (grabXKeyboard()) {
0207                         ibus_panel_impanel_navigate(m_impanel, true, forward);
0208                     } else {
0209                         ibus_panel_impanel_move_next(m_impanel);
0210                     }
0211                 }
0212             }
0213         }
0214     } else if ((event->response_type & ~0x80) == XCB_KEY_RELEASE) {
0215         auto keyrelease = reinterpret_cast<xcb_key_release_event_t *>(event);
0216         if (keyrelease->event == QX11Info::appRootWindow()) {
0217             keyRelease(keyrelease);
0218         }
0219     }
0220     return false;
0221 }
0222 
0223 void App::keyRelease(const xcb_key_release_event_t *event)
0224 {
0225     unsigned int mk = event->state & USED_MASK;
0226     // ev.state is state before the key release, so just checking mk being 0 isn't enough
0227     // using XQueryPointer() also doesn't seem to work well, so the check that all
0228     // modifiers are released: only one modifier is active and the currently released
0229     // key is this modifier - if yes, release the grab
0230     int mod_index = -1;
0231     for (int i = XCB_MAP_INDEX_SHIFT; i <= XCB_MAP_INDEX_5; ++i)
0232         if ((mk & (1 << i)) != 0) {
0233             if (mod_index >= 0)
0234                 return;
0235             mod_index = i;
0236         }
0237     bool release = false;
0238     if (mod_index == -1)
0239         release = true;
0240     else {
0241         auto cookie = xcb_get_modifier_mapping(QX11Info::connection());
0242         auto reply = xcb_get_modifier_mapping_reply(QX11Info::connection(), cookie, nullptr);
0243         if (reply) {
0244             auto keycodes = xcb_get_modifier_mapping_keycodes(reply);
0245             for (int i = 0; i < reply->keycodes_per_modifier; i++) {
0246                 if (keycodes[reply->keycodes_per_modifier * mod_index + i] == event->detail) {
0247                     release = true;
0248                 }
0249             }
0250         }
0251         free(reply);
0252     }
0253     if (!release) {
0254         return;
0255     }
0256     if (m_keyboardGrabbed) {
0257         accept();
0258     }
0259 }
0260 
0261 void App::init()
0262 {
0263     // only init once
0264     if (m_init) {
0265         return;
0266     }
0267     if (!ibus_bus_is_connected(m_bus)) {
0268         return;
0269     }
0270     g_signal_connect(m_bus, "connected", G_CALLBACK(ibus_connected_cb), this);
0271     g_signal_connect(m_bus, "disconnected", G_CALLBACK(ibus_disconnected_cb), this);
0272     connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, &App::finalize);
0273     GDBusConnection *connection = ibus_bus_get_connection(m_bus);
0274     g_dbus_connection_signal_subscribe(connection,
0275                                        "org.freedesktop.DBus",
0276                                        "org.freedesktop.DBus",
0277                                        "NameAcquired",
0278                                        "/org/freedesktop/DBus",
0279                                        IBUS_SERVICE_PANEL,
0280                                        G_DBUS_SIGNAL_FLAGS_NONE,
0281                                        name_acquired_cb,
0282                                        this,
0283                                        nullptr);
0284 
0285     g_dbus_connection_signal_subscribe(connection,
0286                                        "org.freedesktop.DBus",
0287                                        "org.freedesktop.DBus",
0288                                        "NameLost",
0289                                        "/org/freedesktop/DBus",
0290                                        IBUS_SERVICE_PANEL,
0291                                        G_DBUS_SIGNAL_FLAGS_NONE,
0292                                        name_lost_cb,
0293                                        this,
0294                                        nullptr);
0295 
0296     ibus_bus_request_name(m_bus, IBUS_SERVICE_PANEL, IBUS_BUS_NAME_FLAG_ALLOW_REPLACEMENT | IBUS_BUS_NAME_FLAG_REPLACE_EXISTING);
0297     m_init = true;
0298 }
0299 
0300 void App::nameAcquired()
0301 {
0302     if (m_impanel) {
0303         g_object_unref(m_impanel);
0304     }
0305     m_impanel = ibus_panel_impanel_new(ibus_bus_get_connection(m_bus));
0306     ibus_panel_impanel_set_bus(m_impanel, m_bus);
0307     ibus_panel_impanel_set_app(m_impanel, this);
0308 }
0309 
0310 void App::nameLost()
0311 {
0312     setDoGrab(false);
0313     if (m_impanel) {
0314         g_object_unref(m_impanel);
0315     }
0316     m_impanel = nullptr;
0317 }
0318 
0319 QByteArray App::normalizeIconName(const QByteArray &icon) const
0320 {
0321     if (m_iconMap.contains(icon)) {
0322         return m_iconMap[icon];
0323     }
0324 
0325     return icon;
0326 }
0327 
0328 void App::setTriggerKeys(QList<TriggerKey> triggersList)
0329 {
0330     if (m_doGrab) {
0331         ungrabKey();
0332     }
0333     m_triggersList = triggersList;
0334 
0335     if (m_doGrab) {
0336         grabKey();
0337     }
0338 }
0339 
0340 void App::setDoGrab(bool doGrab)
0341 {
0342     if (m_doGrab != doGrab) {
0343         ;
0344         if (doGrab) {
0345             grabKey();
0346         } else {
0347             ungrabKey();
0348         }
0349         m_doGrab = doGrab;
0350     }
0351 }
0352 
0353 void App::grabKey()
0354 {
0355     for (const TriggerKey &key : std::as_const(m_triggersList)) {
0356         xcb_keysym_t sym = key.first;
0357         uint modifiers = key.second;
0358         xcb_keycode_t *keycode = xcb_key_symbols_get_keycode(m_syms, sym);
0359         if (!keycode) {
0360             g_warning("Can not convert keyval=%u to keycode!", sym);
0361         } else {
0362             xcb_grab_key(QX11Info::connection(), true, QX11Info::appRootWindow(), modifiers, keycode[0], XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
0363             if ((modifiers & XCB_MOD_MASK_SHIFT) == 0) {
0364                 xcb_grab_key(QX11Info::connection(),
0365                              true,
0366                              QX11Info::appRootWindow(),
0367                              modifiers | XCB_MOD_MASK_SHIFT,
0368                              keycode[0],
0369                              XCB_GRAB_MODE_ASYNC,
0370                              XCB_GRAB_MODE_ASYNC);
0371             }
0372         }
0373         free(keycode);
0374     }
0375 }
0376 
0377 void App::ungrabKey()
0378 {
0379     for (const TriggerKey &key : std::as_const(m_triggersList)) {
0380         xcb_keysym_t sym = key.first;
0381         uint modifiers = key.second;
0382         xcb_keycode_t *keycode = xcb_key_symbols_get_keycode(m_syms, sym);
0383         if (!keycode) {
0384             g_warning("Can not convert keyval=%u to keycode!", sym);
0385         } else {
0386             xcb_ungrab_key(QX11Info::connection(), keycode[0], QX11Info::appRootWindow(), modifiers);
0387             if ((modifiers & XCB_MOD_MASK_SHIFT) == 0) {
0388                 xcb_ungrab_key(QX11Info::connection(), keycode[0], QX11Info::appRootWindow(), modifiers | XCB_MOD_MASK_SHIFT);
0389             }
0390         }
0391         free(keycode);
0392     }
0393 }
0394 
0395 bool App::grabXKeyboard()
0396 {
0397     if (m_keyboardGrabbed)
0398         return false;
0399     auto w = QX11Info::appRootWindow();
0400     auto cookie = xcb_grab_keyboard(QX11Info::connection(), false, w, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
0401     auto reply = xcb_grab_keyboard_reply(QX11Info::connection(), cookie, nullptr);
0402 
0403     if (reply && reply->status == XCB_GRAB_STATUS_SUCCESS) {
0404         m_keyboardGrabbed = true;
0405     }
0406     free(reply);
0407     return m_keyboardGrabbed;
0408 }
0409 
0410 void App::ungrabXKeyboard()
0411 {
0412     if (!m_keyboardGrabbed) {
0413         // grabXKeyboard() may fail sometimes, so don't fail, but at least warn anyway
0414         qDebug() << "ungrabXKeyboard() called but keyboard not grabbed!";
0415     }
0416     m_keyboardGrabbed = false;
0417     xcb_ungrab_keyboard(QX11Info::connection(), XCB_CURRENT_TIME);
0418 }
0419 
0420 void App::accept()
0421 {
0422     if (m_keyboardGrabbed) {
0423         ungrabXKeyboard();
0424     }
0425 
0426     ibus_panel_impanel_accept(m_impanel);
0427 }
0428 
0429 void App::finalize()
0430 {
0431     clean();
0432     App::exit(0);
0433 }
0434 
0435 void App::clean()
0436 {
0437     if (m_impanel) {
0438         g_object_unref(m_impanel);
0439         m_impanel = nullptr;
0440     }
0441 
0442     if (m_bus) {
0443         g_signal_handlers_disconnect_by_func(m_bus, (gpointer)ibus_disconnected_cb, this);
0444         g_signal_handlers_disconnect_by_func(m_bus, (gpointer)ibus_connected_cb, this);
0445         g_object_unref(m_bus);
0446         m_bus = nullptr;
0447     }
0448     ungrabKey();
0449 }
0450 
0451 App::~App()
0452 {
0453     clean();
0454     if (m_syms) {
0455         xcb_key_symbols_free(m_syms);
0456     }
0457 }