/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * LXDE-Qt - a lightweight, Qt based, desktop toolset * http://razor-qt.org * * Copyright: 2013 Razor team * Authors: * Kuzma Shapran * * This program or library is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pipe_utils.h" #include "string_utils.h" #include "daemon_adaptor.h" #include "native_adaptor.h" #include "base_action.h" #include "method_action.h" #include "client_action.h" #include "command_action.h" #include "core.h" enum { X11_OP_StringToKeycode, X11_OP_KeycodeToString, X11_OP_XGrabKey, X11_OP_XUngrabKey, X11_OP_XGrabKeyboard, X11_OP_XUngrabKeyboard }; static Core *s_Core = 0; static const QLatin1String ExecKey("Exec"); static const QLatin1String EnabledKey("Enabled"); static const QLatin1String CommentKey("Comment"); static const QLatin1String serviceKey("service"); static const QLatin1String pathKey("path"); static const QLatin1String interfaceKey("interface"); static const QLatin1String methodKey("method"); static const QLatin1String firstStr("first"); static const QLatin1String lastStr("last"); static const QLatin1String allStr("all"); static const QLatin1String noneStr("none"); int x11ErrorHandler(Display *display, XErrorEvent *errorEvent) { if (s_Core) { return s_Core->x11ErrorHandler(display, errorEvent); } return 0; } const char *x11opcodeToString(unsigned char opcode) { switch (opcode) { case X_CreateWindow: return "CreateWindow"; case X_ChangeWindowAttributes: return "ChangeWindowAttributes"; case X_GetWindowAttributes: return "GetWindowAttributes"; case X_DestroyWindow: return "DestroyWindow"; case X_DestroySubwindows: return "DestroySubwindows"; case X_ChangeSaveSet: return "ChangeSaveSet"; case X_ReparentWindow: return "ReparentWindow"; case X_MapWindow: return "MapWindow"; case X_MapSubwindows: return "MapSubwindows"; case X_UnmapWindow: return "UnmapWindow"; case X_UnmapSubwindows: return "UnmapSubwindows"; case X_ConfigureWindow: return "ConfigureWindow"; case X_CirculateWindow: return "CirculateWindow"; case X_GetGeometry: return "GetGeometry"; case X_QueryTree: return "QueryTree"; case X_InternAtom: return "InternAtom"; case X_GetAtomName: return "GetAtomName"; case X_ChangeProperty: return "ChangeProperty"; case X_DeleteProperty: return "DeleteProperty"; case X_GetProperty: return "GetProperty"; case X_ListProperties: return "ListProperties"; case X_SetSelectionOwner: return "SetSelectionOwner"; case X_GetSelectionOwner: return "GetSelectionOwner"; case X_ConvertSelection: return "ConvertSelection"; case X_SendEvent: return "SendEvent"; case X_GrabPointer: return "GrabPointer"; case X_UngrabPointer: return "UngrabPointer"; case X_GrabButton: return "GrabButton"; case X_UngrabButton: return "UngrabButton"; case X_ChangeActivePointerGrab: return "ChangeActivePointerGrab"; case X_GrabKeyboard: return "GrabKeyboard"; case X_UngrabKeyboard: return "UngrabKeyboard"; case X_GrabKey: return "GrabKey"; case X_UngrabKey: return "UngrabKey"; case X_AllowEvents: return "AllowEvents"; case X_GrabServer: return "GrabServer"; case X_UngrabServer: return "UngrabServer"; case X_QueryPointer: return "QueryPointer"; case X_GetMotionEvents: return "GetMotionEvents"; case X_TranslateCoords: return "TranslateCoords"; case X_WarpPointer: return "WarpPointer"; case X_SetInputFocus: return "SetInputFocus"; case X_GetInputFocus: return "GetInputFocus"; case X_QueryKeymap: return "QueryKeymap"; case X_OpenFont: return "OpenFont"; case X_CloseFont: return "CloseFont"; case X_QueryFont: return "QueryFont"; case X_QueryTextExtents: return "QueryTextExtents"; case X_ListFonts: return "ListFonts"; case X_ListFontsWithInfo: return "ListFontsWithInfo"; case X_SetFontPath: return "SetFontPath"; case X_GetFontPath: return "GetFontPath"; case X_CreatePixmap: return "CreatePixmap"; case X_FreePixmap: return "FreePixmap"; case X_CreateGC: return "CreateGC"; case X_ChangeGC: return "ChangeGC"; case X_CopyGC: return "CopyGC"; case X_SetDashes: return "SetDashes"; case X_SetClipRectangles: return "SetClipRectangles"; case X_FreeGC: return "FreeGC"; case X_ClearArea: return "ClearArea"; case X_CopyArea: return "CopyArea"; case X_CopyPlane: return "CopyPlane"; case X_PolyPoint: return "PolyPoint"; case X_PolyLine: return "PolyLine"; case X_PolySegment: return "PolySegment"; case X_PolyRectangle: return "PolyRectangle"; case X_PolyArc: return "PolyArc"; case X_FillPoly: return "FillPoly"; case X_PolyFillRectangle: return "PolyFillRectangle"; case X_PolyFillArc: return "PolyFillArc"; case X_PutImage: return "PutImage"; case X_GetImage: return "GetImage"; case X_PolyText8: return "PolyText8"; case X_PolyText16: return "PolyText16"; case X_ImageText8: return "ImageText8"; case X_ImageText16: return "ImageText16"; case X_CreateColormap: return "CreateColormap"; case X_FreeColormap: return "FreeColormap"; case X_CopyColormapAndFree: return "CopyColormapAndFree"; case X_InstallColormap: return "InstallColormap"; case X_UninstallColormap: return "UninstallColormap"; case X_ListInstalledColormaps: return "ListInstalledColormaps"; case X_AllocColor: return "AllocColor"; case X_AllocNamedColor: return "AllocNamedColor"; case X_AllocColorCells: return "AllocColorCells"; case X_AllocColorPlanes: return "AllocColorPlanes"; case X_FreeColors: return "FreeColors"; case X_StoreColors: return "StoreColors"; case X_StoreNamedColor: return "StoreNamedColor"; case X_QueryColors: return "QueryColors"; case X_LookupColor: return "LookupColor"; case X_CreateCursor: return "CreateCursor"; case X_CreateGlyphCursor: return "CreateGlyphCursor"; case X_FreeCursor: return "FreeCursor"; case X_RecolorCursor: return "RecolorCursor"; case X_QueryBestSize: return "QueryBestSize"; case X_QueryExtension: return "QueryExtension"; case X_ListExtensions: return "ListExtensions"; case X_ChangeKeyboardMapping: return "ChangeKeyboardMapping"; case X_GetKeyboardMapping: return "GetKeyboardMapping"; case X_ChangeKeyboardControl: return "ChangeKeyboardControl"; case X_GetKeyboardControl: return "GetKeyboardControl"; case X_Bell: return "Bell"; case X_ChangePointerControl: return "ChangePointerControl"; case X_GetPointerControl: return "GetPointerControl"; case X_SetScreenSaver: return "SetScreenSaver"; case X_GetScreenSaver: return "GetScreenSaver"; case X_ChangeHosts: return "ChangeHosts"; case X_ListHosts: return "ListHosts"; case X_SetAccessControl: return "SetAccessControl"; case X_SetCloseDownMode: return "SetCloseDownMode"; case X_KillClient: return "KillClient"; case X_RotateProperties: return "RotateProperties"; case X_ForceScreenSaver: return "ForceScreenSaver"; case X_SetPointerMapping: return "SetPointerMapping"; case X_GetPointerMapping: return "GetPointerMapping"; case X_SetModifierMapping: return "SetModifierMapping"; case X_GetModifierMapping: return "GetModifierMapping"; case X_NoOperation: return "NoOperation"; } return ""; } static const char *strLevel(int level) { switch (level) { case LOG_EMERG: return "Emergency"; case LOG_ALERT: return "Alert"; case LOG_CRIT: return "Critical"; case LOG_ERR: return "Error"; case LOG_WARNING: return "Warning"; case LOG_NOTICE: return "Notice"; case LOG_INFO: return "Info"; case LOG_DEBUG: return "Debug"; default: return ""; } } Core::Core(bool useSyslog, bool minLogLevelSet, int minLogLevel, const QStringList &configFiles, bool multipleActionsBehaviourSet, MultipleActionsBehaviour multipleActionsBehaviour, QObject *parent) : QThread(parent) , LogTarget() , mReady(false) , mUseSyslog(useSyslog) , mMinLogLevel(minLogLevel) , mDisplay(0) , mInterClientCommunicationWindow(0) , mServiceWatcher{new QDBusServiceWatcher{this}} , mDaemonAdaptor(0) , mNativeAdaptor(0) , mLastId(0ull) , mGrabbingShortcut(false) , AltMask(Mod1Mask) , MetaMask(Mod4Mask) , Level3Mask(Mod5Mask) , Level5Mask(Mod3Mask) , mMultipleActionsBehaviour(multipleActionsBehaviour) , mAllowGrabLocks(false) , mAllowGrabBaseSpecial(false) , mAllowGrabMiscSpecial(true) , mAllowGrabBaseKeypad(true) , mAllowGrabMiscKeypad(true) , mAllowGrabPrintable(false) , mSaveAllowed(false) , mShortcutGrabTimeout(new QTimer(this)) , mShortcutGrabRequested(false) { s_Core = this; initBothPipeEnds(mX11ErrorPipe); initBothPipeEnds(mX11RequestPipe); initBothPipeEnds(mX11ResponsePipe); mConfigFile = QString(getenv("HOME")) + QStringLiteral("/.config/global_key_shortcutss.ini"); try { error_t c_error; openlog("lxqt-global-action-daemon", LOG_PID, LOG_USER); connect(lxqtApp, &LXQt::Application::unixSignal, this, &Core::unixSignalHandler); lxqtApp->listenToUnixSignals(QList() << SIGTERM << SIGINT); if (!QDBusConnection::sessionBus().registerService("org.lxqt.global_key_shortcuts")) { throw std::runtime_error(std::string("Cannot register service 'org.lxqt.global_key_shortcuts'")); } if ((c_error = createPipe(mX11ErrorPipe))) { throw std::runtime_error(std::string("Cannot create error signal pipe: ") + std::string(strerror(c_error))); } if ((c_error = createPipe(mX11RequestPipe))) { throw std::runtime_error(std::string("Cannot create X11 request pipe: ") + std::string(strerror(c_error))); } if ((c_error = createPipe(mX11ResponsePipe))) { throw std::runtime_error(std::string("Cannot create X11 response pipe: ") + std::string(strerror(c_error))); } start(); char signal; error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &signal, sizeof(signal)); if (error > 0) { throw std::runtime_error(std::string("Cannot read X11 start signal: ") + std::string(strerror(c_error))); } if (error < 0) { throw std::runtime_error(std::string("Cannot read X11 start signal")); } if (signal) { throw std::runtime_error(std::string("Cannot start X11 thread")); } { size_t fm = configFiles.size(); for (size_t fi = 0; fi < fm; ++fi) { mConfigFile = configFiles[fi]; QSettings settings(mConfigFile, QSettings::IniFormat, this); QString iniValue; if (!minLogLevelSet) { iniValue = settings.value(/* General/ */QStringLiteral("LogLevel")).toString(); if (!iniValue.isEmpty()) { if (iniValue == QLatin1String("error")) { mMinLogLevel = LOG_ERR; } else if (iniValue == QLatin1String("warning")) { mMinLogLevel = LOG_WARNING; } else if (iniValue == QLatin1String("notice")) { mMinLogLevel = LOG_NOTICE; } else if (iniValue == QLatin1String("info")) { mMinLogLevel = LOG_INFO; } else if (iniValue == QLatin1String("debug")) { mMinLogLevel = LOG_DEBUG; } } } if (!multipleActionsBehaviourSet) { iniValue = settings.value(/* General/ */QStringLiteral("MultipleActionsBehaviour")).toString(); if (!iniValue.isEmpty()) { if (iniValue == firstStr) { mMultipleActionsBehaviour = MULTIPLE_ACTIONS_BEHAVIOUR_FIRST; } else if (iniValue == lastStr) { mMultipleActionsBehaviour = MULTIPLE_ACTIONS_BEHAVIOUR_LAST; } else if (iniValue == allStr) { mMultipleActionsBehaviour = MULTIPLE_ACTIONS_BEHAVIOUR_ALL; } else if (iniValue == noneStr) { mMultipleActionsBehaviour = MULTIPLE_ACTIONS_BEHAVIOUR_NONE; } } } mAllowGrabLocks = settings.value(/* General/ */QStringLiteral("AllowGrabLocks"), mAllowGrabLocks).toBool(); mAllowGrabBaseSpecial = settings.value(/* General/ */QStringLiteral("AllowGrabBaseSpecial"), mAllowGrabBaseSpecial).toBool(); mAllowGrabMiscSpecial = settings.value(/* General/ */QStringLiteral("AllowGrabMiscSpecial"), mAllowGrabMiscSpecial).toBool(); mAllowGrabBaseKeypad = settings.value(/* General/ */QStringLiteral("AllowGrabBaseKeypad"), mAllowGrabBaseKeypad).toBool(); mAllowGrabMiscKeypad = settings.value(/* General/ */QStringLiteral("AllowGrabMiscKeypad"), mAllowGrabMiscKeypad).toBool(); foreach(const QString §ion, settings.childGroups()) { if (section != QLatin1String("General")) { settings.beginGroup(section); QString shortcut = section; int pos = shortcut.indexOf(QLatin1Char('.')); if (pos != -1) { shortcut = shortcut.left(pos); } qulonglong id = 0ull; bool enabled = settings.value(EnabledKey, true).toBool(); QString description = settings.value(CommentKey).toString(); if (settings.contains(ExecKey)) { QStringList values = settings.value(ExecKey).toStringList(); id = registerCommandAction(shortcut, values[0], values.mid(1), description); } else { iniValue = settings.value(pathKey).toString(); if (!iniValue.isEmpty()) { QString path = iniValue; if (settings.contains(interfaceKey)) { QString interface = settings.value(interfaceKey).toString(); iniValue = settings.value(serviceKey).toString(); if (!iniValue.isEmpty()) { QString service = iniValue; iniValue = settings.value(methodKey).toString(); if (!iniValue.isEmpty()) { QString method = iniValue; id = registerMethodAction(shortcut, service, QDBusObjectPath(path), interface, method, description); } } } else { id = registerClientAction(shortcut, QDBusObjectPath(path), description); } } } if (id) { enableActionNonGuarded(id, enabled); } settings.endGroup(); } } } } log(LOG_DEBUG, "Config file: %s", qPrintable(mConfigFile)); log(LOG_DEBUG, "MinLogLevel: %s", strLevel(mMinLogLevel)); switch (mMultipleActionsBehaviour) { case MULTIPLE_ACTIONS_BEHAVIOUR_FIRST: log(LOG_DEBUG, "MultipleActionsBehaviour: first"); break; case MULTIPLE_ACTIONS_BEHAVIOUR_LAST: log(LOG_DEBUG, "MultipleActionsBehaviour: last"); break; case MULTIPLE_ACTIONS_BEHAVIOUR_ALL: log(LOG_DEBUG, "MultipleActionsBehaviour: all"); break; case MULTIPLE_ACTIONS_BEHAVIOUR_NONE: log(LOG_DEBUG, "MultipleActionsBehaviour: none"); break; default: ; } log(LOG_DEBUG, "AllowGrabLocks: %s", mAllowGrabLocks ? "true" : "false"); log(LOG_DEBUG, "AllowGrabBaseSpecial: %s", mAllowGrabBaseSpecial ? "true" : "false"); log(LOG_DEBUG, "AllowGrabMiscSpecial: %s", mAllowGrabMiscSpecial ? "true" : "false"); log(LOG_DEBUG, "AllowGrabBaseKeypad: %s", mAllowGrabBaseKeypad ? "true" : "false"); log(LOG_DEBUG, "AllowGrabMiscKeypad: %s", mAllowGrabMiscKeypad ? "true" : "false"); mSaveAllowed = true; saveConfig(); mServiceWatcher->setConnection(QDBusConnection::sessionBus()); mDaemonAdaptor = new DaemonAdaptor(this); if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/daemon"), mDaemonAdaptor)) { throw std::runtime_error(std::string("Cannot create daemon adaptor")); } mNativeAdaptor = new NativeAdaptor(this); if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/native"), mNativeAdaptor)) { throw std::runtime_error(std::string("Cannot create daemon native client adaptor")); } connect(mServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &Core::serviceDisappeared); connect(mDaemonAdaptor, SIGNAL(onAddMethodAction(QPair&, QString, QString, QDBusObjectPath, QString, QString, QString)), this, SLOT(addMethodAction(QPair&, QString, QString, QDBusObjectPath, QString, QString, QString))); connect(mDaemonAdaptor, SIGNAL(onAddCommandAction(QPair&, QString, QString, QStringList, QString)), this, SLOT(addCommandAction(QPair&, QString, QString, QStringList, QString))); connect(mDaemonAdaptor, SIGNAL(onModifyActionDescription(bool &, qulonglong, QString)), this, SLOT(modifyActionDescription(bool &, qulonglong, QString))); connect(mDaemonAdaptor, SIGNAL(onModifyMethodAction(bool &, qulonglong, QString, QDBusObjectPath, QString, QString, QString)), this, SLOT(modifyMethodAction(bool &, qulonglong, QString, QDBusObjectPath, QString, QString, QString))); connect(mDaemonAdaptor, SIGNAL(onModifyCommandAction(bool &, qulonglong, QString, QStringList, QString)), this, SLOT(modifyCommandAction(bool &, qulonglong, QString, QStringList, QString))); connect(mDaemonAdaptor, SIGNAL(onEnableAction(bool &, qulonglong, bool)), this, SLOT(enableAction(bool &, qulonglong, bool))); connect(mDaemonAdaptor, SIGNAL(onIsActionEnabled(bool &, qulonglong)), this, SLOT(isActionEnabled(bool &, qulonglong))); connect(mDaemonAdaptor, SIGNAL(onGetClientActionSender(QString &, qulonglong)), this, SLOT(getClientActionSender(QString &, qulonglong))); connect(mDaemonAdaptor, SIGNAL(onChangeShortcut(QString &, qulonglong, QString)), this, SLOT(changeShortcut(QString &, qulonglong, QString))); connect(mDaemonAdaptor, SIGNAL(onSwapActions(bool &, qulonglong, qulonglong)), this, SLOT(swapActions(bool &, qulonglong, qulonglong))); connect(mDaemonAdaptor, SIGNAL(onRemoveAction(bool &, qulonglong)), this, SLOT(removeAction(bool &, qulonglong))); connect(mDaemonAdaptor, SIGNAL(onSetMultipleActionsBehaviour(MultipleActionsBehaviour)), this, SLOT(setMultipleActionsBehaviour(MultipleActionsBehaviour))); connect(mDaemonAdaptor, SIGNAL(onGetMultipleActionsBehaviour(MultipleActionsBehaviour &)), this, SLOT(getMultipleActionsBehaviour(MultipleActionsBehaviour &))); connect(mDaemonAdaptor, SIGNAL(onGetAllActionIds(QList&)), this, SLOT(getAllActionIds(QList&))); connect(mDaemonAdaptor, SIGNAL(onGetActionById(QPair&, qulonglong)), this, SLOT(getActionById(QPair&, qulonglong))); connect(mDaemonAdaptor, SIGNAL(onGetAllActions(QMap&)), this, SLOT(getAllActions(QMap&))); connect(mDaemonAdaptor, SIGNAL(onGetClientActionInfoById(QPair&, qulonglong)), this, SLOT(getClientActionInfoById(QPair&, qulonglong))); connect(mDaemonAdaptor, SIGNAL(onGetMethodActionInfoById(QPair&, qulonglong)), this, SLOT(getMethodActionInfoById(QPair&, qulonglong))); connect(mDaemonAdaptor, SIGNAL(onGetCommandActionInfoById(QPair&, qulonglong)), this, SLOT(getCommandActionInfoById(QPair&, qulonglong))); connect(mDaemonAdaptor, SIGNAL(onGrabShortcut(uint, QString &, bool &, bool &, bool &, QDBusMessage)), this, SLOT(grabShortcut(uint, QString &, bool &, bool &, bool &, QDBusMessage))); connect(mDaemonAdaptor, SIGNAL(onCancelShortcutGrab()), this, SLOT(cancelShortcutGrab())); connect(mDaemonAdaptor, SIGNAL(onQuit()), qApp, SLOT(quit())); connect(mNativeAdaptor, SIGNAL(onAddClientAction(QPair&, QString, QDBusObjectPath, QString, QString)), this, SLOT(addClientAction(QPair&, QString, QDBusObjectPath, QString, QString))); connect(mNativeAdaptor, SIGNAL(onModifyClientAction(qulonglong &, QDBusObjectPath, QString, QString)), this, SLOT(modifyClientAction(qulonglong &, QDBusObjectPath, QString, QString))); connect(mNativeAdaptor, SIGNAL(onEnableClientAction(bool &, QDBusObjectPath, bool, QString)), this, SLOT(enableClientAction(bool &, QDBusObjectPath, bool, QString))); connect(mNativeAdaptor, SIGNAL(onIsClientActionEnabled(bool &, QDBusObjectPath, QString)), this, SLOT(isClientActionEnabled(bool &, QDBusObjectPath, QString))); connect(mNativeAdaptor, SIGNAL(onChangeClientActionShortcut(QPair&, QDBusObjectPath, QString, QString)), this, SLOT(changeClientActionShortcut(QPair&, QDBusObjectPath, QString, QString))); connect(mNativeAdaptor, SIGNAL(onRemoveClientAction(bool &, QDBusObjectPath, QString)), this, SLOT(removeClientAction(bool &, QDBusObjectPath, QString))); connect(mNativeAdaptor, SIGNAL(onDeactivateClientAction(bool &, QDBusObjectPath, QString)), this, SLOT(deactivateClientAction(bool &, QDBusObjectPath, QString))); connect(mNativeAdaptor, SIGNAL(onGrabShortcut(uint, QString &, bool &, bool &, bool &, QDBusMessage)), this, SLOT(grabShortcut(uint, QString &, bool &, bool &, bool &, QDBusMessage))); connect(mNativeAdaptor, SIGNAL(onCancelShortcutGrab()), this, SLOT(cancelShortcutGrab())); mShortcutGrabTimeout->setSingleShot(true); connect(this, SIGNAL(onShortcutGrabbed()), this, SLOT(shortcutGrabbed()), Qt::QueuedConnection); connect(mShortcutGrabTimeout, SIGNAL(timeout()), this, SLOT(shortcutGrabTimedout())); log(LOG_NOTICE, "Started"); mReady = true; } catch (const std::exception &err) { log(LOG_CRIT, "%s", err.what()); } } Core::~Core() { log(LOG_INFO, "Stopping"); closeBothPipeEnds(mX11ErrorPipe); closeBothPipeEnds(mX11RequestPipe); closeBothPipeEnds(mX11ResponsePipe); mX11EventLoopActive = false; wakeX11Thread(); wait(); delete mDaemonAdaptor; ShortcutAndActionById::iterator lastShortcutAndActionById = mShortcutAndActionById.end(); for (ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.begin(); shortcutAndActionById != lastShortcutAndActionById; ++shortcutAndActionById) { delete shortcutAndActionById.value().second; } log(LOG_NOTICE, "Stopped"); closelog(); } void Core::saveConfig() { if (!mSaveAllowed) { return; } QSettings settings(mConfigFile, QSettings::IniFormat); settings.clear(); switch (mMultipleActionsBehaviour) { case MULTIPLE_ACTIONS_BEHAVIOUR_FIRST: settings.setValue(/* General/ */"MultipleActionsBehaviour", firstStr); break; case MULTIPLE_ACTIONS_BEHAVIOUR_LAST: settings.setValue(/* General/ */"MultipleActionsBehaviour", lastStr); break; case MULTIPLE_ACTIONS_BEHAVIOUR_ALL: settings.setValue(/* General/ */"MultipleActionsBehaviour", allStr); break; case MULTIPLE_ACTIONS_BEHAVIOUR_NONE: settings.setValue(/* General/ */"MultipleActionsBehaviour", noneStr); break; default: ; } settings.setValue(/* General/ */"AllowGrabLocks", mAllowGrabLocks); settings.setValue(/* General/ */"AllowGrabBaseSpecial", mAllowGrabBaseSpecial); settings.setValue(/* General/ */"AllowGrabMiscSpecial", mAllowGrabMiscSpecial); settings.setValue(/* General/ */"AllowGrabBaseKeypad", mAllowGrabBaseKeypad); settings.setValue(/* General/ */"AllowGrabMiscKeypad", mAllowGrabMiscKeypad); ShortcutAndActionById::const_iterator lastShortcutAndActionById = mShortcutAndActionById.end(); for (ShortcutAndActionById::const_iterator shortcutAndActionById = mShortcutAndActionById.begin(); shortcutAndActionById != lastShortcutAndActionById; ++shortcutAndActionById) { const BaseAction *action = shortcutAndActionById.value().second; QString section = shortcutAndActionById.value().first + "." + QString::number(shortcutAndActionById.key()); settings.beginGroup(section); settings.setValue(EnabledKey, action->isEnabled()); settings.setValue(CommentKey, action->description()); if (!strcmp(action->type(), CommandAction::id())) { const CommandAction *commandAction = dynamic_cast(action); settings.setValue(ExecKey, QVariant(QStringList() << commandAction->command() += commandAction->args())); } else if (!strcmp(action->type(), MethodAction::id())) { const MethodAction *methodAction = dynamic_cast(action); settings.setValue(serviceKey, methodAction->service()); settings.setValue(pathKey, methodAction->path().path()); settings.setValue(interfaceKey, methodAction->interface()); settings.setValue(methodKey, methodAction->method()); } else if (!strcmp(action->type(), ClientAction::id())) { const ClientAction *clientAction = dynamic_cast(action); settings.setValue(pathKey, clientAction->path().path()); } settings.endGroup(); } } void Core::unixSignalHandler(int signalNumber) { log(LOG_INFO, "Signal #%d received", signalNumber); qApp->quit(); } void Core::log(int level, const char *format, ...) const { if (level > mMinLogLevel) { return; } va_list ap; va_start(ap, format); if (mUseSyslog) { vsyslog(LOG_MAKEPRI(LOG_USER, level), format, ap); } else { fprintf(stderr, "[%s] ", strLevel(level)); vfprintf(stderr, format, ap); fprintf(stderr, "\n"); } va_end(ap); } int Core::x11ErrorHandler(Display */*display*/, XErrorEvent *errorEvent) { if (error_t error = writeAll(mX11ErrorPipe[STDOUT_FILENO], errorEvent, sizeof(XErrorEvent))) { log(LOG_CRIT, "Cannot write to error signal pipe: %s", strerror(error)); qApp->quit(); return 0; } return 0; } bool Core::waitForX11Error(int level, uint timeout) { pollfd fds[1]; fds[0].fd = mX11ErrorPipe[STDIN_FILENO]; fds[0].events = POLLIN | POLLERR | POLLHUP; if (poll(fds, 1, timeout) < 0) { return true; } bool result = false; while (fds[0].revents & POLLIN) { XErrorEvent errorEvent; if (error_t error = readAll(mX11ErrorPipe[STDIN_FILENO], &errorEvent, sizeof(errorEvent))) { log(LOG_CRIT, "Cannot read from error signal pipe: %s", strerror(error)); qApp->quit(); } char errorString[1024]; XGetErrorText(errorEvent.display, errorEvent.error_code, errorString, 1023); log(level, "X11 error: type: %d, serial: %lu, error_code: %d '%s', request_code: %d (%s), minor_code: %d, resourceid: %lu", errorEvent.type, errorEvent.serial, errorEvent.error_code, errorString, errorEvent.request_code, x11opcodeToString(errorEvent.request_code), errorEvent.minor_code, errorEvent.resourceid); result = true; if (poll(fds, 1, 0) < 0) { return true; } } return result; } void Core::lockX11Error() { mX11ErrorMutex.lock(); waitForX11Error(false, 0); } bool Core::checkX11Error(int level, uint timeout) { // unsigned long serial = NextRequest(mDisplay); // log(LOG_DEBUG, "X11 error: serial: %lu", serial); bool result = waitForX11Error(level, timeout); mX11ErrorMutex.unlock(); return result; } void Core::wakeX11Thread() { if (mInterClientCommunicationWindow) { XClientMessageEvent dummyEvent; memset(&dummyEvent, 0, sizeof(dummyEvent)); dummyEvent.type = ClientMessage; dummyEvent.window = mInterClientCommunicationWindow; dummyEvent.format = 32; lockX11Error(); XSendEvent(mDisplay, mInterClientCommunicationWindow, 0, 0, reinterpret_cast(&dummyEvent)); checkX11Error(); XFlush(mDisplay); } } bool Core::isEscape(KeySym keySym, unsigned int modifiers) { return ((keySym == XK_Escape) && (!modifiers)); } bool Core::isModifier(KeySym keySym) { switch (keySym) { case XK_Shift_L: case XK_Shift_R: case XK_Control_L: case XK_Control_R: case XK_Meta_L: case XK_Meta_R: case XK_Alt_L: case XK_Alt_R: case XK_Super_L: case XK_Super_R: case XK_Hyper_L: case XK_Hyper_R: case XK_ISO_Level3_Shift: case XK_ISO_Level5_Shift: case XK_ISO_Group_Shift: return true; } return false; } bool Core::isAllowed(KeySym keySym, unsigned int modifiers) { switch (keySym) { case XK_Scroll_Lock: case XK_Num_Lock: case XK_Caps_Lock: case XK_ISO_Lock: case XK_ISO_Level3_Lock: case XK_ISO_Level5_Lock: case XK_ISO_Group_Lock: case XK_ISO_Next_Group_Lock: case XK_ISO_Prev_Group_Lock: case XK_ISO_First_Group_Lock: case XK_ISO_Last_Group_Lock: if (!modifiers) { return mAllowGrabLocks; } break; case XK_Home: case XK_Left: case XK_Up: case XK_Right: case XK_Down: case XK_Page_Up: case XK_Page_Down: case XK_End: case XK_Delete: case XK_Insert: case XK_BackSpace: case XK_Tab: case XK_Return: case XK_space: if (!modifiers) { return mAllowGrabBaseSpecial; } break; case XK_Pause: case XK_Print: case XK_Linefeed: case XK_Clear: case XK_Multi_key: case XK_Codeinput: case XK_SingleCandidate: case XK_MultipleCandidate: case XK_PreviousCandidate: case XK_Begin: case XK_Select: case XK_Execute: case XK_Undo: case XK_Redo: case XK_Menu: case XK_Find: case XK_Cancel: case XK_Help: case XK_Sys_Req: case XK_Break: if (!modifiers) { return mAllowGrabMiscSpecial; } break; case XK_KP_Enter: case XK_KP_Home: case XK_KP_Left: case XK_KP_Up: case XK_KP_Right: case XK_KP_Down: case XK_KP_Page_Up: case XK_KP_Page_Down: case XK_KP_End: case XK_KP_Begin: case XK_KP_Insert: case XK_KP_Delete: case XK_KP_Multiply: case XK_KP_Add: case XK_KP_Subtract: case XK_KP_Decimal: case XK_KP_Divide: case XK_KP_0: case XK_KP_1: case XK_KP_2: case XK_KP_3: case XK_KP_4: case XK_KP_5: case XK_KP_6: case XK_KP_7: case XK_KP_8: case XK_KP_9: if (!modifiers) { return mAllowGrabBaseKeypad; } break; case XK_KP_Space: case XK_KP_Tab: case XK_KP_F1: case XK_KP_F2: case XK_KP_F3: case XK_KP_F4: case XK_KP_Equal: case XK_KP_Separator: if (!modifiers) { return mAllowGrabMiscKeypad; } break; case XK_grave: case XK_1: case XK_2: case XK_3: case XK_4: case XK_5: case XK_6: case XK_7: case XK_8: case XK_9: case XK_0: case XK_minus: case XK_equal: case XK_Q: case XK_W: case XK_E: case XK_R: case XK_T: case XK_Y: case XK_U: case XK_I: case XK_O: case XK_P: case XK_bracketleft: case XK_bracketright: case XK_backslash: case XK_A: case XK_S: case XK_D: case XK_F: case XK_G: case XK_H: case XK_J: case XK_K: case XK_L: case XK_semicolon: case XK_apostrophe: case XK_Z: case XK_X: case XK_C: case XK_V: case XK_B: case XK_N: case XK_M: case XK_comma: case XK_period: case XK_slash: if (!(modifiers & ~(ShiftMask | Level3Mask | Level5Mask))) { return mAllowGrabPrintable; } break; } return true; } void Core::run() { mX11EventLoopActive = true; XInitThreads(); int (*oldx11ErrorHandler)(Display * display, XErrorEvent * errorEvent) = XSetErrorHandler(::x11ErrorHandler); mDisplay = XOpenDisplay(NULL); XSynchronize(mDisplay, True); lockX11Error(); Window rootWindow = DefaultRootWindow(mDisplay); XSelectInput(mDisplay, rootWindow, KeyPressMask); mInterClientCommunicationWindow = XCreateSimpleWindow(mDisplay, rootWindow, 0, 0, 1, 1, 0, 0, 0); XSelectInput(mDisplay, mInterClientCommunicationWindow, StructureNotifyMask); if (checkX11Error()) { return; } QSet allModifiers; unsigned int allShifts = ShiftMask | ControlMask | AltMask | MetaMask | Level3Mask | Level5Mask; unsigned int ignoreMask = 0xff ^ allShifts; for (unsigned int i = 0; i < 0x100; ++i) { unsigned int ignoreLocks = i & ignoreMask; allModifiers.insert(ignoreLocks); } char signal = 0; if (write(mX11ResponsePipe[STDOUT_FILENO], &signal, sizeof(signal)) == sizeof(signal)) { XEvent event; while (mX11EventLoopActive) { XNextEvent(mDisplay, &event); if (!mX11EventLoopActive) { break; } switch (event.type) { case KeyPress: { QMutexLocker lock(&mDataMutex); if (mGrabbingShortcut) { // log(LOG_DEBUG, "KeyPress %08x %08x", event.xkey.state, event.xkey.keycode); bool ignoreKey = false; bool cancel = false; QString shortcut; int keysymsPerKeycode; lockX11Error(); KeySym *keySyms = XGetKeyboardMapping(mDisplay, event.xkey.keycode, 1, &keysymsPerKeycode); checkX11Error(); if (keysymsPerKeycode) { if (keySyms[0]) { KeySym keySym = 0; // log(LOG_DEBUG, "keysymsPerKeycode %d", keysymsPerKeycode); // for (int i = 0; i < keysymsPerKeycode; ++i) // log(LOG_DEBUG, "keySym #%d %08x", i, keySyms[i]); if ((keysymsPerKeycode >= 2) && keySyms[1] && (keySyms[0] >= XK_a) && (keySyms[0] <= XK_z)) { keySym = keySyms[1]; } else if (keysymsPerKeycode >= 1) { keySym = keySyms[0]; } if (keySym) { if (isEscape(keySym, event.xkey.state & allShifts)) { cancel = true; } else { if (isModifier(keySym) || !isAllowed(keySym, event.xkey.state & allShifts)) { ignoreKey = true; } else { char *str = XKeysymToString(keySym); if (str && *str) { if (event.xkey.state & ShiftMask) { shortcut += "Shift+"; } if (event.xkey.state & ControlMask) { shortcut += "Control+"; } if (event.xkey.state & AltMask) { shortcut += "Alt+"; } if (event.xkey.state & MetaMask) { shortcut += "Meta+"; } if (event.xkey.state & Level3Mask) { shortcut += "Level3+"; } if (event.xkey.state & Level5Mask) { shortcut += "Level5+"; } shortcut += str; } } } } } } if (!ignoreKey) { IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(shortcut); if ((idsByShortcut == mIdsByShortcut.end()) || (idsByShortcut.value().isEmpty())) { log(LOG_DEBUG, "grabShortcut: checking %s", qPrintable(shortcut)); lockX11Error(); XUngrabKeyboard(mDisplay, CurrentTime); checkX11Error(); QSet::const_iterator lastAllModifiers = allModifiers.end(); for (QSet::const_iterator modifiers = allModifiers.begin(); modifiers != lastAllModifiers; ++modifiers) { log(LOG_DEBUG, "grabShortcut: checking %02x + %02x", event.xkey.keycode, event.xkey.state | *modifiers); lockX11Error(); XGrabKey(mDisplay, event.xkey.keycode, event.xkey.state | *modifiers, rootWindow, False, GrabModeAsync, GrabModeAsync); ignoreKey |= checkX11Error(LOG_DEBUG); } lockX11Error(); XUngrabKey(mDisplay, event.xkey.keycode, event.xkey.state, rootWindow); checkX11Error(); if (ignoreKey) { lockX11Error(); XGrabKeyboard(mDisplay, rootWindow, False, GrabModeAsync, GrabModeAsync, CurrentTime); checkX11Error(); } } else { log(LOG_DEBUG, "grabShortcut: already grabbed %s", qPrintable(shortcut)); lockX11Error(); XUngrabKeyboard(mDisplay, CurrentTime); checkX11Error(); } } if (!ignoreKey) { mGrabbingShortcut = false; if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &cancel, sizeof(cancel))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } if (!cancel) { size_t length = shortcut.length(); if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &length, sizeof(length))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], qPrintable(shortcut), length)) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } } emit onShortcutGrabbed(); } } else { QString shortcut = mShortcutByX11[qMakePair(static_cast(event.xkey.keycode), event.xkey.state & allShifts)]; log(LOG_DEBUG, "KeyPress %08x %08x %s", event.xkey.state & allShifts, event.xkey.keycode, qPrintable(shortcut)); IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(shortcut); if (idsByShortcut != mIdsByShortcut.end()) { Ids &ids = idsByShortcut.value(); switch (mMultipleActionsBehaviour) { case MULTIPLE_ACTIONS_BEHAVIOUR_FIRST: { Ids::iterator lastIds = ids.end(); for (Ids::iterator idi = ids.begin(); idi != lastIds; ++idi) if (mShortcutAndActionById[*idi].second->call()) { break; } } break; case MULTIPLE_ACTIONS_BEHAVIOUR_LAST: { Ids::iterator firstIds = ids.begin(); for (Ids::iterator idi = ids.end(); idi != firstIds;) { --idi; if (mShortcutAndActionById[*idi].second->call()) { break; } } } break; case MULTIPLE_ACTIONS_BEHAVIOUR_NONE: if (ids.size() == 1) { mShortcutAndActionById[*(ids.begin())].second->call(); } break; case MULTIPLE_ACTIONS_BEHAVIOUR_ALL: { Ids::iterator lastIds = ids.end(); for (Ids::iterator idi = ids.begin(); idi != lastIds; ++idi) { mShortcutAndActionById[*idi].second->call(); } } break; default: ; } } } } break; default: { pollfd fds[1]; fds[0].fd = mX11RequestPipe[STDIN_FILENO]; fds[0].events = POLLIN | POLLERR | POLLHUP; if (poll(fds, 1, 0) >= 0) { if (fds[0].revents & POLLIN) { size_t X11Operation; if (error_t error = readAll(mX11RequestPipe[STDIN_FILENO], &X11Operation, sizeof(X11Operation))) { log(LOG_CRIT, "Cannot read from X11 request pipe: %s", strerror(error)); close(mX11ResponsePipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } // log(LOG_DEBUG, "X11Operation: %d", X11Operation); switch (X11Operation) { case X11_OP_StringToKeycode: { bool x11Error = false; KeyCode keyCode = 0; size_t length; if (error_t error = readAll(mX11RequestPipe[STDIN_FILENO], &length, sizeof(length))) { log(LOG_CRIT, "Cannot read from X11 request pipe: %s", strerror(error)); close(mX11ResponsePipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } if (length) { QScopedArrayPointer str(new char[length + 1]); str[length] = '\0'; if (error_t error = readAll(mX11RequestPipe[STDIN_FILENO], str.data(), length)) { log(LOG_CRIT, "Cannot read from X11 request pipe: %s", strerror(error)); close(mX11ResponsePipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } KeySym keySym = XStringToKeysym(str.data()); lockX11Error(); keyCode = XKeysymToKeycode(mDisplay, keySym); x11Error = checkX11Error(); } signal = x11Error ? 1 : 0; if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } if (!x11Error) if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &keyCode, sizeof(keyCode))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } } break; case X11_OP_KeycodeToString: { KeyCode keyCode; bool x11Error = false; if (error_t error = readAll(mX11RequestPipe[STDIN_FILENO], &keyCode, sizeof(keyCode))) { log(LOG_CRIT, "Cannot read from X11 request pipe: %s", strerror(error)); close(mX11ResponsePipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } int keysymsPerKeycode; lockX11Error(); KeySym *keySyms = XGetKeyboardMapping(mDisplay, keyCode, 1, &keysymsPerKeycode); x11Error = checkX11Error(); char *str = NULL; if (!x11Error) { KeySym keySym = 0; if ((keysymsPerKeycode >= 2) && keySyms[1] && (keySyms[0] >= XK_a) && (keySyms[0] <= XK_z)) { keySym = keySyms[1]; } else if (keysymsPerKeycode >= 1) { keySym = keySyms[0]; } if (keySym) { str = XKeysymToString(keySym); } } signal = x11Error ? 1 : 0; if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } if (!x11Error) { size_t length = 0; if (str) { length = strlen(str); } if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &length, sizeof(length))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } if (length) { if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], str, length)) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } } } } break; case X11_OP_XGrabKey: { X11Shortcut X11shortcut; bool x11Error = false; if (error_t error = readAll(mX11RequestPipe[STDIN_FILENO], &X11shortcut.first, sizeof(X11shortcut.first))) { log(LOG_CRIT, "Cannot read from X11 request pipe: %s", strerror(error)); close(mX11ResponsePipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } if (error_t error = readAll(mX11RequestPipe[STDIN_FILENO], &X11shortcut.second, sizeof(X11shortcut.second))) { log(LOG_CRIT, "Cannot read from X11 request pipe: %s", strerror(error)); close(mX11ResponsePipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } QSet::const_iterator lastAllModifiers = allModifiers.end(); for (QSet::const_iterator modifiers = allModifiers.begin(); modifiers != lastAllModifiers; ++modifiers) { lockX11Error(); XGrabKey(mDisplay, X11shortcut.first, X11shortcut.second | *modifiers, rootWindow, False, GrabModeAsync, GrabModeAsync); bool x11e = checkX11Error(); if (x11e) { log(LOG_DEBUG, "XGrabKey: %02x + %02x", X11shortcut.first, X11shortcut.second | *modifiers); } x11Error |= x11e; } signal = x11Error ? 1 : 0; if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } } break; case X11_OP_XUngrabKey: { X11Shortcut X11shortcut; bool x11Error = false; if (error_t error = readAll(mX11RequestPipe[STDIN_FILENO], &X11shortcut.first, sizeof(X11shortcut.first))) { log(LOG_CRIT, "Cannot read from X11 request pipe: %s", strerror(error)); close(mX11ResponsePipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } if (error_t error = readAll(mX11RequestPipe[STDIN_FILENO], &X11shortcut.second, sizeof(X11shortcut.second))) { log(LOG_CRIT, "Cannot read from X11 request pipe: %s", strerror(error)); close(mX11ResponsePipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } lockX11Error(); QSet::const_iterator lastAllModifiers = allModifiers.end(); for (QSet::const_iterator modifiers = allModifiers.begin(); modifiers != lastAllModifiers; ++modifiers) { XUngrabKey(mDisplay, X11shortcut.first, X11shortcut.second | *modifiers, rootWindow); } x11Error = checkX11Error(); signal = x11Error ? 1 : 0; if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } } break; case X11_OP_XGrabKeyboard: { lockX11Error(); int result = XGrabKeyboard(mDisplay, rootWindow, False, GrabModeAsync, GrabModeAsync, CurrentTime); bool x11Error = checkX11Error(); if (!result && x11Error) { result = -1; } if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &result, sizeof(result))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } mDataMutex.lock(); mGrabbingShortcut = true; mDataMutex.unlock(); } break; case X11_OP_XUngrabKeyboard: { lockX11Error(); XUngrabKeyboard(mDisplay, CurrentTime); bool x11Error = checkX11Error(); signal = x11Error ? 1 : 0; if (error_t error = writeAll(mX11ResponsePipe[STDOUT_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot write to X11 response pipe: %s", strerror(error)); close(mX11RequestPipe[STDIN_FILENO]); mX11EventLoopActive = false; break; } mDataMutex.lock(); mGrabbingShortcut = false; mDataMutex.unlock(); } break; } } } } } } } lockX11Error(); XUngrabKey(mDisplay, AnyKey, AnyModifier, rootWindow); XSetErrorHandler(oldx11ErrorHandler); XCloseDisplay(mDisplay); checkX11Error(0); } void Core::serviceDisappeared(const QString &sender) { log(LOG_DEBUG, "serviceDisappeared '%s'", qPrintable(sender)); QMutexLocker lock(&mDataMutex); ClientPathsBySender::iterator clientPathsBySender = mClientPathsBySender.find(sender); if (clientPathsBySender != mClientPathsBySender.end()) { ClientPaths::const_iterator lastClientPath = clientPathsBySender.value().end(); for (ClientPaths::const_iterator clientPath = clientPathsBySender.value().begin(); clientPath != lastClientPath; ++clientPath) { const QDBusObjectPath &path = *clientPath; IdByClientPath::iterator idByClientPath = mIdByClientPath.find(path); if (idByClientPath != mIdByClientPath.end()) { const qulonglong &id = idByClientPath.value(); log(LOG_INFO, "Disactivating client action for '%s' @ %s", qPrintable(sender), qPrintable(path.path())); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById != mShortcutAndActionById.end()) { const QString &shortcut = shortcutAndActionById.value().first; ClientAction * action = dynamic_cast(shortcutAndActionById.value().second); action->disappeared(); mDaemonAdaptor->emit_clientActionSenderChanged(id, QString()); X11Shortcut X11shortcut = mX11ByShortcut[shortcut]; if (action->isEnabled()) { IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(shortcut); if (idsByShortcut != mIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mIdsByShortcut.erase(idsByShortcut); if (!remoteXUngrabKey(X11shortcut)) { log(LOG_WARNING, "Cannot ungrab shortcut '%s'", qPrintable(shortcut)); } } } } else { IdsByShortcut::iterator idsByShortcut = mDisabledIdsByShortcut.find(shortcut); if (idsByShortcut != mDisabledIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mDisabledIdsByShortcut.erase(idsByShortcut); } } } } } mSenderByClientPath.remove(path); } mClientPathsBySender.erase(clientPathsBySender); mServiceWatcher->removeWatchedService(clientPathsBySender.key()); } } KeyCode Core::remoteStringToKeycode(const QString &str) { size_t X11Operation = X11_OP_StringToKeycode; size_t length = str.length(); if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11Operation, sizeof(X11Operation))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return 0; } if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &length, sizeof(length))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return 0; } if (length) { if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], qPrintable(str), length)) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return 0; } } wakeX11Thread(); char signal; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return 0; } if (signal) { return 0; } KeyCode keyCode; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &keyCode, sizeof(keyCode))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return 0; } return keyCode; } QString Core::remoteKeycodeToString(KeyCode keyCode) { QString result; size_t X11Operation = X11_OP_KeycodeToString; if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11Operation, sizeof(X11Operation))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return QString(); } if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &keyCode, sizeof(keyCode))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return QString(); } wakeX11Thread(); char signal; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return QString(); } if (signal) { return QString(); } size_t length; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &length, sizeof(length))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return QString(); } if (length) { QScopedArrayPointer str(new char[length + 1]); str[length] = '\0'; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], str.data(), length)) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return QString(); } result = str.data(); } return result; } bool Core::remoteXGrabKey(const X11Shortcut &X11shortcut) { size_t X11Operation = X11_OP_XGrabKey; if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11Operation, sizeof(X11Operation))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return false; } if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11shortcut.first, sizeof(X11shortcut.first))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return false; } if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11shortcut.second, sizeof(X11shortcut.second))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return false; } wakeX11Thread(); char signal; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return false; } if (signal) { return false; } return true; } bool Core::remoteXUngrabKey(const X11Shortcut &X11shortcut) { size_t X11Operation = X11_OP_XUngrabKey; if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11Operation, sizeof(X11Operation))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return false; } if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11shortcut.first, sizeof(X11shortcut.first))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return false; } if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11shortcut.second, sizeof(X11shortcut.second))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return false; } wakeX11Thread(); char signal; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return false; } if (signal) { return false; } return true; } QString Core::grabOrReuseKey(const X11Shortcut &X11shortcut, const QString &shortcut) { IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(shortcut); if ((idsByShortcut != mIdsByShortcut.end()) && (!idsByShortcut.value().isEmpty())) { return shortcut; } if (!remoteXGrabKey(X11shortcut)) { log(LOG_WARNING, "Cannot grab shortcut '%s'", qPrintable(shortcut)); return QString(); } return shortcut; } Core::X11Shortcut Core::ShortcutToX11(const QString &shortcut) { X11Shortcut result(0, 0); QStringList parts = shortcut.split('+'); size_t m = parts.size(); for (size_t i = 0; i < m - 1; ++i) { if (parts[i] == "Shift") { result.second |= ShiftMask; } else if (parts[i] == "Control") { result.second |= ControlMask; } else if (parts[i] == "Alt") { result.second |= AltMask; } else if (parts[i] == "Meta") { result.second |= MetaMask; } else if (parts[i] == "Level3") { result.second |= Level3Mask; } else if (parts[i] == "Level5") { result.second |= Level5Mask; } else { throw false; } } if (m) { KeyCode keyCode = remoteStringToKeycode(parts[m - 1]); if (!keyCode) { throw false; } result.first = keyCode; } return result; } QString Core::X11ToShortcut(const X11Shortcut &X11shortcut) { QString result; if (X11shortcut.second & ShiftMask) { result += "Shift+"; } if (X11shortcut.second & ControlMask) { result += "Control+"; } if (X11shortcut.second & AltMask) { result += "Alt+"; } if (X11shortcut.second & MetaMask) { result += "Meta+"; } if (X11shortcut.second & Level3Mask) { result += "Level3+"; } if (X11shortcut.second & Level5Mask) { result += "Level5+"; } QString key = remoteKeycodeToString(X11shortcut.first); if (key.isEmpty()) { throw false; } result += key; return result; } QString Core::checkShortcut(const QString &shortcut, X11Shortcut &X11shortcut) { if (shortcut.isEmpty()) return QString(); QString usedShortcut; try { X11shortcut = ShortcutToX11(shortcut); } catch (bool) { log(LOG_WARNING, "Cannot extract keycode and modifiers from shortcut '%s'", qPrintable(shortcut)); return QString(); } try { ShortcutByX11::const_iterator shortcutByX11 = mShortcutByX11.find(X11shortcut); if (shortcutByX11 != mShortcutByX11.end()) { usedShortcut = shortcutByX11.value(); } else { usedShortcut = X11ToShortcut(X11shortcut); mShortcutByX11[X11shortcut] = usedShortcut; } } catch (bool) { log(LOG_WARNING, "Cannot get back shortcut '%s'", qPrintable(shortcut)); return QString(); } if (shortcut != usedShortcut) { log(LOG_INFO, "Using shortcut '%s' instead of '%s'", qPrintable(usedShortcut), qPrintable(shortcut)); } X11ByShortcut::const_iterator x11ByShortcut = mX11ByShortcut.find(usedShortcut); if (x11ByShortcut == mX11ByShortcut.end()) { mX11ByShortcut[usedShortcut] = X11shortcut; } return usedShortcut; } QPair Core::addOrRegisterClientAction(const QString &shortcut, const QDBusObjectPath &path, const QString &description, const QString &sender) { X11Shortcut X11shortcut; QString newShortcut = checkShortcut(shortcut, X11shortcut); // if (newShortcut.isEmpty()) // { // return qMakePair(QString(), 0ull); // } IdByClientPath::iterator idByNativeClient = mIdByClientPath.find(path); if (idByNativeClient != mIdByClientPath.end()) { qulonglong id = idByNativeClient.value(); ShortcutAndAction &shortcutAndAction = mShortcutAndActionById[id]; if (newShortcut != shortcutAndAction.first) { mShortcutAndActionById[id].first = newShortcut; } if (!newShortcut.isEmpty()) { newShortcut = grabOrReuseKey(X11shortcut, newShortcut); mIdsByShortcut[newShortcut].insert(id); } dynamic_cast(shortcutAndAction.second)->appeared(QDBusConnection::sessionBus(), sender); return qMakePair(newShortcut, id); } qulonglong id = ++mLastId; if (!sender.isEmpty() && !newShortcut.isEmpty()) { newShortcut = grabOrReuseKey(X11shortcut, newShortcut); mIdsByShortcut[newShortcut].insert(id); } mIdByClientPath[path] = id; ClientAction *clientAction = sender.isEmpty() ? new ClientAction(this, path, description) : new ClientAction(this, QDBusConnection::sessionBus(), sender, path, description); mShortcutAndActionById[id] = qMakePair(newShortcut, clientAction); log(LOG_INFO, "addClientAction shortcut:'%s' id:%llu", qPrintable(newShortcut), id); return qMakePair(newShortcut, id); } void Core::addClientAction(QPair &result, const QString &shortcut, const QDBusObjectPath &path, const QString &description, const QString &sender) { log(LOG_INFO, "addClientAction shortcut:'%s' path:'%s' description:'%s' sender:'%s'", qPrintable(shortcut), qPrintable(path.path()), qPrintable(description), qPrintable(sender)); QMutexLocker lock(&mDataMutex); SenderByClientPath::iterator senderByClientPath = mSenderByClientPath.find(path); if (senderByClientPath != mSenderByClientPath.end()) { log(LOG_WARNING, "Action already registered for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = qMakePair(QString(), 0ull); return; } QString useShortcut = shortcut; if (shortcut.isEmpty()) { IdByClientPath::ConstIterator idByClientPath = mIdByClientPath.find(path); if (idByClientPath != mIdByClientPath.constEnd()) { useShortcut = mShortcutAndActionById[idByClientPath.value()].first;; } } mSenderByClientPath[path] = sender; auto it = mClientPathsBySender.find(sender); if (mClientPathsBySender.end() == it) { mServiceWatcher->addWatchedService(sender); it = mClientPathsBySender.insert(sender, {}); } it->insert(path); result = addOrRegisterClientAction(useShortcut, path, description, sender); saveConfig(); mDaemonAdaptor->emit_clientActionSenderChanged(result.second, sender); mDaemonAdaptor->emit_actionAdded(result.second); } qulonglong Core::registerClientAction(const QString &shortcut, const QDBusObjectPath &path, const QString &description) { log(LOG_INFO, "registerClientAction shortcut:'%s' path:'%s' description:'%s'", qPrintable(shortcut), qPrintable(path.path()), qPrintable(description)); QMutexLocker lock(&mDataMutex); return addOrRegisterClientAction(shortcut, path, description, QString()).second; } void Core::addMethodAction(QPair &result, const QString &shortcut, const QString &service, const QDBusObjectPath &path, const QString &interface, const QString &method, const QString &description) { log(LOG_INFO, "addMethodAction shortcut:'%s' service:'%s' path:'%s' interface:'%s' method:'%s' description:'%s'", qPrintable(shortcut), qPrintable(service), qPrintable(path.path()), qPrintable(interface), qPrintable(method), qPrintable(description)); QMutexLocker lock(&mDataMutex); X11Shortcut X11shortcut; QString newShortcut = checkShortcut(shortcut, X11shortcut); if (newShortcut.isEmpty()) { result = qMakePair(QString(), 0ull); return; } newShortcut = grabOrReuseKey(X11shortcut, newShortcut); if (newShortcut.isEmpty()) { result = qMakePair(QString(), 0ull); return; } qulonglong id = ++mLastId; mIdsByShortcut[newShortcut].insert(id); mShortcutAndActionById[id] = qMakePair(newShortcut, new MethodAction(this, QDBusConnection::sessionBus(), service, path, interface, method, description)); log(LOG_INFO, "addMethodAction shortcut:'%s' id:%llu", qPrintable(newShortcut), id); saveConfig(); result = qMakePair(newShortcut, id); } qulonglong Core::registerMethodAction(const QString &shortcut, const QString &service, const QDBusObjectPath &path, const QString &interface, const QString &method, const QString &description) { QPair result; addMethodAction(result, shortcut, service, path, interface, method, description); return result.second; } void Core::addCommandAction(QPair &result, const QString &shortcut, const QString &command, const QStringList &arguments, const QString &description) { log(LOG_INFO, "addCommandAction shortcut:'%s' command:'%s' arguments:'%s' description:'%s'", qPrintable(shortcut), qPrintable(command), qPrintable(joinToString(arguments, "", "' '", "")), qPrintable(description)); QMutexLocker lock(&mDataMutex); X11Shortcut X11shortcut; QString newShortcut = checkShortcut(shortcut, X11shortcut); if (newShortcut.isEmpty()) { result = qMakePair(QString(), 0ull); return; } newShortcut = grabOrReuseKey(X11shortcut, newShortcut); if (newShortcut.isEmpty()) { result = qMakePair(QString(), 0ull); return; } qulonglong id = ++mLastId; mIdsByShortcut[newShortcut].insert(id); mShortcutAndActionById[id] = qMakePair(newShortcut, new CommandAction(this, command, arguments, description)); log(LOG_INFO, "addCommandAction shortcut:'%s' id:%llu", qPrintable(newShortcut), id); saveConfig(); result = qMakePair(newShortcut, id); } qulonglong Core::registerCommandAction(const QString &shortcut, const QString &command, const QStringList &arguments, const QString &description) { QPair result; addCommandAction(result, shortcut, command, arguments, description); return result.second; } void Core::modifyClientAction(qulonglong &result, const QDBusObjectPath &path, const QString &description, const QString &sender) { log(LOG_INFO, "modifyClientAction path:'%s' description:'%s' sender:'%s'", qPrintable(path.path()), qPrintable(description), qPrintable(sender)); QMutexLocker lock(&mDataMutex); IdByClientPath::iterator idByNativeClient = mIdByClientPath.find(path); if (idByNativeClient == mIdByClientPath.end()) { log(LOG_WARNING, "No action registered for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = 0ull; return; } SenderByClientPath::Iterator senderByClientPath = mSenderByClientPath.find(path); if (senderByClientPath == mSenderByClientPath.end()) { log(LOG_WARNING, "No action activated for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = 0ull; return; } if (senderByClientPath.value() != sender) { log(LOG_WARNING, "Sender mismatch: caller: %s owner: %s", qPrintable(senderByClientPath.value()), qPrintable(sender)); result = 0ull; return; } qulonglong id = idByNativeClient.value(); mShortcutAndActionById[id].second->setDescription(description); saveConfig(); result = id; mDaemonAdaptor->emit_actionModified(result); } void Core::modifyActionDescription(bool &result, const qulonglong &id, const QString &description) { log(LOG_INFO, "modifyActionDescription id:%llu description:'%s'", id, qPrintable(description)); QMutexLocker lock(&mDataMutex); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); result = false; return; } BaseAction *action = shortcutAndActionById.value().second; if ((strcmp(action->type(), MethodAction::id())) && (strcmp(action->type(), CommandAction::id()))) { log(LOG_WARNING, "modifyActionDescription attempts to modify action of type '%s'", action->type()); result = false; return; } action->setDescription(description); saveConfig(); result = true; } void Core::modifyMethodAction(bool &result, const qulonglong &id, const QString &service, const QDBusObjectPath &path, const QString &interface, const QString &method, const QString &description) { log(LOG_INFO, "modifyMethodAction id:%llu service:'%s' path:'%s' interface:'%s' method:'%s' description:'%s'", id, qPrintable(service), qPrintable(path.path()), qPrintable(interface), qPrintable(method), qPrintable(description)); QMutexLocker lock(&mDataMutex); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); result = false; return; } BaseAction *action = shortcutAndActionById.value().second; if (strcmp(action->type(), MethodAction::id())) { log(LOG_WARNING, "modifyMethodAction attempts to modify action of type '%s'", action->type()); result = false; return; } delete action; shortcutAndActionById.value().second = new MethodAction(this, QDBusConnection::sessionBus(), service, path, interface, method, description); saveConfig(); result = true; } void Core::modifyCommandAction(bool &result, const qulonglong &id, const QString &command, const QStringList &arguments, const QString &description) { log(LOG_INFO, "modifyCommandAction id:%llu command:'%s' arguments:'%s' description:'%s'", id, qPrintable(command), qPrintable(joinToString(arguments, "", "' '", "")), qPrintable(description)); QMutexLocker lock(&mDataMutex); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); result = false; return; } BaseAction *action = shortcutAndActionById.value().second; if (strcmp(action->type(), CommandAction::id())) { log(LOG_WARNING, "modifyMethodAction attempts to modify action of type '%s'", action->type()); result = false; return; } delete action; shortcutAndActionById.value().second = new CommandAction(this, command, arguments, description); saveConfig(); result = true; } void Core::enableClientAction(bool &result, const QDBusObjectPath &path, bool enabled, const QString &sender) { log(LOG_INFO, "enableClientAction path:'%s' enabled:%s sender:'%s'", qPrintable(path.path()), enabled ? " true" : "false", qPrintable(sender)); QMutexLocker lock(&mDataMutex); IdByClientPath::iterator idByNativeClient = mIdByClientPath.find(path); if (idByNativeClient == mIdByClientPath.end()) { log(LOG_WARNING, "No action registered for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = false; return; } SenderByClientPath::Iterator senderByClientPath = mSenderByClientPath.find(path); if (senderByClientPath == mSenderByClientPath.end()) { log(LOG_WARNING, "No action activated for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = false; return; } if (senderByClientPath.value() != sender) { log(LOG_WARNING, "Sender mismatch: caller: %s owner: %s", qPrintable(senderByClientPath.value()), qPrintable(sender)); result = false; return; } qulonglong id = idByNativeClient.value(); enableActionNonGuarded(id, enabled); saveConfig(); result = true; mDaemonAdaptor->emit_actionEnabled(id, result); } void Core::isClientActionEnabled(bool &enabled, const QDBusObjectPath &path, const QString &sender) { log(LOG_INFO, "isClientActionEnabled path:'%s' sender:'%s'", qPrintable(path.path()), qPrintable(sender)); enabled = false; QMutexLocker lock(&mDataMutex); IdByClientPath::iterator idByNativeClient = mIdByClientPath.find(path); if (idByNativeClient == mIdByClientPath.end()) { log(LOG_WARNING, "No action registered for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); return; } SenderByClientPath::Iterator senderByClientPath = mSenderByClientPath.find(path); if (senderByClientPath == mSenderByClientPath.end()) { log(LOG_WARNING, "No action activated for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); return; } if (senderByClientPath.value() != sender) { log(LOG_WARNING, "Sender mismatch: caller: %s owner: %s", qPrintable(senderByClientPath.value()), qPrintable(sender)); return; } enabled = mShortcutAndActionById[idByNativeClient.value()].second->isEnabled(); } void Core::enableAction(bool &result, qulonglong id, bool enabled) { QMutexLocker lock(&mDataMutex); result = enableActionNonGuarded(id, enabled); } bool Core::enableActionNonGuarded(qulonglong id, bool enabled) { log(LOG_INFO, "enableAction id:%llu enabled:%s", id, enabled ? "true" : " false"); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); return false; } BaseAction * action = shortcutAndActionById.value().second; QString const & shortcut = shortcutAndActionById.value().first; if (action->isEnabled() != enabled) { shortcutAndActionById.value().second->setEnabled(enabled); if (enabled) { IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(shortcut); if (idsByShortcut != mIdsByShortcut.end()) { if (idsByShortcut.value().isEmpty()) { if (!remoteXGrabKey(mX11ByShortcut[shortcut])) { log(LOG_WARNING, "Cannot grab shortcut '%s'", qPrintable(shortcut)); } } } idsByShortcut = mDisabledIdsByShortcut.find(shortcut); if (idsByShortcut != mDisabledIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mDisabledIdsByShortcut.erase(idsByShortcut); } } mIdsByShortcut[shortcut].insert(id); } else { IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(shortcut); if (idsByShortcut != mIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mIdsByShortcut.erase(idsByShortcut); if (!remoteXUngrabKey(mX11ByShortcut[shortcut])) { log(LOG_WARNING, "Cannot ungrab shortcut '%s'", qPrintable(shortcut)); } } } mDisabledIdsByShortcut[shortcut].insert(id); } saveConfig(); } return true; } void Core::isActionEnabled(bool &enabled, qulonglong id) { log(LOG_INFO, "isActionEnabled id:%llu", id); enabled = false; QMutexLocker lock(&mDataMutex); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); return; } enabled = shortcutAndActionById.value().second->isEnabled(); } void Core::getClientActionSender(QString &sender, qulonglong id) { log(LOG_INFO, "getClientActionSender id:'%llu'", id); sender.clear(); QMutexLocker lock(&mDataMutex); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); return; } BaseAction *action = shortcutAndActionById.value().second; if (!strcmp(action->type(), ClientAction::id())) { sender = dynamic_cast(action)->service(); } } void Core::changeClientActionShortcut(QPair &result, const QDBusObjectPath &path, const QString &shortcut, const QString &sender) { log(LOG_INFO, "changeClientActionShortcut path:'%s' shortcut:'%s' sender:'%s'", qPrintable(path.path()), qPrintable(shortcut), qPrintable(sender)); if (shortcut.isEmpty()) { result = qMakePair(QString(), 0ull); return; } QMutexLocker lock(&mDataMutex); IdByClientPath::iterator idByNativeClient = mIdByClientPath.find(path); if (idByNativeClient == mIdByClientPath.end()) { log(LOG_WARNING, "No action registered for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = qMakePair(QString(), 0ull); return; } SenderByClientPath::Iterator senderByClientPath = mSenderByClientPath.find(path); if (senderByClientPath == mSenderByClientPath.end()) { log(LOG_WARNING, "No action activated for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = qMakePair(QString(), 0ull); return; } if (senderByClientPath.value() != sender) { log(LOG_WARNING, "Sender mismatch: caller: %s owner: %s", qPrintable(senderByClientPath.value()), qPrintable(sender)); result = qMakePair(QString(), 0ull); return; } qulonglong id = idByNativeClient.value(); X11Shortcut X11shortcut; QString newShortcut = checkShortcut(shortcut, X11shortcut); if (newShortcut.isEmpty()) { result = qMakePair(QString(), id); return; } ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); QString oldShortcut = shortcutAndActionById.value().first; if (oldShortcut != newShortcut) { BaseAction const * const action = shortcutAndActionById.value().second; if (action->isEnabled()) { newShortcut = grabOrReuseKey(X11shortcut, newShortcut); if (newShortcut.isEmpty()) { result = qMakePair(QString(), id); return; } IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(oldShortcut); if (idsByShortcut != mIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mIdsByShortcut.erase(idsByShortcut); if (!remoteXUngrabKey(mX11ByShortcut[oldShortcut])) { log(LOG_WARNING, "Cannot ungrab shortcut '%s'", qPrintable(shortcut)); } } } mIdsByShortcut[newShortcut].insert(id); } else { IdsByShortcut::iterator idsByShortcut = mDisabledIdsByShortcut.find(oldShortcut); if (idsByShortcut != mDisabledIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mDisabledIdsByShortcut.erase(idsByShortcut); } } mDisabledIdsByShortcut[newShortcut].insert(id); } shortcutAndActionById.value().first = newShortcut; } saveConfig(); dynamic_cast(shortcutAndActionById.value().second)->shortcutChanged(oldShortcut, newShortcut); mDaemonAdaptor->emit_actionShortcutChanged(id); result = qMakePair(newShortcut, id); } void Core::changeShortcut(QString &result, const qulonglong &id, const QString &shortcut) { log(LOG_INFO, "changeShortcut id:%llu shortcut:'%s'", id, qPrintable(shortcut)); if (shortcut.isEmpty()) { result = QString(); return; } QMutexLocker lock(&mDataMutex); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); result = QString(); return; } X11Shortcut X11shortcut; QString newShortcut = checkShortcut(shortcut, X11shortcut); if (newShortcut.isEmpty()) { result = QString(); return; } QString oldShortcut = shortcutAndActionById.value().first; if (oldShortcut != newShortcut) { BaseAction const * const action = shortcutAndActionById.value().second; if (action->isEnabled()) { newShortcut = grabOrReuseKey(X11shortcut, newShortcut); if (newShortcut.isEmpty()) { result = QString(); return; } IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(oldShortcut); if (idsByShortcut != mIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mIdsByShortcut.erase(idsByShortcut); if (!remoteXUngrabKey(mX11ByShortcut[oldShortcut])) { log(LOG_WARNING, "Cannot ungrab shortcut '%s'", qPrintable(shortcut)); } } } mIdsByShortcut[newShortcut].insert(id); } else { IdsByShortcut::iterator idsByShortcut = mDisabledIdsByShortcut.find(oldShortcut); if (idsByShortcut != mDisabledIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mDisabledIdsByShortcut.erase(idsByShortcut); } } mDisabledIdsByShortcut[newShortcut].insert(id); } shortcutAndActionById.value().first = newShortcut; if (!strcmp(shortcutAndActionById.value().second->type(), ClientAction::id())) { dynamic_cast(shortcutAndActionById.value().second)->shortcutChanged(oldShortcut, newShortcut); } } saveConfig(); result = newShortcut; } void Core::swapActions(bool &result, const qulonglong &id1, const qulonglong &id2) { log(LOG_INFO, "swapActions id1:%llu id2:%llu", id1, id2); QMutexLocker lock(&mDataMutex); ShortcutAndActionById::iterator shortcutAndActionById1 = mShortcutAndActionById.find(id1); if (shortcutAndActionById1 == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id1); result = false; return; } ShortcutAndActionById::iterator shortcutAndActionById2 = mShortcutAndActionById.find(id2); if (shortcutAndActionById2 == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id2); result = false; return; } if (shortcutAndActionById1.value().first != shortcutAndActionById2.value().first) { log(LOG_WARNING, "swapActions attempts to swap action assigned to different shortcuts"); result = false; return; } std::swap(shortcutAndActionById1.value().second, shortcutAndActionById2.value().second); saveConfig(); result = true; } void Core::removeClientAction(bool &result, const QDBusObjectPath &path, const QString &sender) { log(LOG_INFO, "removeClientAction path:'%s' sender:'%s'", qPrintable(path.path()), qPrintable(sender)); QMutexLocker lock(&mDataMutex); IdByClientPath::iterator idByNativeClient = mIdByClientPath.find(path); if (idByNativeClient == mIdByClientPath.end()) { log(LOG_WARNING, "No action registered for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = false; return; } SenderByClientPath::Iterator senderByClientPath = mSenderByClientPath.find(path); if (senderByClientPath == mSenderByClientPath.end()) { log(LOG_WARNING, "No action activated for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = false; return; } if (senderByClientPath.value() != sender) { log(LOG_WARNING, "Sender mismatch: caller: %s owner: %s", qPrintable(senderByClientPath.value()), qPrintable(sender)); result = false; return; } qulonglong id = idByNativeClient.value(); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); QString shortcut = shortcutAndActionById.value().first; X11Shortcut X11shortcut = mX11ByShortcut[shortcut]; BaseAction * action = shortcutAndActionById.value().second; const bool enabled = action->isEnabled(); delete action; mShortcutAndActionById.erase(shortcutAndActionById); mIdByClientPath.remove(path); if (enabled) { IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(shortcut); if (idsByShortcut != mIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mIdsByShortcut.erase(idsByShortcut); if (!remoteXUngrabKey(X11shortcut)) { log(LOG_WARNING, "Cannot ungrab shortcut '%s'", qPrintable(shortcut)); } } } } else { IdsByShortcut::iterator idsByShortcut = mDisabledIdsByShortcut.find(shortcut); if (idsByShortcut != mDisabledIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mDisabledIdsByShortcut.erase(idsByShortcut); } } } mSenderByClientPath.remove(path); mClientPathsBySender[sender].remove(path); if (mClientPathsBySender[sender].isEmpty()) { mClientPathsBySender.remove(sender); mServiceWatcher->removeWatchedService(sender); } saveConfig(); result = true; mDaemonAdaptor->emit_actionRemoved(id); } void Core::removeAction(bool &result, const qulonglong &id) { log(LOG_INFO, "removeAction id:%llu", id); QMutexLocker lock(&mDataMutex); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); result = false; return; } BaseAction *action = shortcutAndActionById.value().second; bool isClientAction = !strcmp(action->type(), ClientAction::id()); if (isClientAction) { ClientAction *clientAction = dynamic_cast(action); if (clientAction->isPresent()) { log(LOG_WARNING, "Cannot remove active client action by id"); result = false; return; } else { const QDBusObjectPath &path = clientAction->path(); SenderByClientPath::Iterator senderByClientPath = mSenderByClientPath.find(path); if (senderByClientPath != mSenderByClientPath.end()) { log(LOG_WARNING, "Action is currently registered for '%s'", qPrintable(path.path())); result = false; return; } } } QString shortcut = shortcutAndActionById.value().first; X11Shortcut X11shortcut = mX11ByShortcut[shortcut]; const bool enabled = action->isEnabled(); delete action; mShortcutAndActionById.erase(shortcutAndActionById); if (enabled) { IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(shortcut); if (idsByShortcut != mIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mIdsByShortcut.erase(idsByShortcut); if (enabled && !remoteXUngrabKey(X11shortcut)) { log(LOG_WARNING, "Cannot ungrab shortcut '%s'", qPrintable(shortcut)); } } } } else { IdsByShortcut::iterator idsByShortcut = mDisabledIdsByShortcut.find(shortcut); if (idsByShortcut != mDisabledIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mDisabledIdsByShortcut.erase(idsByShortcut); } } } saveConfig(); result = true; } void Core::deactivateClientAction(bool &result, const QDBusObjectPath &path, const QString &sender) { log(LOG_INFO, "deactivateClientAction path:'%s' sender:'%s'", qPrintable(path.path()), qPrintable(sender)); QMutexLocker lock(&mDataMutex); IdByClientPath::iterator idByNativeClient = mIdByClientPath.find(path); if (idByNativeClient == mIdByClientPath.end()) { log(LOG_WARNING, "No action registered for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = false; return; } SenderByClientPath::Iterator senderByClientPath = mSenderByClientPath.find(path); if (senderByClientPath == mSenderByClientPath.end()) { log(LOG_WARNING, "No action activated for '%s' (sender: %s)", qPrintable(path.path()), qPrintable(sender)); result = false; return; } if (senderByClientPath.value() != sender) { log(LOG_WARNING, "Sender mismatch: caller: %s owner: %s", qPrintable(senderByClientPath.value()), qPrintable(sender)); result = false; return; } qulonglong id = idByNativeClient.value(); ShortcutAndActionById::iterator shortcutAndActionById = mShortcutAndActionById.find(id); QString shortcut = shortcutAndActionById.value().first; ClientAction * const action = dynamic_cast(shortcutAndActionById.value().second); action->disappeared(); if (action->isEnabled()) { IdsByShortcut::iterator idsByShortcut = mIdsByShortcut.find(shortcut); if (idsByShortcut != mIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mIdsByShortcut.erase(idsByShortcut); if (!remoteXUngrabKey(mX11ByShortcut[shortcut])) { log(LOG_WARNING, "Cannot ungrab shortcut '%s'", qPrintable(shortcut)); } } } } else { IdsByShortcut::iterator idsByShortcut = mDisabledIdsByShortcut.find(shortcut); if (idsByShortcut != mDisabledIdsByShortcut.end()) { idsByShortcut.value().remove(id); if (idsByShortcut.value().isEmpty()) { mDisabledIdsByShortcut.erase(idsByShortcut); } } } mSenderByClientPath.remove(path); mClientPathsBySender[sender].remove(path); if (mClientPathsBySender[sender].isEmpty()) { mClientPathsBySender.remove(sender); mServiceWatcher->removeWatchedService(sender); } result = true; mDaemonAdaptor->emit_clientActionSenderChanged(id, QString()); } void Core::setMultipleActionsBehaviour(const MultipleActionsBehaviour &behaviour) { QMutexLocker lock(&mDataMutex); mMultipleActionsBehaviour = behaviour; saveConfig(); } void Core::getMultipleActionsBehaviour(MultipleActionsBehaviour &result) const { QMutexLocker lock(&mDataMutex); result = mMultipleActionsBehaviour; } void Core::getAllActionIds(QList &result) const { QMutexLocker lock(&mDataMutex); result.clear(); result.reserve(mShortcutAndActionById.size()); ShortcutAndActionById::const_iterator lastShortcutAndActionById = mShortcutAndActionById.end(); for (ShortcutAndActionById::const_iterator shortcutAndActionById = mShortcutAndActionById.begin(); shortcutAndActionById != lastShortcutAndActionById; ++shortcutAndActionById) { result.push_back(shortcutAndActionById.key()); } } GeneralActionInfo Core::actionInfo(const ShortcutAndAction &shortcutAndAction) const { GeneralActionInfo result; result.shortcut = shortcutAndAction.first; const BaseAction *action = shortcutAndAction.second; result.description = action->description(); result.enabled = action->isEnabled(); result.type = action->type(); if (result.type == ClientAction::id()) { const ClientAction *clientAction = dynamic_cast(action); result.info = clientAction->path().path(); } else if (result.type == MethodAction::id()) { const MethodAction *methodAction = dynamic_cast(action); result.info = methodAction->service() + " " + methodAction->path().path() + " " + methodAction->interface() + " " + methodAction->method(); } else if (result.type == CommandAction::id()) { const CommandAction *commandAction = dynamic_cast(action); result.info = joinCommandLine(commandAction->command(), commandAction->args()); } return result; } void Core::getActionById(QPair &result, const qulonglong &id) const { log(LOG_INFO, "getActionById id:%llu", id); QMutexLocker lock(&mDataMutex); ShortcutAndActionById::const_iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); result = qMakePair(false, GeneralActionInfo()); return; } result = qMakePair(true, actionInfo(shortcutAndActionById.value())); } void Core::getAllActions(QMap &result) const { QMutexLocker lock(&mDataMutex); result.clear(); ShortcutAndActionById::const_iterator lastShortcutAndActionById = mShortcutAndActionById.end(); for (ShortcutAndActionById::const_iterator shortcutAndActionById = mShortcutAndActionById.begin(); shortcutAndActionById != lastShortcutAndActionById; ++shortcutAndActionById) { result[shortcutAndActionById.key()] = actionInfo(shortcutAndActionById.value()); } } void Core::getClientActionInfoById(QPair &result, const qulonglong &id) const { log(LOG_INFO, "getClientActionInfoById id:%llu", id); ClientActionInfo info; QMutexLocker lock(&mDataMutex); ShortcutAndActionById::const_iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); result = qMakePair(false, info); return; } const BaseAction *action = shortcutAndActionById.value().second; if (strcmp(action->type(), ClientAction::id())) { log(LOG_WARNING, "getClientActionInfoById attempts to request action of type '%s'", action->type()); result = qMakePair(false, info); return; } info.shortcut = shortcutAndActionById.value().first; info.description = action->description(); info.enabled = action->isEnabled(); const ClientAction *clientAction = dynamic_cast(action); info.path = clientAction->path(); result = qMakePair(true, info); } void Core::getMethodActionInfoById(QPair &result, const qulonglong &id) const { log(LOG_INFO, "getMethodActionInfoById id:%llu", id); MethodActionInfo info; QMutexLocker lock(&mDataMutex); ShortcutAndActionById::const_iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); result = qMakePair(false, info); return; } const BaseAction *action = shortcutAndActionById.value().second; if (strcmp(action->type(), MethodAction::id())) { log(LOG_WARNING, "getMethodActionInfoById attempts to request action of type '%s'", action->type()); result = qMakePair(false, info); return; } info.shortcut = shortcutAndActionById.value().first; info.description = action->description(); info.enabled = action->isEnabled(); const MethodAction *methodAction = dynamic_cast(action); info.service = methodAction->service(); info.path = methodAction->path(); info.interface = methodAction->interface(); info.method = methodAction->method(); result = qMakePair(true, info); } void Core::getCommandActionInfoById(QPair &result, const qulonglong &id) const { log(LOG_INFO, "getCommandActionInfoById id:%llu", id); CommandActionInfo info; QMutexLocker lock(&mDataMutex); ShortcutAndActionById::const_iterator shortcutAndActionById = mShortcutAndActionById.find(id); if (shortcutAndActionById == mShortcutAndActionById.end()) { log(LOG_WARNING, "No action registered with id #%llu", id); result = qMakePair(false, info); return; } const BaseAction *action = shortcutAndActionById.value().second; if (strcmp(action->type(), CommandAction::id())) { log(LOG_WARNING, "getCommandActionInfoById attempts to request action of type '%s'", action->type()); result = qMakePair(false, info); return; } info.shortcut = shortcutAndActionById.value().first; info.description = action->description(); info.enabled = action->isEnabled(); const CommandAction *commandAction = dynamic_cast(action); info.command = commandAction->command(); info.arguments = commandAction->args(); result = qMakePair(true, info); } void Core::grabShortcut(const uint &timeout, QString &/*shortcut*/, bool &failed, bool &cancelled, bool &timedout, const QDBusMessage &message) { log(LOG_INFO, "grabShortcut timeout:%u", timeout); failed = false; cancelled = false; timedout = false; QMutexLocker lock(&mDataMutex); if (mGrabbingShortcut) { failed = true; log(LOG_DEBUG, "grabShortcut failed: already grabbing"); return; } if ((timeout > 60000) || (timeout < 1000)) { timedout = true; log(LOG_DEBUG, "grabShortcut wrong timedout"); return; } size_t X11Operation = X11_OP_XGrabKeyboard; if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11Operation, sizeof(X11Operation))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return; } wakeX11Thread(); int x11result; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &x11result, sizeof(x11result))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return; } if (x11result) { failed = true; log(LOG_DEBUG, "grabShortcut failed: grab failed"); return; } mShortcutGrabRequested = true; mShortcutGrabTimeout->setInterval(timeout); mShortcutGrabTimeout->start(); message.setDelayedReply(true); mShortcutGrabRequest = message.createReply(); log(LOG_DEBUG, "grabShortcut delayed"); } void Core::shortcutGrabbed() { log(LOG_INFO, "shortcutGrabbed"); QString shortcut; bool failed = false; bool cancelled = false; bool timedout = false; QMutexLocker lock(&mDataMutex); mShortcutGrabTimeout->stop(); if (!mShortcutGrabRequested) { return; } if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &cancelled, sizeof(cancelled))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return; } if (!cancelled) { size_t length; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &length, sizeof(length))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return; } if (length) { QScopedArrayPointer str(new char[length + 1]); str[length] = '\0'; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], str.data(), length)) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return; } shortcut = str.data(); } } if (cancelled) { log(LOG_DEBUG, "grabShortcut: cancelled"); } else { log(LOG_DEBUG, "grabShortcut: shortcut:%s", qPrintable(shortcut)); } mShortcutGrabRequest << shortcut << failed << cancelled << timedout; QDBusConnection::sessionBus().send(mShortcutGrabRequest); mShortcutGrabRequested = false; } void Core::shortcutGrabTimedout() { log(LOG_INFO, "shortcutGrabTimedout"); QString shortcut; bool failed = false; bool cancelled = false; bool timedout = true; QMutexLocker lock(&mDataMutex); if (!mShortcutGrabRequested) { return; } size_t X11Operation = X11_OP_XUngrabKeyboard; if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11Operation, sizeof(X11Operation))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return; } wakeX11Thread(); char signal; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return; } if (signal) { failed = true; } log(LOG_DEBUG, "shortcutGrabTimedout: failed:%s", failed ? "true" : "false"); mShortcutGrabRequest << shortcut << failed << cancelled << timedout; QDBusConnection::sessionBus().send(mShortcutGrabRequest); mShortcutGrabRequested = false; } void Core::cancelShortcutGrab() { log(LOG_INFO, "cancelShortcutGrab"); QString shortcut; bool failed = false; bool cancelled = true; bool timedout = false; QMutexLocker lock(&mDataMutex); if (!mGrabbingShortcut) { log(LOG_DEBUG, "cancelShortcutGrab failed: not grabbing"); return; } mShortcutGrabTimeout->stop(); if (!mShortcutGrabRequested) { return; } size_t X11Operation = X11_OP_XUngrabKeyboard; if (error_t error = writeAll(mX11RequestPipe[STDOUT_FILENO], &X11Operation, sizeof(X11Operation))) { log(LOG_CRIT, "Cannot write to X11 request pipe: %s", strerror(error)); qApp->quit(); return; } wakeX11Thread(); char signal; if (error_t error = readAll(mX11ResponsePipe[STDIN_FILENO], &signal, sizeof(signal))) { log(LOG_CRIT, "Cannot read from X11 response pipe: %s", strerror(error)); qApp->quit(); return; } if (signal) { failed = true; } log(LOG_DEBUG, "cancelShortcutGrab: failed:%s", failed ? "true" : "false"); mShortcutGrabRequest << shortcut << failed << cancelled << timedout; QDBusConnection::sessionBus().send(mShortcutGrabRequest); mShortcutGrabRequested = false; }