kdelibs/kdeui/shortcuts/kglobalaccel.cpp

338 lines
11 KiB
C++
Raw Normal View History

2014-11-13 01:04:59 +02:00
/* This file is part of the KDE libraries
Copyright (C) 2001,2002 Ellis Whitehead <ellis@kde.org>
Copyright (C) 2006 Hamish Rodda <rodda@kde.org>
Copyright (C) 2007 Andreas Hartmetz <ahartmetz@gmail.com>
Copyright (C) 2008 Michael Jansen <kde@michael-jansen.biz>
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 "kapplication.h"
#include "klocale.h"
#include "kaboutdata.h"
2014-11-13 01:04:59 +02:00
#include "kaction_p.h"
#include "kmessagebox.h"
#include "kkeyserver.h"
#include "kxerrorhandler.h"
#include "kdebug.h"
2014-11-13 01:04:59 +02:00
// for reference:
// https://tronche.com/gui/x/xlib/input/XGrabKey.html
// https://tronche.com/gui/x/xlib/input/XUngrabKey.html
// see kdebug.areas
static const int s_kglobalaccelarea = 125;
K_GLOBAL_STATIC(KGlobalAccel, kGlobalAccel)
2014-11-13 01:04:59 +02:00
struct KGlobalAccelStruct
{
KAction* action;
uint keyModX;
int keyCodeX;
bool operator==(const KGlobalAccelStruct &other) const
{ return (action == other.action && keyModX == other.keyModX && keyCodeX == other.keyCodeX); }
};
extern "C" {
static int XGrabErrorHandler(Display *, XErrorEvent *e) {
if (e->error_code != BadAccess) {
kWarning(s_kglobalaccelarea) << "grabKey: got X error " << e->type << " instead of BadAccess";
2014-11-13 01:04:59 +02:00
}
return 1;
2014-11-13 01:04:59 +02:00
}
}
static bool kGrabKey(const int keyQt, uint &keyModX, int &keyCodeX)
2014-11-13 01:04:59 +02:00
{
if (keyQt == 0) {
kDebug(s_kglobalaccelarea) << "null keyQt";
return false;
2014-11-13 01:04:59 +02:00
}
Display* display = QX11Info::display();
const Qt::HANDLE approotwindow = QX11Info::appRootWindow();
if (!display || !approotwindow) {
kWarning(s_kglobalaccelarea) << "null display or application root window";
return false;
2014-11-13 01:04:59 +02:00
}
uint keySymX = 0;
if (!KKeyServer::keyQtToModX(keyQt, &keyModX)) {
kWarning(s_kglobalaccelarea) << "keyQt (0x" << QByteArray::number(keyQt, 16) << ") failed to resolve to x11 modifier";
return false;
}
if (!KKeyServer::keyQtToSymX(keyQt, (int *)&keySymX) ) {
kWarning(s_kglobalaccelarea) << "keyQt (0x" << QByteArray::number(keyQt, 16) << ") failed to resolve to x11 keycode";
return false;
}
2014-11-13 01:04:59 +02:00
keyCodeX = XKeysymToKeycode(display, keySymX);
if (!keyCodeX) {
kWarning(s_kglobalaccelarea) << "keyQt (0x" << QByteArray::number(keyQt, 16) << ") was resolved to x11 keycode 0";
return false;
}
2014-11-13 01:04:59 +02:00
KXErrorHandler handler(XGrabErrorHandler);
XGrabKey(
display, keyCodeX, keyModX & KKeyServer::accelModMaskX(),
approotwindow, True, GrabModeAsync, GrabModeAsync
);
return !handler.error(true);
2014-11-13 01:04:59 +02:00
}
static bool kUngrabKey(const uint keyModX, const int keyCodeX)
2014-11-13 01:04:59 +02:00
{
Display* display = QX11Info::display();
const Qt::HANDLE approotwindow = QX11Info::appRootWindow();
if (!display || !approotwindow) {
kWarning(s_kglobalaccelarea) << "null display or application root window";
return false;
}
KXErrorHandler handler(XGrabErrorHandler);
XUngrabKey(display, keyCodeX, keyModX & KKeyServer::accelModMaskX(), approotwindow);
return !handler.error(true);
2014-11-13 01:04:59 +02:00
}
class KGlobalAccelFilter : public QWidget
2014-11-13 01:04:59 +02:00
{
Q_OBJECT
public:
KGlobalAccelFilter();
QList<KGlobalAccelStruct> shortcuts;
2014-11-13 01:04:59 +02:00
private Q_SLOTS:
void slotBlockShortcuts(int data);
protected:
bool x11Event(XEvent *xevent) final;
private:
int m_block;
};
2014-11-13 01:04:59 +02:00
KGlobalAccelFilter::KGlobalAccelFilter()
: QWidget(),
m_block(0)
{
connect(
KGlobalSettings::self(), SIGNAL(blockShortcuts(int)),
this, SLOT(slotBlockShortcuts(int))
);
}
bool KGlobalAccelFilter::x11Event(XEvent *xevent)
2014-11-13 01:04:59 +02:00
{
if (xevent->type == KeyPress) {
if (m_block) {
return false;
}
foreach (const KGlobalAccelStruct &shortcut, shortcuts) {
if (xevent->xkey.state == shortcut.keyModX && xevent->xkey.keycode == shortcut.keyCodeX) {
kDebug(s_kglobalaccelarea) << "triggering action" << shortcut.keyModX << shortcut.keyCodeX << shortcut.action;
shortcut.action->trigger();
return true;
}
}
}
return false;
2014-11-13 01:04:59 +02:00
}
void KGlobalAccelFilter::slotBlockShortcuts(int data)
{
if (data) {
m_block++;
kDebug(s_kglobalaccelarea) << "shorcuts block request" << m_block;
} else if (m_block) {
m_block--;
kDebug(s_kglobalaccelarea) << "shorcuts unblock request" << m_block;
}
}
KGlobalAccelPrivate::KGlobalAccelPrivate(KGlobalAccel *_q)
: q(_q),
filter(nullptr)
2014-11-13 01:04:59 +02:00
{
if (kapp) {
filter = new KGlobalAccelFilter();
kapp->installX11EventFilter(filter);
kDebug(s_kglobalaccelarea) << "KGlobalAccelFilter is installed";
} else {
kWarning(s_kglobalaccelarea) << "no KApplication instance, KGlobalAccel will not work";
}
2014-11-13 01:04:59 +02:00
}
KGlobalAccelPrivate::~KGlobalAccelPrivate()
2014-11-13 01:04:59 +02:00
{
if (filter) {
if (kapp) {
kDebug(s_kglobalaccelarea) << "removing KGlobalAccelFilter";
kapp->removeX11EventFilter(filter);
}
2014-11-13 01:04:59 +02:00
QList<KGlobalAccelStruct> shortcuts = filter->shortcuts;
kDebug(s_kglobalaccelarea) << "releasing shortcuts" << shortcuts.size();
foreach (const KGlobalAccelStruct &shortcut, shortcuts) {
remove(shortcut.action);
2014-11-13 01:04:59 +02:00
}
delete filter;
2014-11-13 01:04:59 +02:00
}
}
2014-11-13 01:04:59 +02:00
bool KGlobalAccelPrivate::doRegister(KAction *action)
2014-11-13 01:04:59 +02:00
{
const QKeySequence keysequence = action->globalShortcut();
bool result = false;
for (int i = 0; i < keysequence.count(); i++) {
uint keyModX = 0;
int keyCodeX = 0;
if (kGrabKey(keysequence[i], keyModX, keyCodeX)) {
KGlobalAccelStruct shortcut;
shortcut.action = action;
shortcut.keyModX = keyModX;
shortcut.keyCodeX = keyCodeX;
filter->shortcuts.append(shortcut);
kDebug(s_kglobalaccelarea) << "grabbed shortcut" << shortcut.keyModX << shortcut.keyCodeX << shortcut.action;
// grabbed one, that is success
result = true;
} else {
kWarning(s_kglobalaccelarea) << "could not grab shortcut" << keysequence[i] << action;
}
}
return result;
2014-11-13 01:04:59 +02:00
}
bool KGlobalAccelPrivate::remove(KAction *action)
2014-11-13 01:04:59 +02:00
{
bool result = false;
bool found = false;
QMutableListIterator<KGlobalAccelStruct> iter(filter->shortcuts);
while (iter.hasNext()) {
const KGlobalAccelStruct shortcut = iter.next();
if (shortcut.action == action) {
found = true;
if (kUngrabKey(shortcut.keyModX, shortcut.keyCodeX)) {
kDebug(s_kglobalaccelarea) << "ungrabbed shortcut" << shortcut.keyModX << shortcut.keyCodeX << shortcut.action;
iter.remove();
result = true;
} else {
kWarning(s_kglobalaccelarea) << "could not ungrab shortcut" << shortcut.keyModX << shortcut.keyCodeX << shortcut.action;
}
}
}
return (result || !found);
2014-11-13 01:04:59 +02:00
}
KGlobalAccel::KGlobalAccel()
: d(new KGlobalAccelPrivate(this))
2014-11-13 01:04:59 +02:00
{
}
KGlobalAccel::~KGlobalAccel()
2014-11-13 01:04:59 +02:00
{
delete d;
2014-11-13 01:04:59 +02:00
}
KGlobalAccel* KGlobalAccel::self()
2014-11-13 01:04:59 +02:00
{
return kGlobalAccel;
2014-11-13 01:04:59 +02:00
}
QList<KGlobalShortcutInfo> KGlobalAccel::getGlobalShortcutsByKey(const QKeySequence &seq)
2014-11-13 01:04:59 +02:00
{
QList<KGlobalShortcutInfo> result;
foreach (const KGlobalAccelStruct &shortcut, d->filter->shortcuts) {
if (shortcut.action->globalShortcut().matches(seq) != QKeySequence::NoMatch) {
KGlobalShortcutInfo globalshortcutinfo;
globalshortcutinfo.componentFriendlyName = shortcut.action->d->componentData.aboutData()->programName();
globalshortcutinfo.friendlyName = KGlobal::locale()->removeAcceleratorMarker(shortcut.action->text());
globalshortcutinfo.contextFriendlyName = shortcut.action->objectName();
result.append(globalshortcutinfo);
2014-11-13 01:04:59 +02:00
}
}
return result;
2014-11-13 01:04:59 +02:00
}
bool KGlobalAccel::isGlobalShortcutAvailable(const QKeySequence &seq, const QString &comp)
2014-11-13 01:04:59 +02:00
{
foreach (const KGlobalAccelStruct &shortcut, d->filter->shortcuts) {
if (shortcut.action->globalShortcut().matches(seq) != QKeySequence::NoMatch) {
return false;
}
2014-11-13 01:04:59 +02:00
}
return true;
2014-11-13 01:04:59 +02:00
}
void KGlobalAccel::stealShortcutSystemwide(const QKeySequence &seq)
2014-11-13 01:04:59 +02:00
{
foreach (const KGlobalAccelStruct &shortcut, d->filter->shortcuts) {
if (shortcut.action->globalShortcut().matches(seq) != QKeySequence::NoMatch) {
// TODO: in case of partial match this can steal only the matching one
shortcut.action->setGlobalShortcut(QKeySequence());
d->remove(shortcut.action);
break;
}
2014-11-13 01:04:59 +02:00
}
}
bool KGlobalAccel::promptStealShortcutSystemwide(QWidget *parent,
const QList<KGlobalShortcutInfo> &shortcuts,
const QKeySequence &seq)
2014-11-13 01:04:59 +02:00
{
if (shortcuts.isEmpty()) {
// Usage error. Just say no
return false;
}
QString component = shortcuts[0].componentFriendlyName;
2014-11-13 01:04:59 +02:00
QString message;
if (shortcuts.size() ==1) {
2014-11-13 01:04:59 +02:00
message = i18n("The '%1' key combination is registered by application %2 for action %3:",
seq.toString(),
component,
shortcuts[0].friendlyName);
2014-11-13 01:04:59 +02:00
} else {
QString actionList;
Q_FOREACH(const KGlobalShortcutInfo &info, shortcuts) {
actionList += i18n("In context '%1' for action '%2'\n",
info.contextFriendlyName,
info.friendlyName);
2014-11-13 01:04:59 +02:00
}
message = i18n("The '%1' key combination is registered by application %2.\n%3",
seq.toString(),
component,
actionList);
}
QString title = i18n("Conflict With Registered Global Shortcut");
return KMessageBox::warningContinueCancel(
parent, message, title, KGuiItem(i18n("Reassign"))
) == KMessageBox::Continue;
2014-11-13 01:04:59 +02:00
}
#include "moc_kglobalaccel.cpp"
#include "kglobalaccel.moc"