diff --git a/src/kglobalaccel.cpp b/src/kglobalaccel.cpp index 335a4d3..2056c53 100644 --- a/src/kglobalaccel.cpp +++ b/src/kglobalaccel.cpp @@ -1,726 +1,726 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Ellis Whitehead Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Andreas Hartmetz Copyright (C) 2008 Michael Jansen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kglobalaccel.h" #include "kglobalaccel_p.h" #include #include #include #include #include #include #include #include #include #include #if HAVE_X11 #include #endif org::kde::kglobalaccel::Component *KGlobalAccelPrivate::getComponent(const QString &componentUnique, bool remember = false) { // Check if we already have this component if (components.contains(componentUnique)) { return components[componentUnique]; } // Connect to the kglobalaccel daemon org::kde::KGlobalAccel kglobalaccel( QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/kglobalaccel"), QDBusConnection::sessionBus()); if (!kglobalaccel.isValid()) { qDebug() << "Failed to connect to the kglobalaccel daemon" << QDBusConnection::sessionBus().lastError(); return nullptr; } // Get the path for our component. We have to do that because // componentUnique is probably not a valid dbus object path QDBusReply reply = kglobalaccel.getComponent(componentUnique); if (!reply.isValid()) { if (reply.error().name() == QLatin1String("org.kde.kglobalaccel.NoSuchComponent")) { // No problem. The component doesn't exists. That's normal return nullptr; } // An unknown error. qDebug() << "Failed to get dbus path for component " << componentUnique << reply.error(); return nullptr; } // Now get the component org::kde::kglobalaccel::Component *component = new org::kde::kglobalaccel::Component( QStringLiteral("org.kde.kglobalaccel"), reply.value().path(), QDBusConnection::sessionBus(), q); // No component no cleaning if (!component->isValid()) { qDebug() << "Failed to get component" << componentUnique << QDBusConnection::sessionBus().lastError(); return nullptr; } if (remember) { // Connect to the signals we are interested in. q->connect(component, SIGNAL(globalShortcutPressed(QString,QString,qlonglong)), SLOT(_k_invokeAction(QString,QString,qlonglong))); components[componentUnique] = component; } return component; } namespace { QString serviceName() { return QStringLiteral("org.kde.kglobalaccel"); } } void KGlobalAccelPrivate::cleanup() { qDeleteAll(components); delete m_iface; m_iface = nullptr; delete m_watcher; m_watcher = nullptr; } KGlobalAccelPrivate::KGlobalAccelPrivate(KGlobalAccel *q) : #ifndef KGLOBALACCEL_NO_DEPRECATED enabled(true), #endif q(q), - m_iface(Q_NULLPTR) + m_iface(nullptr) { m_watcher = new QDBusServiceWatcher(serviceName(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, q); q->connect(m_watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), q, SLOT(_k_serviceOwnerChanged(QString,QString,QString))); } org::kde::KGlobalAccel *KGlobalAccelPrivate::iface() { if (!m_iface) { m_iface = new org::kde::KGlobalAccel(serviceName(), QStringLiteral("/kglobalaccel"), QDBusConnection::sessionBus()); // Make sure kglobalaccel is running. The iface declaration above somehow works anyway. QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); if (bus && !bus->isServiceRegistered(serviceName())) { QDBusReply reply = bus->startService(serviceName()); if (!reply.isValid()) { qCritical() << "Couldn't start kglobalaccel from org.kde.kglobalaccel.service:" << reply.error(); } } q->connect(m_iface, SIGNAL(yourShortcutGotChanged(QStringList,QList)), SLOT(_k_shortcutGotChanged(QStringList,QList))); } return m_iface; } KGlobalAccel::KGlobalAccel() : d(new KGlobalAccelPrivate(this)) { qDBusRegisterMetaType >(); qDBusRegisterMetaType >(); qDBusRegisterMetaType(); qDBusRegisterMetaType >(); } KGlobalAccel::~KGlobalAccel() { delete d; } void KGlobalAccel::activateGlobalShortcutContext( const QString &contextUnique, const QString &contextFriendly, const QString &programName) { Q_UNUSED(contextFriendly); // TODO: provide contextFriendly self()->d->iface()->activateGlobalShortcutContext(programName, contextUnique); } // static bool KGlobalAccel::cleanComponent(const QString &componentUnique) { org::kde::kglobalaccel::Component *component = self()->getComponent(componentUnique); if (!component) { return false; } return component->cleanUp(); } // static bool KGlobalAccel::isComponentActive(const QString &componentUnique) { org::kde::kglobalaccel::Component *component = self()->getComponent(componentUnique); if (!component) { return false; } return component->isActive(); } #ifndef KGLOBALACCEL_NO_DEPRECATED bool KGlobalAccel::isEnabled() const { return d->enabled; } #endif org::kde::kglobalaccel::Component *KGlobalAccel::getComponent(const QString &componentUnique) { return d->getComponent(componentUnique); } #ifndef KGLOBALACCEL_NO_DEPRECATED void KGlobalAccel::setEnabled(bool enabled) { d->enabled = enabled; } #endif class KGlobalAccelSingleton { public: KGlobalAccelSingleton(); KGlobalAccel instance; }; Q_GLOBAL_STATIC(KGlobalAccelSingleton, s_instance) KGlobalAccelSingleton::KGlobalAccelSingleton() { qAddPostRoutine([]() { s_instance->instance.d->cleanup(); }); } KGlobalAccel *KGlobalAccel::self() { return &s_instance()->instance; } bool KGlobalAccelPrivate::doRegister(QAction *action) { if (!action || action->objectName().isEmpty() || action->objectName().startsWith(QLatin1String("unnamed-"))) { qWarning() << "Attempt to set global shortcut for action without objectName()." " Read the setGlobalShortcut() documentation."; return false; } const bool isRegistered = actions.contains(action); if (isRegistered) { return true; } QStringList actionId = makeActionId(action); nameToAction.insertMulti(actionId.at(KGlobalAccel::ActionUnique), action); actions.insert(action); iface()->doRegister(actionId); QObject::connect(action, &QObject::destroyed, [this, action](QObject *) { if (actions.contains(action) && (actionShortcuts.contains(action) || actionDefaultShortcuts.contains(action))) { remove(action, KGlobalAccelPrivate::SetInactive); } }); return true; } void KGlobalAccelPrivate::remove(QAction *action, Removal removal) { if (!action || action->objectName().isEmpty()) { return; } const bool isRegistered = actions.contains(action); if (!isRegistered) { return; } QStringList actionId = makeActionId(action); nameToAction.remove(actionId.at(KGlobalAccel::ActionUnique), action); actions.remove(action); if (removal == UnRegister) { // Complete removal of the shortcut is requested // (forgetGlobalShortcut) iface()->unRegister(actionId); } else { // If the action is a configurationAction wen only remove it from our // internal registry. That happened above. if (!action->property("isConfigurationAction").toBool()) { // If it's a session shortcut unregister it. action->objectName().startsWith(QStringLiteral("_k_session:")) ? iface()->unRegister(actionId) : iface()->setInactive(actionId); } } actionDefaultShortcuts.remove(action); actionShortcuts.remove(action); } void KGlobalAccelPrivate::updateGlobalShortcut(/*const would be better*/QAction* action, ShortcutTypes actionFlags, KGlobalAccel::GlobalShortcutLoading globalFlags) { // No action or no objectname -> Do nothing if (!action || action->objectName().isEmpty()) { return; } QStringList actionId = makeActionId(action); const QList activeShortcut = actionShortcuts.value(action); const QList defaultShortcut = actionDefaultShortcuts.value(action); uint setterFlags = 0; if (globalFlags & NoAutoloading) { setterFlags |= NoAutoloading; } if (actionFlags & ActiveShortcut) { bool isConfigurationAction = action->property("isConfigurationAction").toBool(); uint activeSetterFlags = setterFlags; // setPresent tells kglobalaccel that the shortcut is active if (!isConfigurationAction) { activeSetterFlags |= SetPresent; } // Sets the shortcut, returns the active/real keys const QList result = iface()->setShortcut( actionId, intListFromShortcut(activeShortcut), activeSetterFlags); // Make sure we get informed about changes in the component by kglobalaccel getComponent(componentUniqueForAction(action), true); // Create a shortcut from the result const QList scResult(shortcutFromIntList(result)); if (isConfigurationAction && (globalFlags & NoAutoloading)) { // If this is a configuration action and we have set the shortcut, // inform the real owner of the change. // Note that setForeignShortcut will cause a signal to be sent to applications // even if it did not "see" that the shortcut has changed. This is Good because // at the time of comparison (now) the action *already has* the new shortcut. // We called setShortcut(), remember? // Also note that we will see our own signal so we may not need to call // setActiveGlobalShortcutNoEnable - _k_shortcutGotChanged() does it. // In practice it's probably better to get the change propagated here without // DBus delay as we do below. iface()->setForeignShortcut(actionId, result); } if (scResult != activeShortcut) { // If kglobalaccel returned a shortcut that differs from the one we // sent, use that one. There must have been clashes or some other problem. actionShortcuts.insert(action, scResult); emit q->globalShortcutChanged(action, scResult.isEmpty() ? QKeySequence() : scResult.first()); } } if (actionFlags & DefaultShortcut) { iface()->setShortcut(actionId, intListFromShortcut(defaultShortcut), setterFlags | IsDefault); } } QStringList KGlobalAccelPrivate::makeActionId(const QAction *action) { QStringList ret(componentUniqueForAction(action)); // Component Unique Id ( see actionIdFields ) Q_ASSERT(!ret.at(KGlobalAccel::ComponentUnique).isEmpty()); Q_ASSERT(!action->objectName().isEmpty()); ret.append(action->objectName()); // Action Unique Name ret.append(componentFriendlyForAction(action)); // Component Friendly name const QString actionText = action->text().replace(QLatin1Char('&'), QStringLiteral("")); ret.append(actionText); // Action Friendly Name return ret; } QList KGlobalAccelPrivate::intListFromShortcut(const QList &cut) { QList ret; Q_FOREACH (const QKeySequence &sequence, cut) { ret.append(sequence[0]); } while (!ret.isEmpty() && ret.last() == 0) { ret.removeLast(); } return ret; } QList KGlobalAccelPrivate::shortcutFromIntList(const QList &list) { QList ret; Q_FOREACH (int i, list) { ret.append(i); } return ret; } QString KGlobalAccelPrivate::componentUniqueForAction(const QAction *action) { if (!action->property("componentName").isValid()) { return QCoreApplication::applicationName(); } else { return action->property("componentName").toString(); } } QString KGlobalAccelPrivate::componentFriendlyForAction(const QAction *action) { QString property = action->property("componentDisplayName").toString(); if (!property.isEmpty()) { return property; } if (!QGuiApplication::applicationDisplayName().isEmpty()) { return QGuiApplication::applicationDisplayName(); } return QCoreApplication::applicationName(); } #if HAVE_X11 int _k_timestampCompare(unsigned long time1_, unsigned long time2_) // like strcmp() { quint32 time1 = time1_; quint32 time2 = time2_; if (time1 == time2) { return 0; } return quint32(time1 - time2) < 0x7fffffffU ? 1 : -1; // time1 > time2 -> 1, handle wrapping } #endif void KGlobalAccelPrivate::_k_invokeAction( const QString &componentUnique, const QString &actionUnique, qlonglong timestamp) { QAction *action = nullptr; QList candidates = nameToAction.values(actionUnique); Q_FOREACH (QAction *const a, candidates) { if (componentUniqueForAction(a) == componentUnique) { action = a; } } // We do not trigger if // - there is no action // - the action is not enabled // - the action is an configuration action if (!action || !action->isEnabled() || action->property("isConfigurationAction").toBool()) { return; } #if HAVE_X11 // Update this application's X timestamp if needed. // TODO The 100%-correct solution should probably be handling this action // in the proper place in relation to the X events queue in order to avoid // the possibility of wrong ordering of user events. if (QX11Info::isPlatformX11()) { if (_k_timestampCompare(timestamp, QX11Info::appTime()) > 0) { QX11Info::setAppTime(timestamp); } if (_k_timestampCompare(timestamp, QX11Info::appUserTime()) > 0) { QX11Info::setAppUserTime(timestamp); } } #endif action->setProperty("org.kde.kglobalaccel.activationTimestamp", timestamp); action->trigger(); } void KGlobalAccelPrivate::_k_shortcutGotChanged(const QStringList &actionId, const QList &keys) { QAction *action = nameToAction.value(actionId.at(KGlobalAccel::ActionUnique)); if (!action) { return; } const QList shortcuts = shortcutFromIntList(keys); actionShortcuts.insert(action, shortcuts); emit q->globalShortcutChanged(action, keys.isEmpty() ? QKeySequence() : shortcuts.first()); } void KGlobalAccelPrivate::_k_serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); if (name == QLatin1String("org.kde.kglobalaccel") && !newOwner.isEmpty()) { // kglobalaccel was restarted qDebug() << "detected kglobalaccel restarting, re-registering all shortcut keys"; reRegisterAll(); } } void KGlobalAccelPrivate::reRegisterAll() { //We clear all our data, assume that all data on the other side is clear too, //and register each action as if it just was allowed to have global shortcuts. //If the kded side still has the data it doesn't matter because of the //autoloading mechanism. The worst case I can imagine is that an action's //shortcut was changed but the kded side died before it got the message so //autoloading will now assign an old shortcut to the action. Particularly //picky apps might assert or misbehave. QSet allActions = actions; nameToAction.clear(); actions.clear(); Q_FOREACH (QAction *const action, allActions) { if (doRegister(action)) { updateGlobalShortcut(action, ActiveShortcut, KGlobalAccel::Autoloading); } } } #ifndef KGLOBALACCEL_NO_DEPRECATED QList KGlobalAccel::allMainComponents() { return d->iface()->allMainComponents(); } #endif #ifndef KGLOBALACCEL_NO_DEPRECATED QList KGlobalAccel::allActionsForComponent(const QStringList &actionId) { return d->iface()->allActionsForComponent(actionId); } #endif //static #ifndef KGLOBALACCEL_NO_DEPRECATED QStringList KGlobalAccel::findActionNameSystemwide(const QKeySequence &seq) { return self()->d->iface()->action(seq[0]); } #endif QList KGlobalAccel::getGlobalShortcutsByKey(const QKeySequence &seq) { return self()->d->iface()->getGlobalShortcutsByKey(seq[0]); } bool KGlobalAccel::isGlobalShortcutAvailable(const QKeySequence &seq, const QString &comp) { return self()->d->iface()->isGlobalShortcutAvailable(seq[0], comp); } //static #ifndef KGLOBALACCEL_NO_DEPRECATED bool KGlobalAccel::promptStealShortcutSystemwide(QWidget *parent, const QStringList &actionIdentifier, const QKeySequence &seq) { if (actionIdentifier.size() < 4) { return false; } QString title = tr("Conflict with Global Shortcut"); QString message = tr("The '%1' key combination has already been allocated " "to the global action \"%2\" in %3.\n" "Do you want to reassign it from that action to the current one?") .arg(seq.toString(), actionIdentifier.at(KGlobalAccel::ActionFriendly)) .arg(actionIdentifier.at(KGlobalAccel::ComponentFriendly)); QMessageBox box(parent); box.setWindowTitle(title); box.setText(message); box.addButton(QMessageBox::Ok)->setText(tr("Reassign")); box.addButton(QMessageBox::Cancel); return box.exec() == QMessageBox::Ok; } #endif //static bool KGlobalAccel::promptStealShortcutSystemwide( QWidget *parent, const QList &shortcuts, const QKeySequence &seq) { if (shortcuts.isEmpty()) { // Usage error. Just say no return false; } QString component = shortcuts[0].componentFriendlyName(); QString message; if (shortcuts.size() == 1) { message = tr("The '%1' key combination is registered by application %2 for action %3:") .arg(seq.toString()) .arg(component) .arg(shortcuts[0].friendlyName()); } else { QString actionList; Q_FOREACH (const KGlobalShortcutInfo &info, shortcuts) { actionList += tr("In context '%1' for action '%2'\n") .arg(info.contextFriendlyName()) .arg(info.friendlyName()); } message = tr("The '%1' key combination is registered by application %2.\n%3") .arg(seq.toString()) .arg(component) .arg(actionList); } QString title = tr("Conflict With Registered Global Shortcut"); QMessageBox box(parent); box.setWindowTitle(title); box.setText(message); box.addButton(QMessageBox::Ok)->setText(tr("Reassign")); box.addButton(QMessageBox::Cancel); return box.exec() == QMessageBox::Ok; } //static void KGlobalAccel::stealShortcutSystemwide(const QKeySequence &seq) { //get the shortcut, remove seq, and set the new shortcut const QStringList actionId = self()->d->iface()->action(seq[0]); if (actionId.size() < 4) { // not a global shortcut return; } QList sc = self()->d->iface()->shortcut(actionId); for (int i = 0; i < sc.count(); i++) if (sc[i] == seq[0]) { sc[i] = 0; } self()->d->iface()->setForeignShortcut(actionId, sc); } bool checkGarbageKeycode(const QList &shortcut) { // protect against garbage keycode -1 that Qt sometimes produces for exotic keys; // at the moment (~mid 2008) Multimedia PlayPause is one of those keys. Q_FOREACH (const QKeySequence &sequence, shortcut) { for (int i = 0; i < 4; i++) { if (sequence[i] == -1) { qWarning() << "Encountered garbage keycode (keycode = -1) in input, not doing anything."; return true; } } } return false; } bool KGlobalAccel::setDefaultShortcut(QAction *action, const QList &shortcut, GlobalShortcutLoading loadFlag) { if (checkGarbageKeycode(shortcut)) { return false; } if (!d->doRegister(action)) { return false; } d->actionDefaultShortcuts.insert(action, shortcut); d->updateGlobalShortcut(action, KGlobalAccelPrivate::DefaultShortcut, loadFlag); return true; } bool KGlobalAccel::setShortcut(QAction *action, const QList &shortcut, GlobalShortcutLoading loadFlag) { if (checkGarbageKeycode(shortcut)) { return false; } if (!d->doRegister(action)) { return false; } d->actionShortcuts.insert(action, shortcut); d->updateGlobalShortcut(action, KGlobalAccelPrivate::ActiveShortcut, loadFlag); return true; } QList KGlobalAccel::defaultShortcut(const QAction *action) const { return d->actionDefaultShortcuts.value(action); } QList KGlobalAccel::shortcut(const QAction *action) const { return d->actionShortcuts.value(action); } QList KGlobalAccel::globalShortcut(const QString& componentName, const QString& actionId) const { // see also d->updateGlobalShortcut(action, KGlobalAccelPrivate::ActiveShortcut, KGlobalAccel::Autoloading); // how componentName and actionId map to QAction, e.g: // action->setProperty("componentName", "kwin"); // action->setObjectName("Kill Window"); const QList result = self()->d->iface()->shortcut({ componentName, actionId, QString(), QString() }); const QList scResult(d->shortcutFromIntList(result)); return scResult; } void KGlobalAccel::removeAllShortcuts(QAction *action) { d->remove(action, KGlobalAccelPrivate::UnRegister); } bool KGlobalAccel::hasShortcut(const QAction *action) const { return d->actionShortcuts.contains(action) || d->actionDefaultShortcuts.contains(action); } bool KGlobalAccel::eventFilter(QObject *watched, QEvent *event) { return QObject::eventFilter(watched, event); } bool KGlobalAccel::setGlobalShortcut(QAction *action, const QList &shortcut) { KGlobalAccel *g = KGlobalAccel::self(); return g->setShortcut(action, shortcut) && g->setDefaultShortcut(action, shortcut); } bool KGlobalAccel::setGlobalShortcut(QAction *action, const QKeySequence &shortcut) { return KGlobalAccel::setGlobalShortcut(action, QList() << shortcut); } #include "moc_kglobalaccel.cpp" diff --git a/src/runtime/globalshortcutsregistry.cpp b/src/runtime/globalshortcutsregistry.cpp index 827e606..d94c798 100644 --- a/src/runtime/globalshortcutsregistry.cpp +++ b/src/runtime/globalshortcutsregistry.cpp @@ -1,440 +1,440 @@ /* Copyright (C) 2008 Michael Jansen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "globalshortcutsregistry.h" #include "component.h" #include "kserviceactioncomponent.h" #include "globalshortcut.h" #include "globalshortcutcontext.h" #include #include "logging_p.h" #include "kglobalaccel_interface.h" #include #include #include #include #include #include #include #include #include #include static KGlobalAccelInterface *loadPlugin(GlobalShortcutsRegistry *parent) { const QVector candidates = KPluginLoader::findPlugins(QStringLiteral("org.kde.kglobalaccel5.platforms")); QString platformName = QString::fromLocal8Bit(qgetenv("KGLOBALACCELD_PLATFORM")); if (platformName.isEmpty()) { platformName = QGuiApplication::platformName(); } foreach (const KPluginMetaData &candidate, candidates) { const QJsonArray platforms = candidate.rawData().value(QStringLiteral("platforms")).toArray(); for (auto it = platforms.begin(); it != platforms.end(); ++it) { if (QString::compare(platformName, (*it).toString(), Qt::CaseInsensitive) == 0) { KGlobalAccelInterface *interface = qobject_cast< KGlobalAccelInterface* >(candidate.instantiate()); if (interface) { qCDebug(KGLOBALACCELD) << "Loaded plugin" << candidate.fileName() << "for platform" << platformName; interface->setRegistry(parent); return interface; } } } } qCWarning(KGLOBALACCELD) << "Could not find any platform plugin"; - return Q_NULLPTR; + return nullptr; } GlobalShortcutsRegistry::GlobalShortcutsRegistry() : QObject() ,_active_keys() ,_components() ,_manager(loadPlugin(this)) ,_config(qEnvironmentVariableIsSet("KGLOBALACCEL_TEST_MODE") ? QString() : QStringLiteral("kglobalshortcutsrc"), KConfig::SimpleConfig) { if (_manager) { _manager->setEnabled(true); } } GlobalShortcutsRegistry::~GlobalShortcutsRegistry() { if (_manager) { _manager->setEnabled(false); // Ungrab all keys. We don't go over GlobalShortcuts because // GlobalShortcutsRegistry::self() doesn't work anymore. Q_FOREACH (const int key, _active_keys.keys()) { _manager->grabKey(key, false); } } _active_keys.clear(); } KdeDGlobalAccel::Component *GlobalShortcutsRegistry::addComponent(KdeDGlobalAccel::Component *component) { if (_components.value(component->uniqueName())) { Q_ASSERT_X(false, "GlobalShortcutsRegistry::addComponent", "component already registered?!?!"); return _components.value(component->uniqueName()); } _components.insert(component->uniqueName(), component); QDBusConnection conn(QDBusConnection::sessionBus()); conn.registerObject( component->dbusPath().path(), component, QDBusConnection::ExportScriptableContents); return component; } void GlobalShortcutsRegistry::activateShortcuts() { Q_FOREACH (KdeDGlobalAccel::Component *component, _components) { component->activateShortcuts(); } } QList GlobalShortcutsRegistry::allMainComponents() const { return _components.values(); } void GlobalShortcutsRegistry::clear() { Q_FOREACH(KdeDGlobalAccel::Component *component, _components) { delete component; } _components.clear(); // The shortcuts should have deregistered themselves Q_ASSERT(_active_keys.isEmpty()); } QDBusObjectPath GlobalShortcutsRegistry::dbusPath() const { return _dbusPath; } void GlobalShortcutsRegistry::deactivateShortcuts(bool temporarily) { Q_FOREACH (KdeDGlobalAccel::Component *component, _components) { component->deactivateShortcuts(temporarily); } } GlobalShortcut *GlobalShortcutsRegistry::getActiveShortcutByKey(int key) const { return _active_keys.value(key); } KdeDGlobalAccel::Component *GlobalShortcutsRegistry::getComponent(const QString &uniqueName) { return _components.value(uniqueName); } GlobalShortcut *GlobalShortcutsRegistry::getShortcutByKey(int key) const { Q_FOREACH (KdeDGlobalAccel::Component *component, _components) { GlobalShortcut *rc = component->getShortcutByKey(key); if (rc) return rc; } return nullptr; } QList GlobalShortcutsRegistry::getShortcutsByKey(int key) const { QList rc; Q_FOREACH (KdeDGlobalAccel::Component *component, _components) { rc = component->getShortcutsByKey(key); if (!rc.isEmpty()) return rc; } return rc; } bool GlobalShortcutsRegistry::isShortcutAvailable( int shortcut, const QString &componentName, const QString &contextName) const { Q_FOREACH (KdeDGlobalAccel::Component *component, _components) { if (!component->isShortcutAvailable(shortcut, componentName, contextName)) return false; } return true; } Q_GLOBAL_STATIC( GlobalShortcutsRegistry, _self ) GlobalShortcutsRegistry * GlobalShortcutsRegistry::self() { return _self; } bool GlobalShortcutsRegistry::keyPressed(int keyQt) { GlobalShortcut *shortcut = getShortcutByKey(keyQt); if (!shortcut) { // This can happen for example with the ALT-Print shortcut of kwin. // ALT+PRINT is SYSREQ on my keyboard. So we grab something we think // is ALT+PRINT but symXToKeyQt and modXToQt make ALT+SYSREQ of it // when pressed (correctly). We can't match that. #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << "Got unknown key" << QKeySequence(keyQt).toString(); #endif // In production mode just do nothing. return false; } else if (!shortcut->isActive()) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << "Got inactive key" << QKeySequence(keyQt).toString(); #endif // In production mode just do nothing. return false; } #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << QKeySequence(keyQt).toString() << "=" << shortcut->uniqueName(); #endif QStringList data(shortcut->context()->component()->uniqueName()); data.append(shortcut->uniqueName()); data.append(shortcut->context()->component()->friendlyName()); data.append(shortcut->friendlyName()); // Make sure kglobalacceld has ungrabbed the keyboard after receiving the // keypress, otherwise actions in application that try to grab the // keyboard (e.g. in kwin) may fail to do so. There is still a small race // condition with this being out-of-process. if (_manager) { _manager->syncWindowingSystem(); } // 1st Invoke the action shortcut->context()->component()->emitGlobalShortcutPressed( *shortcut ); return true; } void GlobalShortcutsRegistry::loadSettings() { foreach (const QString &groupName, _config.groupList()) { qCDebug(KGLOBALACCELD) << "Loading group " << groupName; Q_ASSERT(groupName.indexOf('\x1d')==-1); // loadSettings isn't designed to be called in between. Only at the // beginning. Q_ASSERT(!getComponent(groupName)); KConfigGroup configGroup(&_config, groupName); // We previously stored the friendly name in a separate group. migrate // that const QString friendlyName = configGroup.readEntry("_k_friendly_name"); // Create the component KdeDGlobalAccel::Component *component = nullptr; if (groupName.endsWith(QLatin1String(".desktop"))) { component = new KdeDGlobalAccel::KServiceActionComponent( groupName, friendlyName, this); } else { component = new KdeDGlobalAccel::Component( groupName, friendlyName, this); } // Now load the contexts Q_FOREACH(const QString& context, configGroup.groupList()) { // Skip the friendly name group if (context==QLatin1String("Friendly Name")) continue; KConfigGroup contextGroup(&configGroup, context); QString contextFriendlyName = contextGroup.readEntry("_k_friendly_name"); component->createGlobalShortcutContext(context, contextFriendlyName); component->activateGlobalShortcutContext(context); component->loadSettings(contextGroup); } // Load the default context component->activateGlobalShortcutContext("default"); component->loadSettings(configGroup); } // Load the configured KServiceActions const QStringList desktopPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kglobalaccel"), QStandardPaths::LocateDirectory); foreach (const QString &path, desktopPaths) { QDir dir(path); if (!dir.exists()) { continue; } const QStringList patterns = {QStringLiteral("*.desktop")}; foreach (const QString &desktopFile, dir.entryList(patterns)) { if (_components.contains(desktopFile)) { continue; } KDesktopFile f(dir.filePath(desktopFile)); if (f.noDisplay()) { continue; } KdeDGlobalAccel::KServiceActionComponent *component = new KdeDGlobalAccel::KServiceActionComponent( desktopFile, f.readName(), this); component->activateGlobalShortcutContext(QStringLiteral("default")); component->loadFromService(); } } } void GlobalShortcutsRegistry::grabKeys() { activateShortcuts(); } bool GlobalShortcutsRegistry::registerKey(int key, GlobalShortcut *shortcut) { if (!_manager) { return false; } if (key == 0) { qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Key '" << QKeySequence(key).toString() << "' already taken by " << _active_keys.value(key)->uniqueName() << "."; return false; } else if (_active_keys.value(key)) { qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Attempt to register key 0."; return false; } qCDebug(KGLOBALACCELD) << "Registering key" << QKeySequence(key).toString() << "for" << shortcut->context()->component()->uniqueName() << ":" << shortcut->uniqueName(); _active_keys.insert(key, shortcut); return _manager->grabKey(key, true); } void GlobalShortcutsRegistry::setAccelManager(KGlobalAccelInterface *manager) { _manager = manager; } void GlobalShortcutsRegistry::setDBusPath(const QDBusObjectPath &path) { _dbusPath = path; } KdeDGlobalAccel::Component *GlobalShortcutsRegistry::takeComponent(KdeDGlobalAccel::Component *component) { QDBusConnection conn(QDBusConnection::sessionBus()); conn.unregisterObject(component->dbusPath().path()); return _components.take(component->uniqueName()); } void GlobalShortcutsRegistry::ungrabKeys() { deactivateShortcuts(); } bool GlobalShortcutsRegistry::unregisterKey(int key, GlobalShortcut *shortcut) { if (!_manager) { return false; } if (_active_keys.value(key)!=shortcut) { // The shortcut doesn't own the key or the key isn't grabbed return false; } qCDebug(KGLOBALACCELD) << "Unregistering key" << QKeySequence(key).toString() << "for" << shortcut->context()->component()->uniqueName() << ":" << shortcut->uniqueName(); _manager->grabKey(key, false); _active_keys.take(key); return true; } void GlobalShortcutsRegistry::writeSettings() const { Q_FOREACH( const KdeDGlobalAccel::Component *component, GlobalShortcutsRegistry::self()->allMainComponents()) { KConfigGroup configGroup(&_config, component->uniqueName()); if (component->allShortcuts().isEmpty()) { configGroup.deleteGroup(); delete component; } else { component->writeSettings(configGroup); } } _config.sync(); } #include "moc_globalshortcutsregistry.cpp" diff --git a/src/runtime/plugins/xcb/kglobalaccel_x11.cpp b/src/runtime/plugins/xcb/kglobalaccel_x11.cpp index 3e63209..9b37c7b 100644 --- a/src/runtime/plugins/xcb/kglobalaccel_x11.cpp +++ b/src/runtime/plugins/xcb/kglobalaccel_x11.cpp @@ -1,314 +1,314 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Ellis Whitehead Copyright (C) 2013 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kglobalaccel_x11.h" #include "logging_p.h" #include "kkeyserver.h" #include #include #include #include #include #include // xcb #include #include // g_keyModMaskXAccel // mask of modifiers which can be used in shortcuts // (meta, alt, ctrl, shift) // g_keyModMaskXOnOrOff // mask of modifiers where we don't care whether they are on or off // (caps lock, num lock, scroll lock) static uint g_keyModMaskXAccel = 0; static uint g_keyModMaskXOnOrOff = 0; static void calculateGrabMasks() { g_keyModMaskXAccel = KKeyServer::accelModMaskX(); g_keyModMaskXOnOrOff = KKeyServer::modXLock() | KKeyServer::modXNumLock() | KKeyServer::modXScrollLock() | KKeyServer::modXModeSwitch(); //qCDebug(KGLOBALACCELD) << "g_keyModMaskXAccel = " << g_keyModMaskXAccel // << "g_keyModMaskXOnOrOff = " << g_keyModMaskXOnOrOff << endl; } //---------------------------------------------------- KGlobalAccelImpl::KGlobalAccelImpl(QObject *parent) : KGlobalAccelInterface(parent) - , m_keySymbols(Q_NULLPTR) + , m_keySymbols(nullptr) { calculateGrabMasks(); if (QX11Info::isPlatformX11()) { m_keySymbols = xcb_key_symbols_alloc(QX11Info::connection()); } } KGlobalAccelImpl::~KGlobalAccelImpl() { if (m_keySymbols) { xcb_key_symbols_free(m_keySymbols); } } bool KGlobalAccelImpl::grabKey( int keyQt, bool grab ) { //grabKey is called during shutdown //shutdown might be due to the X server being killed //if so, fail immediately before trying to make other xcb calls if (!QX11Info::connection() || xcb_connection_has_error(QX11Info::connection())) { return false; } if (!m_keySymbols) { return false; } if( !keyQt ) { qCDebug(KGLOBALACCELD) << "Tried to grab key with null code."; return false; } uint keyModX; xcb_keysym_t keySymX; // Resolve the modifier if( !KKeyServer::keyQtToModX(keyQt, &keyModX) ) { qCDebug(KGLOBALACCELD) << "keyQt (0x" << hex << keyQt << ") failed to resolve to x11 modifier"; return false; } // Resolve the X symbol if( !KKeyServer::keyQtToSymX(keyQt, (int *)&keySymX) ) { qCDebug(KGLOBALACCELD) << "keyQt (0x" << hex << keyQt << ") failed to resolve to x11 keycode"; return false; } xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(m_keySymbols, keySymX); if (!keyCodes) { return false; } int i = 0; bool success = !grab; while (keyCodes[i] != XCB_NO_SYMBOL) { xcb_keycode_t keyCodeX = keyCodes[i++]; // Check if shift needs to be added to the grab since KKeySequenceWidget // can remove shift for some keys. (all the %&* and such) if( !(keyQt & Qt::SHIFT) && !KKeyServer::isShiftAsModifierAllowed( keyQt ) && keySymX != xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 0) && keySymX == xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 1) ) { qCDebug(KGLOBALACCELD) << "adding shift to the grab"; keyModX |= KKeyServer::modXShift(); } keyModX &= g_keyModMaskXAccel; // Get rid of any non-relevant bits in mod if( !keyCodeX ) { qCDebug(KGLOBALACCELD) << "keyQt (0x" << hex << keyQt << ") was resolved to x11 keycode 0"; continue; } // We'll have to grab 8 key modifier combinations in order to cover all // combinations of CapsLock, NumLock, ScrollLock. // Does anyone with more X-savvy know how to set a mask on QX11Info::appRootWindow so that // the irrelevant bits are always ignored and we can just make one XGrabKey // call per accelerator? -- ellis #ifndef NDEBUG QString sDebug = QString("\tcode: 0x%1 state: 0x%2 | ").arg(keyCodeX,0,16).arg(keyModX,0,16); #endif uint keyModMaskX = ~g_keyModMaskXOnOrOff; QVector cookies; for( uint irrelevantBitsMask = 0; irrelevantBitsMask <= 0xff; irrelevantBitsMask++ ) { if( (irrelevantBitsMask & keyModMaskX) == 0 ) { #ifndef NDEBUG sDebug += QString("0x%3, ").arg(irrelevantBitsMask, 0, 16); #endif if( grab ) cookies << xcb_grab_key_checked(QX11Info::connection(), true, QX11Info::appRootWindow(), keyModX | irrelevantBitsMask, keyCodeX, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_SYNC); else cookies << xcb_ungrab_key_checked(QX11Info::connection(), keyCodeX, QX11Info::appRootWindow(), keyModX | irrelevantBitsMask); } } bool failed = false; if( grab ) { for (int i = 0; i < cookies.size(); ++i) { QScopedPointer error(xcb_request_check(QX11Info::connection(), cookies.at(i))); if (!error.isNull()) { failed = true; } } if( failed ) { qCDebug(KGLOBALACCELD) << "grab failed!\n"; for( uint m = 0; m <= 0xff; m++ ) { if(( m & keyModMaskX ) == 0 ) xcb_ungrab_key(QX11Info::connection(), keyCodeX, QX11Info::appRootWindow(), keyModX | m); } } else { success = true; } } } free(keyCodes); return success; } bool KGlobalAccelImpl::nativeEventFilter(const QByteArray &eventType, void *message, long *) { if (eventType != "xcb_generic_event_t") { return false; } xcb_generic_event_t *event = reinterpret_cast(message); const uint8_t responseType = event->response_type & ~0x80; switch (responseType) { case XCB_MAPPING_NOTIFY: qCDebug(KGLOBALACCELD) << "Got XMappingNotify event"; xcb_refresh_keyboard_mapping(m_keySymbols, reinterpret_cast(event)); x11MappingNotify(); return true; case XCB_KEY_PRESS: #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << "Got XKeyPress event"; #endif return x11KeyPress(reinterpret_cast(event)); default: // We get all XEvents. Just ignore them. return false; } Q_ASSERT(false); return false; } void KGlobalAccelImpl::x11MappingNotify() { // Maybe the X modifier map has been changed. // uint oldKeyModMaskXAccel = g_keyModMaskXAccel; // uint oldKeyModMaskXOnOrOff = g_keyModMaskXOnOrOff; // First ungrab all currently grabbed keys. This is needed because we // store the keys as qt keycodes and use KKeyServer to map them to x11 key // codes. After calling KKeyServer::initializeMods() they could map to // different keycodes. ungrabKeys(); KKeyServer::initializeMods(); calculateGrabMasks(); grabKeys(); } bool KGlobalAccelImpl::x11KeyPress(xcb_key_press_event_t *pEvent) { if (QWidget::keyboardGrabber() || QApplication::activePopupWidget()) { qCWarning(KGLOBALACCELD) << "kglobalacceld should be popup and keyboard grabbing free!"; } // Keyboard needs to be ungrabed after XGrabKey() activates the grab, // otherwise it becomes frozen. xcb_connection_t *c = QX11Info::connection(); xcb_ungrab_keyboard(c, XCB_TIME_CURRENT_TIME); xcb_flush(c); xcb_keycode_t keyCodeX = pEvent->detail; uint16_t keyModX = pEvent->state & (g_keyModMaskXAccel | KKeyServer::MODE_SWITCH); xcb_keysym_t keySymX = xcb_key_press_lookup_keysym(m_keySymbols, pEvent, 0); // If numlock is active and a keypad key is pressed, XOR the SHIFT state. // e.g., KP_4 => Shift+KP_Left, and Shift+KP_4 => KP_Left. if (pEvent->state & KKeyServer::modXNumLock()) { // If this is a keypad key, if( keySymX >= XK_KP_Space && keySymX <= XK_KP_9 ) { switch( keySymX ) { // Leave the following keys unaltered // FIXME: The proper solution is to see which keysyms don't change when shifted. case XK_KP_Multiply: case XK_KP_Add: case XK_KP_Subtract: case XK_KP_Divide: case XK_KP_Enter: break; default: keyModX ^= KKeyServer::modXShift(); } } } int keyCodeQt; int keyModQt; KKeyServer::symXToKeyQt(keySymX, &keyCodeQt); KKeyServer::modXToQt(keyModX, &keyModQt); if ((keyModQt & Qt::SHIFT) && !KKeyServer::isShiftAsModifierAllowed( keyCodeQt ) ) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << "removing shift modifier"; #endif if (keyCodeQt != Qt::Key_Tab) { // KKeySequenceWidget does not map shift+tab to backtab static const int FirstLevelShift = 1; keySymX = xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, FirstLevelShift); KKeyServer::symXToKeyQt(keySymX, &keyCodeQt); } keyModQt &= ~Qt::SHIFT; } int keyQt = keyCodeQt | keyModQt; // All that work for this hey... argh... if (NET::timestampCompare(pEvent->time, QX11Info::appTime()) > 0) { QX11Info::setAppTime(pEvent->time); } return keyPressed(keyQt); } void KGlobalAccelImpl::setEnabled( bool enable ) { if (enable && qApp->platformName() == QLatin1String("xcb")) { qApp->installNativeEventFilter(this); } else { qApp->removeNativeEventFilter(this); } } void KGlobalAccelImpl::syncX() { xcb_connection_t *c = QX11Info::connection(); auto *value = xcb_get_input_focus_reply(c, xcb_get_input_focus_unchecked(c), nullptr); free(value); }