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"
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
#include "kapplication.h"
|
|
|
|
#include "klocale.h"
|
|
|
|
#include "kaboutdata.h"
|
2014-11-13 01:04:59 +02:00
|
|
|
#include "kaction_p.h"
|
|
|
|
#include "kmessagebox.h"
|
2024-04-21 22:05:44 +03:00
|
|
|
#include "kkeyserver.h"
|
|
|
|
#include "kxerrorhandler.h"
|
|
|
|
#include "kdebug.h"
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2024-04-25 07:24:32 +03:00
|
|
|
// for reference:
|
|
|
|
// https://tronche.com/gui/x/xlib/input/XGrabKey.html
|
|
|
|
// https://tronche.com/gui/x/xlib/input/XUngrabKey.html
|
|
|
|
|
2024-04-25 07:14:06 +03:00
|
|
|
// see kdebug.areas
|
|
|
|
static const int s_kglobalaccelarea = 125;
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
K_GLOBAL_STATIC(KGlobalAccel, kGlobalAccel)
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2024-04-21 22:05:44 +03: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) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kWarning(s_kglobalaccelarea) << "grabKey: got X error " << e->type << " instead of BadAccess";
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
2024-04-21 22:05:44 +03:00
|
|
|
return 1;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
static bool kGrabKey(const int keyQt, uint &keyModX, int &keyCodeX)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
if (keyQt == 0) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kDebug(s_kglobalaccelarea) << "null keyQt";
|
2024-04-21 22:05:44 +03:00
|
|
|
return false;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
Display* display = QX11Info::display();
|
|
|
|
const Qt::HANDLE approotwindow = QX11Info::appRootWindow();
|
|
|
|
if (!display || !approotwindow) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kWarning(s_kglobalaccelarea) << "null display or application root window";
|
2024-04-21 22:05:44 +03:00
|
|
|
return false;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
uint keySymX = 0;
|
|
|
|
if (!KKeyServer::keyQtToModX(keyQt, &keyModX)) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kWarning(s_kglobalaccelarea) << "keyQt (0x" << QByteArray::number(keyQt, 16) << ") failed to resolve to x11 modifier";
|
2024-04-21 22:05:44 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!KKeyServer::keyQtToSymX(keyQt, (int *)&keySymX) ) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kWarning(s_kglobalaccelarea) << "keyQt (0x" << QByteArray::number(keyQt, 16) << ") failed to resolve to x11 keycode";
|
2024-04-21 22:05:44 +03:00
|
|
|
return false;
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
keyCodeX = XKeysymToKeycode(display, keySymX);
|
|
|
|
if (!keyCodeX) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kWarning(s_kglobalaccelarea) << "keyQt (0x" << QByteArray::number(keyQt, 16) << ") was resolved to x11 keycode 0";
|
2024-04-21 22:05:44 +03:00
|
|
|
return false;
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2024-04-21 22:05:44 +03: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
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
static bool kUngrabKey(const uint keyModX, const int keyCodeX)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
Display* display = QX11Info::display();
|
|
|
|
const Qt::HANDLE approotwindow = QX11Info::appRootWindow();
|
|
|
|
if (!display || !approotwindow) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kWarning(s_kglobalaccelarea) << "null display or application root window";
|
2024-04-21 22:05:44 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
KXErrorHandler handler(XGrabErrorHandler);
|
|
|
|
XUngrabKey(display, keyCodeX, keyModX & KKeyServer::accelModMaskX(), approotwindow);
|
|
|
|
return !handler.error(true);
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
class KGlobalAccelFilter : public QWidget
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-25 07:14:06 +03:00
|
|
|
Q_OBJECT
|
2024-04-21 22:05:44 +03:00
|
|
|
public:
|
2024-04-25 07:14:06 +03:00
|
|
|
KGlobalAccelFilter();
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
QList<KGlobalAccelStruct> shortcuts;
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2024-04-25 07:14:06 +03:00
|
|
|
private Q_SLOTS:
|
|
|
|
void slotBlockShortcuts(int data);
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
protected:
|
|
|
|
bool x11Event(XEvent *xevent) final;
|
2024-04-25 07:14:06 +03:00
|
|
|
|
|
|
|
private:
|
|
|
|
int m_block;
|
2024-04-21 22:05:44 +03:00
|
|
|
};
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2024-04-25 07:14:06 +03:00
|
|
|
KGlobalAccelFilter::KGlobalAccelFilter()
|
|
|
|
: QWidget(),
|
|
|
|
m_block(0)
|
|
|
|
{
|
|
|
|
connect(
|
|
|
|
KGlobalSettings::self(), SIGNAL(blockShortcuts(int)),
|
|
|
|
this, SLOT(slotBlockShortcuts(int))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
bool KGlobalAccelFilter::x11Event(XEvent *xevent)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-25 07:14:06 +03:00
|
|
|
if (m_block) {
|
|
|
|
return false;
|
|
|
|
}
|
2024-04-21 22:05:44 +03:00
|
|
|
if (xevent->type == KeyPress) {
|
|
|
|
foreach (const KGlobalAccelStruct &shortcut, shortcuts) {
|
|
|
|
if (xevent->xkey.state == shortcut.keyModX && xevent->xkey.keycode == shortcut.keyCodeX) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kDebug(s_kglobalaccelarea) << "triggering action" << shortcut.keyModX << shortcut.keyCodeX << shortcut.action;
|
2024-04-21 22:05:44 +03:00
|
|
|
shortcut.action->trigger();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-25 07:14:06 +03: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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
KGlobalAccelPrivate::KGlobalAccelPrivate(KGlobalAccel *_q)
|
|
|
|
: q(_q),
|
|
|
|
filter(nullptr)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
if (kapp) {
|
|
|
|
filter = new KGlobalAccelFilter();
|
|
|
|
kapp->installX11EventFilter(filter);
|
2024-04-25 07:14:06 +03:00
|
|
|
kDebug(s_kglobalaccelarea) << "KGlobalAccelFilter is installed";
|
2024-04-21 22:05:44 +03:00
|
|
|
} else {
|
2024-04-25 07:14:06 +03:00
|
|
|
kWarning(s_kglobalaccelarea) << "no KApplication instance, KGlobalAccel will not work";
|
2022-09-27 10:46:46 +03:00
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
KGlobalAccelPrivate::~KGlobalAccelPrivate()
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
if (filter) {
|
|
|
|
if (kapp) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kDebug(s_kglobalaccelarea) << "removing KGlobalAccelFilter";
|
2024-04-21 22:05:44 +03:00
|
|
|
kapp->removeX11EventFilter(filter);
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
QList<KGlobalAccelStruct> shortcuts = filter->shortcuts;
|
2024-04-25 07:14:06 +03:00
|
|
|
kDebug(s_kglobalaccelarea) << "releasing shortcuts" << shortcuts.size();
|
2024-04-21 22:05:44 +03:00
|
|
|
foreach (const KGlobalAccelStruct &shortcut, shortcuts) {
|
2024-04-22 02:51:29 +03:00
|
|
|
remove(shortcut.action);
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
2024-04-21 22:05:44 +03:00
|
|
|
delete filter;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
|
2024-04-24 23:37:43 +03:00
|
|
|
bool KGlobalAccelPrivate::updateGlobalShortcut(KAction *action)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-22 02:51:29 +03:00
|
|
|
if (!remove(action)) {
|
2024-04-24 23:37:43 +03:00
|
|
|
return false;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
2024-04-24 23:37:43 +03:00
|
|
|
return doRegister(action);
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-24 23:37:43 +03:00
|
|
|
bool KGlobalAccelPrivate::doRegister(KAction *action)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-24 09:24:13 +03:00
|
|
|
const QKeySequence keysequence = action->globalShortcut();
|
|
|
|
for (int i = 0; i < keysequence.count(); i++) {
|
2024-04-21 22:05:44 +03:00
|
|
|
uint keyModX = 0;
|
|
|
|
int keyCodeX = 0;
|
2024-04-24 09:24:13 +03:00
|
|
|
if (kGrabKey(keysequence[i], keyModX, keyCodeX)) {
|
2024-04-21 22:05:44 +03:00
|
|
|
KGlobalAccelStruct shortcut;
|
|
|
|
shortcut.action = action;
|
|
|
|
shortcut.keyModX = keyModX;
|
|
|
|
shortcut.keyCodeX = keyCodeX;
|
|
|
|
filter->shortcuts.append(shortcut);
|
2024-04-25 07:14:06 +03:00
|
|
|
kDebug(s_kglobalaccelarea) << "grabbed shortcut" << shortcut.keyModX << shortcut.keyCodeX << shortcut.action;
|
2024-04-24 23:37:43 +03:00
|
|
|
return true;
|
2024-04-21 22:05:44 +03:00
|
|
|
} else {
|
2024-04-25 07:14:06 +03:00
|
|
|
kWarning(s_kglobalaccelarea) << "could not grab shortcut" << keysequence[i] << action;
|
2024-04-21 22:05:44 +03:00
|
|
|
}
|
|
|
|
}
|
2024-04-24 23:37:43 +03:00
|
|
|
return false;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-22 02:51:29 +03:00
|
|
|
bool KGlobalAccelPrivate::remove(KAction *action)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
foreach (const KGlobalAccelStruct &shortcut, filter->shortcuts) {
|
|
|
|
if (shortcut.action == action) {
|
|
|
|
if (kUngrabKey(shortcut.keyModX, shortcut.keyCodeX)) {
|
2024-04-25 07:14:06 +03:00
|
|
|
kDebug(s_kglobalaccelarea) << "ungrabbed shortcut" << shortcut.keyModX << shortcut.keyCodeX << shortcut.action;
|
2024-04-21 22:05:44 +03:00
|
|
|
filter->shortcuts.removeOne(shortcut);
|
|
|
|
return true;
|
|
|
|
}
|
2024-04-25 07:14:06 +03:00
|
|
|
kWarning(s_kglobalaccelarea) << "could not ungrab shortcut" << shortcut.keyModX << shortcut.keyCodeX << shortcut.action;
|
2024-04-21 22:05:44 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
KGlobalAccel::KGlobalAccel()
|
|
|
|
: d(new KGlobalAccelPrivate(this))
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
KGlobalAccel::~KGlobalAccel()
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
delete d;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
KGlobalAccel* KGlobalAccel::self()
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
return kGlobalAccel;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
QList<KGlobalShortcutInfo> KGlobalAccel::getGlobalShortcutsByKey(const QKeySequence &seq)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
QList<KGlobalShortcutInfo> result;
|
|
|
|
foreach (const KGlobalAccelStruct &shortcut, d->filter->shortcuts) {
|
2024-04-24 09:24:13 +03:00
|
|
|
if (shortcut.action->globalShortcut().matches(seq) != QKeySequence::NoMatch) {
|
2024-04-21 22:05:44 +03:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2024-04-21 22:05:44 +03:00
|
|
|
return result;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
bool KGlobalAccel::isGlobalShortcutAvailable(const QKeySequence &seq, const QString &comp)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
foreach (const KGlobalAccelStruct &shortcut, d->filter->shortcuts) {
|
2024-04-24 09:24:13 +03:00
|
|
|
if (shortcut.action->globalShortcut().matches(seq) != QKeySequence::NoMatch) {
|
2024-04-21 22:05:44 +03:00
|
|
|
return false;
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
2024-04-21 22:05:44 +03:00
|
|
|
return true;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
void KGlobalAccel::stealShortcutSystemwide(const QKeySequence &seq)
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
2024-04-21 22:05:44 +03:00
|
|
|
foreach (const KGlobalAccelStruct &shortcut, d->filter->shortcuts) {
|
2024-04-24 09:24:13 +03:00
|
|
|
if (shortcut.action->globalShortcut().matches(seq) != QKeySequence::NoMatch) {
|
2024-04-24 23:41:04 +03:00
|
|
|
shortcut.action->setGlobalShortcut(QKeySequence());
|
2024-04-22 02:51:29 +03:00
|
|
|
d->remove(shortcut.action);
|
2024-04-21 22:05:44 +03:00
|
|
|
break;
|
|
|
|
}
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03: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;
|
|
|
|
}
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
QString component = shortcuts[0].componentFriendlyName;
|
2014-11-13 01:04:59 +02:00
|
|
|
|
|
|
|
QString message;
|
2024-04-21 22:05:44 +03:00
|
|
|
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,
|
2024-04-21 22:05:44 +03:00
|
|
|
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",
|
2024-04-21 22:05:44 +03:00
|
|
|
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");
|
|
|
|
|
2024-04-21 22:05:44 +03:00
|
|
|
return KMessageBox::warningContinueCancel(
|
|
|
|
parent, message, title, KGuiItem(i18n("Reassign"))
|
|
|
|
) == KMessageBox::Continue;
|
2014-11-13 01:04:59 +02:00
|
|
|
}
|
|
|
|
|
2015-02-27 07:40:26 +00:00
|
|
|
#include "moc_kglobalaccel.cpp"
|
2024-04-25 07:14:06 +03:00
|
|
|
#include "kglobalaccel.moc"
|