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 }