KDEUI
kglobalaccel.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2001,2002 Ellis Whitehead <ellis@kde.org> 00003 Copyright (C) 2006 Hamish Rodda <rodda@kde.org> 00004 Copyright (C) 2007 Andreas Hartmetz <ahartmetz@gmail.com> 00005 Copyright (C) 2008 Michael Jansen <kde@michael-jansen.biz> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License as published by the Free Software Foundation; either 00010 version 2 of the License, or (at your option) any later version. 00011 00012 This library is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 Library General Public License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to 00019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 Boston, MA 02110-1301, USA. 00021 */ 00022 00023 #include "kglobalaccel.h" 00024 #include "kglobalaccel_p.h" 00025 00026 #include <memory> 00027 00028 #include <QtDBus/QDBusInterface> 00029 #include <QtDBus/QDBusMetaType> 00030 #ifdef Q_WS_X11 00031 #include <QtGui/QX11Info> 00032 #include <netwm_def.h> 00033 #endif 00034 00035 #include <kdebug.h> 00036 #include <ktoolinvocation.h> 00037 #include <kaboutdata.h> 00038 #include <kcomponentdata.h> 00039 #include "kaction.h" 00040 #include "kaction_p.h" 00041 #include "kmessagebox.h" 00042 #include "kshortcut.h" 00043 00044 org::kde::kglobalaccel::Component *KGlobalAccelPrivate::getComponent(const QString &componentUnique, bool remember = false) 00045 { 00046 // Check if we already have this component 00047 if (components.contains(componentUnique)) { 00048 return components[componentUnique]; 00049 } 00050 00051 // Connect to the kglobalaccel daemon 00052 org::kde::KGlobalAccel kglobalaccel( 00053 "org.kde.kglobalaccel", 00054 "/kglobalaccel", 00055 QDBusConnection::sessionBus()); 00056 if (!kglobalaccel.isValid()) { 00057 kDebug() << "Failed to connect to the kglobalaccel daemon" << QDBusConnection::sessionBus().lastError(); 00058 return NULL; 00059 } 00060 00061 // Get the path for our component. We have to do that because 00062 // componentUnique is probably not a valid dbus object path 00063 QDBusReply<QDBusObjectPath> reply = kglobalaccel.getComponent(componentUnique); 00064 if (!reply.isValid()) { 00065 00066 if (reply.error().name() == "org.kde.kglobalaccel.NoSuchComponent") { 00067 // No problem. The component doesn't exists. That's normal 00068 return NULL; 00069 } 00070 00071 // An unknown error. 00072 kDebug() << "Failed to get dbus path for component " << componentUnique << reply.error(); 00073 return NULL; 00074 } 00075 00076 // Now get the component 00077 org::kde::kglobalaccel::Component *component = new org::kde::kglobalaccel::Component( 00078 "org.kde.kglobalaccel", 00079 reply.value().path(), 00080 QDBusConnection::sessionBus(), 00081 q); 00082 00083 // No component no cleaning 00084 if (!component->isValid()) { 00085 kDebug() << "Failed to get component" << componentUnique << QDBusConnection::sessionBus().lastError(); 00086 return NULL; 00087 } 00088 00089 if (remember) 00090 { 00091 // Connect to the signals we are interested in. 00092 q->connect(component, SIGNAL(globalShortcutPressed(const QString &, const QString &, qlonglong)), 00093 SLOT(_k_invokeAction(const QString &, const QString &, qlonglong))); 00094 00095 components[componentUnique] = component; 00096 } 00097 00098 return component; 00099 } 00100 00101 00102 00103 KGlobalAccelPrivate::KGlobalAccelPrivate(KGlobalAccel *q) 00104 : isUsingForeignComponentName(false), 00105 #ifndef KDE_NO_DEPRECATED 00106 enabled(true), 00107 #endif 00108 iface("org.kde.kglobalaccel", "/kglobalaccel", QDBusConnection::sessionBus()), 00109 q(q) 00110 { 00111 // Make sure kded is running. The iface declaration above somehow 00112 // works anyway. 00113 QDBusConnectionInterface* bus = QDBusConnection::sessionBus().interface(); 00114 if (!bus->isServiceRegistered("org.kde.kglobalaccel")) { 00115 QString error; 00116 int ret = KToolInvocation::startServiceByDesktopPath( 00117 "kglobalaccel.desktop", 00118 QStringList(), 00119 &error); 00120 00121 if (ret > 0) { 00122 kError() << "Couldn't start kglobalaccel from kglobalaccel.desktop: " << error << endl; 00123 } 00124 } 00125 QDBusServiceWatcher *watcher = new QDBusServiceWatcher(iface.service(), 00126 QDBusConnection::sessionBus(), 00127 QDBusServiceWatcher::WatchForOwnerChange, 00128 q); 00129 q->connect(watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), 00130 q, SLOT(_k_serviceOwnerChanged(QString,QString,QString))); 00131 } 00132 00133 00134 void KGlobalAccelPrivate::readComponentData(const KComponentData &componentData) 00135 { 00136 Q_ASSERT(!componentData.componentName().isEmpty()); 00137 00138 mainComponent = componentData; 00139 if (componentData.aboutData()->programName().isEmpty()) { 00140 kDebug(123) << componentData.componentName() << " has empty programName()"; 00141 } 00142 } 00143 00144 00145 KGlobalAccel::KGlobalAccel() 00146 : d(new KGlobalAccelPrivate(this)) 00147 { 00148 qDBusRegisterMetaType<QList<int> >(); 00149 qDBusRegisterMetaType<QList<QStringList> >(); 00150 qDBusRegisterMetaType<KGlobalShortcutInfo>(); 00151 qDBusRegisterMetaType<QList<KGlobalShortcutInfo> >(); 00152 00153 connect(&d->iface, SIGNAL(yourShortcutGotChanged(const QStringList &, const QList<int> &)), 00154 SLOT(_k_shortcutGotChanged(const QStringList &, const QList<int> &))); 00155 00156 if (KGlobal::hasMainComponent()) { 00157 d->readComponentData( KGlobal::mainComponent() ); 00158 } 00159 00160 } 00161 00162 00163 KGlobalAccel::~KGlobalAccel() 00164 { 00165 delete d; 00166 } 00167 00168 00169 void KGlobalAccel::activateGlobalShortcutContext( 00170 const QString &contextUnique, 00171 const QString &contextFriendly, 00172 const KComponentData &component) 00173 { 00174 Q_UNUSED(contextFriendly); 00175 // TODO: provide contextFriendly 00176 self()->d->iface.activateGlobalShortcutContext(component.aboutData()->programName(), contextUnique); 00177 } 00178 00179 00180 // static 00181 bool KGlobalAccel::cleanComponent(const QString &componentUnique) 00182 { 00183 org::kde::kglobalaccel::Component* component = self()->getComponent(componentUnique); 00184 if (!component) return false; 00185 00186 return component->cleanUp(); 00187 } 00188 00189 00190 // static 00191 bool KGlobalAccel::isComponentActive(const QString &componentUnique) 00192 { 00193 org::kde::kglobalaccel::Component* component = self()->getComponent(componentUnique); 00194 if (!component) return false; 00195 00196 return component->isActive(); 00197 } 00198 00199 00200 #ifndef KDE_NO_DEPRECATED 00201 bool KGlobalAccel::isEnabled() const 00202 { 00203 return d->enabled; 00204 } 00205 #endif 00206 00207 00208 org::kde::kglobalaccel::Component *KGlobalAccel::getComponent(const QString &componentUnique) 00209 { 00210 return d->getComponent(componentUnique); 00211 } 00212 00213 00214 #ifndef KDE_NO_DEPRECATED 00215 void KGlobalAccel::setEnabled(bool enabled) 00216 { 00217 d->enabled = enabled; 00218 } 00219 #endif 00220 00221 00222 #ifndef KDE_NO_DEPRECATED 00223 void KGlobalAccel::overrideMainComponentData(const KComponentData &kcd) 00224 { 00225 d->readComponentData(kcd); 00226 d->isUsingForeignComponentName = true; 00227 } 00228 #endif 00229 00230 00231 KGlobalAccel *KGlobalAccel::self() 00232 { 00233 K_GLOBAL_STATIC(KGlobalAccel, s_instance) 00234 return s_instance; 00235 } 00236 00237 00238 void KGlobalAccelPrivate::doRegister(KAction *action) 00239 { 00240 if (!action || action->objectName().isEmpty()) { 00241 return; 00242 } 00243 00244 const bool isRegistered = actions.contains(action); 00245 if (isRegistered) 00246 return; 00247 00248 // Under configuration mode - deprecated - we ignore the component given 00249 // from the action and use our own. 00250 if (isUsingForeignComponentName) { 00251 action->d->componentData = mainComponent; 00252 } 00253 QStringList actionId = makeActionId(action); 00254 00255 nameToAction.insertMulti(actionId.at(KGlobalAccel::ActionUnique), action); 00256 actions.insert(action); 00257 iface.doRegister(actionId); 00258 } 00259 00260 00261 void KGlobalAccelPrivate::remove(KAction *action, Removal removal) 00262 { 00263 if (!action || action->objectName().isEmpty()) { 00264 return; 00265 } 00266 00267 const bool isRegistered = actions.contains(action); 00268 if (!isRegistered) { 00269 return; 00270 } 00271 00272 QStringList actionId = makeActionId(action); 00273 00274 nameToAction.remove(actionId.at(KGlobalAccel::ActionUnique), action); 00275 actions.remove(action); 00276 00277 if (removal == UnRegister) { 00278 // Complete removal of the shortcut is requested 00279 // (forgetGlobalShortcut) 00280 iface.unRegister(actionId); 00281 } else { 00282 // If the action is a configurationAction wen only remove it from our 00283 // internal registry. That happened above. 00284 if (!action->property("isConfigurationAction").toBool()) { 00285 // If it's a session shortcut unregister it. 00286 action->objectName().startsWith(QLatin1String("_k_session:")) 00287 ? iface.unRegister(actionId) 00288 : iface.setInactive(actionId); 00289 } 00290 } 00291 } 00292 00293 00294 void KGlobalAccelPrivate::updateGlobalShortcut(KAction *action, uint flags) 00295 { 00296 // No action or no objectname -> Do nothing 00297 // KAction::setGlobalShortcut informs the user 00298 if (!action || action->objectName().isEmpty()) { 00299 return; 00300 } 00301 00302 QStringList actionId = makeActionId(action); 00303 const KShortcut activeShortcut = action->globalShortcut(); 00304 const KShortcut defaultShortcut = action->globalShortcut(KAction::DefaultShortcut); 00305 00306 uint setterFlags = 0; 00307 if (flags & KAction::NoAutoloading) { 00308 setterFlags |= NoAutoloading; 00309 } 00310 00311 if (flags & KAction::ActiveShortcut) { 00312 bool isConfigurationAction = isUsingForeignComponentName 00313 || action->property("isConfigurationAction").toBool(); 00314 uint activeSetterFlags = setterFlags; 00315 00316 // setPresent tells kglobalaccel that the shortcut is active 00317 if (!isConfigurationAction) { 00318 activeSetterFlags |= SetPresent; 00319 } 00320 00321 // Sets the shortcut, returns the active/real keys 00322 const QList<int> result = iface.setShortcut( 00323 actionId, 00324 intListFromShortcut(activeShortcut), 00325 activeSetterFlags); 00326 00327 // Make sure we get informed about changes in the component by kglobalaccel 00328 getComponent(componentUniqueForAction(action), true); 00329 00330 // Create a shortcut from the result 00331 const KShortcut scResult(shortcutFromIntList(result)); 00332 00333 if (isConfigurationAction && (flags & KAction::NoAutoloading)) { 00334 // If this is a configuration action and we have set the shortcut, 00335 // inform the real owner of the change. 00336 // Note that setForeignShortcut will cause a signal to be sent to applications 00337 // even if it did not "see" that the shortcut has changed. This is Good because 00338 // at the time of comparison (now) the action *already has* the new shortcut. 00339 // We called setShortcut(), remember? 00340 // Also note that we will see our own signal so we may not need to call 00341 // setActiveGlobalShortcutNoEnable - _k_shortcutGotChanged() does it. 00342 // In practice it's probably better to get the change propagated here without 00343 // DBus delay as we do below. 00344 iface.setForeignShortcut(actionId, result); 00345 } 00346 if (scResult != activeShortcut) { 00347 // If kglobalaccel returned a shortcut that differs from the one we 00348 // sent, use that one. There must have been clashes or some other problem. 00349 action->d->setActiveGlobalShortcutNoEnable(scResult); 00350 } 00351 } 00352 00353 if (flags & KAction::DefaultShortcut) { 00354 iface.setShortcut(actionId, intListFromShortcut(defaultShortcut), 00355 setterFlags | IsDefault); 00356 } 00357 } 00358 00359 00360 QStringList KGlobalAccelPrivate::makeActionId(const KAction *action) 00361 { 00362 QStringList ret(componentUniqueForAction(action)); // Component Unique Id ( see actionIdFields ) 00363 Q_ASSERT(!ret.at(KGlobalAccel::ComponentUnique).isEmpty()); 00364 Q_ASSERT(!action->objectName().isEmpty()); 00365 ret.append(action->objectName()); // Action Unique Name 00366 ret.append(componentFriendlyForAction(action)); // Component Friendly name 00367 const QString actionText = KGlobal::locale()->removeAcceleratorMarker(action->text()); 00368 ret.append(actionText); // Action Friendly Name 00369 return ret; 00370 } 00371 00372 00373 QList<int> KGlobalAccelPrivate::intListFromShortcut(const KShortcut &cut) 00374 { 00375 QList<int> ret; 00376 ret.append(cut.primary()[0]); 00377 ret.append(cut.alternate()[0]); 00378 while (!ret.isEmpty() && ret.last() == 0) 00379 ret.removeLast(); 00380 return ret; 00381 } 00382 00383 00384 KShortcut KGlobalAccelPrivate::shortcutFromIntList(const QList<int> &list) 00385 { 00386 KShortcut ret; 00387 if (list.count() > 0) 00388 ret.setPrimary(list[0]); 00389 if (list.count() > 1) 00390 ret.setAlternate(list[1]); 00391 return ret; 00392 } 00393 00394 00395 QString KGlobalAccelPrivate::componentUniqueForAction(const KAction *action) 00396 { 00397 Q_ASSERT(action->d->componentData.isValid()); 00398 return action->d->componentData.componentName(); 00399 } 00400 00401 00402 QString KGlobalAccelPrivate::componentFriendlyForAction(const KAction *action) 00403 { 00404 Q_ASSERT(action->d->componentData.isValid()); 00405 return action->d->componentData.aboutData()->programName(); 00406 } 00407 00408 00409 void KGlobalAccelPrivate::_k_invokeAction( 00410 const QString &componentUnique, 00411 const QString &actionUnique, 00412 qlonglong timestamp) 00413 { 00414 // If overrideMainComponentData() is active the app can only have 00415 // configuration actions. 00416 if (isUsingForeignComponentName ) { 00417 return; 00418 } 00419 00420 KAction *action = 0; 00421 QList<KAction *> candidates = nameToAction.values(actionUnique); 00422 foreach (KAction *const a, candidates) { 00423 if (componentUniqueForAction(a) == componentUnique) { 00424 action = a; 00425 } 00426 } 00427 00428 // We do not trigger if 00429 // - there is no action 00430 // - the action is not enabled 00431 // - the action is an configuration action 00432 if (!action || !action->isEnabled() || action->property("isConfigurationAction").toBool()) { 00433 return; 00434 } 00435 00436 #ifdef Q_WS_X11 00437 // Update this application's X timestamp if needed. 00438 // TODO The 100%-correct solution should probably be handling this action 00439 // in the proper place in relation to the X events queue in order to avoid 00440 // the possibility of wrong ordering of user events. 00441 if( NET::timestampCompare(timestamp, QX11Info::appTime()) > 0) 00442 QX11Info::setAppTime(timestamp); 00443 if( NET::timestampCompare(timestamp, QX11Info::appUserTime()) > 0) 00444 QX11Info::setAppUserTime(timestamp); 00445 #else 00446 Q_UNUSED(timestamp); 00447 #endif 00448 00449 action->trigger(); 00450 } 00451 00452 00453 void KGlobalAccelPrivate::_k_shortcutGotChanged(const QStringList &actionId, 00454 const QList<int> &keys) 00455 { 00456 KAction *action = nameToAction.value(actionId.at(KGlobalAccel::ActionUnique)); 00457 if (!action) 00458 return; 00459 00460 action->d->setActiveGlobalShortcutNoEnable(shortcutFromIntList(keys)); 00461 } 00462 00463 void KGlobalAccelPrivate::_k_serviceOwnerChanged(const QString &name, const QString &oldOwner, 00464 const QString &newOwner) 00465 { 00466 Q_UNUSED(oldOwner); 00467 if (name == QLatin1String("org.kde.kglobalaccel") && !newOwner.isEmpty()) { 00468 // kglobalaccel was restarted 00469 kDebug(123) << "detected kglobalaccel restarting, re-registering all shortcut keys"; 00470 reRegisterAll(); 00471 } 00472 } 00473 00474 void KGlobalAccelPrivate::reRegisterAll() 00475 { 00476 //### Special case for isUsingForeignComponentName? 00477 00478 //We clear all our data, assume that all data on the other side is clear too, 00479 //and register each action as if it just was allowed to have global shortcuts. 00480 //If the kded side still has the data it doesn't matter because of the 00481 //autoloading mechanism. The worst case I can imagine is that an action's 00482 //shortcut was changed but the kded side died before it got the message so 00483 //autoloading will now assign an old shortcut to the action. Particularly 00484 //picky apps might assert or misbehave. 00485 QSet<KAction *> allActions = actions; 00486 nameToAction.clear(); 00487 actions.clear(); 00488 foreach(KAction *const action, allActions) { 00489 doRegister(action); 00490 updateGlobalShortcut(action, KAction::Autoloading | KAction::ActiveShortcut); 00491 } 00492 } 00493 00494 00495 #ifndef KDE_NO_DEPRECATED 00496 QList<QStringList> KGlobalAccel::allMainComponents() 00497 { 00498 return d->iface.allMainComponents(); 00499 } 00500 #endif 00501 00502 00503 #ifndef KDE_NO_DEPRECATED 00504 QList<QStringList> KGlobalAccel::allActionsForComponent(const QStringList &actionId) 00505 { 00506 return d->iface.allActionsForComponent(actionId); 00507 } 00508 #endif 00509 00510 00511 //static 00512 #ifndef KDE_NO_DEPRECATED 00513 QStringList KGlobalAccel::findActionNameSystemwide(const QKeySequence &seq) 00514 { 00515 return self()->d->iface.action(seq[0]); 00516 } 00517 #endif 00518 00519 00520 QList<KGlobalShortcutInfo> KGlobalAccel::getGlobalShortcutsByKey(const QKeySequence &seq) 00521 { 00522 return self()->d->iface.getGlobalShortcutsByKey(seq[0]); 00523 } 00524 00525 00526 bool KGlobalAccel::isGlobalShortcutAvailable(const QKeySequence &seq, const QString &comp) 00527 { 00528 return self()->d->iface.isGlobalShortcutAvailable(seq[0], comp); 00529 } 00530 00531 00532 //static 00533 #ifndef KDE_NO_DEPRECATED 00534 bool KGlobalAccel::promptStealShortcutSystemwide(QWidget *parent, const QStringList &actionIdentifier, 00535 const QKeySequence &seq) 00536 { 00537 if (actionIdentifier.size() < 4) { 00538 return false; 00539 } 00540 QString title = i18n("Conflict with Global Shortcut"); 00541 QString message = i18n("The '%1' key combination has already been allocated " 00542 "to the global action \"%2\" in %3.\n" 00543 "Do you want to reassign it from that action to the current one?", 00544 seq.toString(), actionIdentifier.at(KGlobalAccel::ActionFriendly), 00545 actionIdentifier.at(KGlobalAccel::ComponentFriendly)); 00546 00547 return KMessageBox::warningContinueCancel(parent, message, title, KGuiItem(i18n("Reassign"))) 00548 == KMessageBox::Continue; 00549 } 00550 #endif 00551 00552 00553 //static 00554 bool KGlobalAccel::promptStealShortcutSystemwide( 00555 QWidget *parent, 00556 const QList<KGlobalShortcutInfo> &shortcuts, 00557 const QKeySequence &seq) 00558 { 00559 if (shortcuts.isEmpty()) { 00560 // Usage error. Just say no 00561 return false; 00562 } 00563 00564 QString component = shortcuts[0].componentFriendlyName(); 00565 00566 QString message; 00567 if (shortcuts.size()==1) { 00568 message = i18n("The '%1' key combination is registered by application %2 for action %3:", 00569 seq.toString(), 00570 component, 00571 shortcuts[0].friendlyName()); 00572 } else { 00573 QString actionList; 00574 Q_FOREACH(const KGlobalShortcutInfo &info, shortcuts) { 00575 actionList += i18n("In context '%1' for action '%2'\n", 00576 info.contextFriendlyName(), 00577 info.friendlyName()); 00578 } 00579 message = i18n("The '%1' key combination is registered by application %2.\n%3", 00580 seq.toString(), 00581 component, 00582 actionList); 00583 } 00584 00585 QString title = i18n("Conflict With Registered Global Shortcut"); 00586 00587 return KMessageBox::warningContinueCancel(parent, message, title, KGuiItem(i18n("Reassign"))) 00588 == KMessageBox::Continue; 00589 } 00590 00591 00592 //static 00593 void KGlobalAccel::stealShortcutSystemwide(const QKeySequence &seq) 00594 { 00595 //get the shortcut, remove seq, and set the new shortcut 00596 const QStringList actionId = self()->d->iface.action(seq[0]); 00597 if (actionId.size() < 4) // not a global shortcut 00598 return; 00599 QList<int> sc = self()->d->iface.shortcut(actionId); 00600 00601 for (int i = 0; i < sc.count(); i++) 00602 if (sc[i] == seq[0]) 00603 sc[i] = 0; 00604 00605 self()->d->iface.setForeignShortcut(actionId, sc); 00606 } 00607 00608 #include "kglobalaccel.moc" 00609
KDE 4.6 API Reference