Files
flameshot/external/QHotkey/qhotkey_x11.cpp
2021-01-15 17:54:56 -06:00

260 lines
7.8 KiB
C++

#include "qhotkey.h"
#include "qhotkey_p.h"
#include <QDebug>
#include <QThreadStorage>
#include <QTimer>
#include <QX11Info>
#include <X11/Xlib.h>
#include <xcb/xcb.h>
// compability to pre Qt 5.8
#ifndef Q_FALLTHROUGH
#define Q_FALLTHROUGH() (void)0
#endif
class QHotkeyPrivateX11 : public QHotkeyPrivate
{
public:
// QAbstractNativeEventFilter interface
bool nativeEventFilter(const QByteArray& eventType,
void* message,
long* result) Q_DECL_OVERRIDE;
protected:
// QHotkeyPrivate interface
quint32 nativeKeycode(Qt::Key keycode, bool& ok) Q_DECL_OVERRIDE;
quint32 nativeModifiers(Qt::KeyboardModifiers modifiers,
bool& ok) Q_DECL_OVERRIDE;
static QString getX11String(Qt::Key keycode);
bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE;
bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE;
private:
static const QVector<quint32> specialModifiers;
static const quint32 validModsMask;
xcb_key_press_event_t prevHandledEvent;
xcb_key_press_event_t prevEvent;
static QString formatX11Error(Display* display, int errorCode);
class HotkeyErrorHandler
{
public:
HotkeyErrorHandler();
~HotkeyErrorHandler();
static bool hasError;
static QString errorString;
private:
XErrorHandler prevHandler;
static int handleError(Display* display, XErrorEvent* error);
};
};
NATIVE_INSTANCE(QHotkeyPrivateX11)
bool QHotkeyPrivate::isPlatformSupported()
{
return QX11Info::isPlatformX11();
}
const QVector<quint32> QHotkeyPrivateX11::specialModifiers = { 0,
Mod2Mask,
LockMask,
(Mod2Mask |
LockMask) };
const quint32 QHotkeyPrivateX11::validModsMask =
ShiftMask | ControlMask | Mod1Mask | Mod4Mask;
bool QHotkeyPrivateX11::nativeEventFilter(const QByteArray& eventType,
void* message,
long* result)
{
Q_UNUSED(eventType)
Q_UNUSED(result)
auto* genericEvent = static_cast<xcb_generic_event_t*>(message);
if (genericEvent->response_type == XCB_KEY_PRESS) {
xcb_key_press_event_t keyEvent =
*static_cast<xcb_key_press_event_t*>(message);
this->prevEvent = keyEvent;
if (this->prevHandledEvent.response_type == XCB_KEY_RELEASE) {
if (this->prevHandledEvent.time == keyEvent.time)
return false;
}
this->activateShortcut(
{ keyEvent.detail,
keyEvent.state & QHotkeyPrivateX11::validModsMask });
} else if (genericEvent->response_type == XCB_KEY_RELEASE) {
xcb_key_release_event_t keyEvent =
*static_cast<xcb_key_release_event_t*>(message);
this->prevEvent = keyEvent;
QTimer::singleShot(50, [this, keyEvent] {
if (this->prevEvent.time == keyEvent.time &&
this->prevEvent.response_type == keyEvent.response_type &&
this->prevEvent.detail == keyEvent.detail) {
this->releaseShortcut(
{ keyEvent.detail,
keyEvent.state & QHotkeyPrivateX11::validModsMask });
}
});
this->prevHandledEvent = keyEvent;
}
return false;
}
QString QHotkeyPrivateX11::getX11String(Qt::Key keycode)
{
switch (keycode) {
case Qt::Key_MediaLast:
case Qt::Key_MediaPrevious:
return QStringLiteral("XF86AudioPrev");
case Qt::Key_MediaNext:
return QStringLiteral("XF86AudioNext");
case Qt::Key_MediaPause:
case Qt::Key_MediaPlay:
case Qt::Key_MediaTogglePlayPause:
return QStringLiteral("XF86AudioPlay");
case Qt::Key_MediaRecord:
return QStringLiteral("XF86AudioRecord");
case Qt::Key_MediaStop:
return QStringLiteral("XF86AudioStop");
default:
return QKeySequence(keycode).toString(QKeySequence::NativeText);
}
}
quint32 QHotkeyPrivateX11::nativeKeycode(Qt::Key keycode, bool& ok)
{
QString keyString = getX11String(keycode);
KeySym keysym = XStringToKeysym(keyString.toLatin1().constData());
if (keysym == NoSymbol) {
// not found -> just use the key
if (keycode <= 0xFFFF)
keysym = keycode;
else
return 0;
}
if (QX11Info::isPlatformX11()) {
auto res = XKeysymToKeycode(QX11Info::display(), keysym);
if (res != 0)
ok = true;
return res;
}
return 0;
}
quint32 QHotkeyPrivateX11::nativeModifiers(Qt::KeyboardModifiers modifiers,
bool& ok)
{
quint32 nMods = 0;
if (modifiers & Qt::ShiftModifier)
nMods |= ShiftMask;
if (modifiers & Qt::ControlModifier)
nMods |= ControlMask;
if (modifiers & Qt::AltModifier)
nMods |= Mod1Mask;
if (modifiers & Qt::MetaModifier)
nMods |= Mod4Mask;
ok = true;
return nMods;
}
bool QHotkeyPrivateX11::registerShortcut(QHotkey::NativeShortcut shortcut)
{
Display* display = QX11Info::display();
if (!display || !QX11Info::isPlatformX11())
return false;
HotkeyErrorHandler errorHandler;
for (quint32 specialMod : QHotkeyPrivateX11::specialModifiers) {
XGrabKey(display,
shortcut.key,
shortcut.modifier | specialMod,
DefaultRootWindow(display),
True,
GrabModeAsync,
GrabModeAsync);
}
XSync(display, False);
if (errorHandler.hasError) {
error = errorHandler.errorString;
this->unregisterShortcut(shortcut);
return false;
}
return true;
}
bool QHotkeyPrivateX11::unregisterShortcut(QHotkey::NativeShortcut shortcut)
{
Display* display = QX11Info::display();
if (!display)
return false;
HotkeyErrorHandler errorHandler;
for (quint32 specialMod : QHotkeyPrivateX11::specialModifiers) {
XUngrabKey(display,
shortcut.key,
shortcut.modifier | specialMod,
XDefaultRootWindow(display));
}
XSync(display, False);
if (HotkeyErrorHandler::hasError) {
error = HotkeyErrorHandler::errorString;
return false;
}
return true;
}
QString QHotkeyPrivateX11::formatX11Error(Display* display, int errorCode)
{
char errStr[256];
XGetErrorText(display, errorCode, errStr, 256);
return QString::fromLatin1(errStr);
}
// ---------- QHotkeyPrivateX11::HotkeyErrorHandler implementation ----------
bool QHotkeyPrivateX11::HotkeyErrorHandler::hasError = false;
QString QHotkeyPrivateX11::HotkeyErrorHandler::errorString;
QHotkeyPrivateX11::HotkeyErrorHandler::HotkeyErrorHandler()
{
prevHandler = XSetErrorHandler(&HotkeyErrorHandler::handleError);
}
QHotkeyPrivateX11::HotkeyErrorHandler::~HotkeyErrorHandler()
{
XSetErrorHandler(prevHandler);
hasError = false;
errorString.clear();
}
int QHotkeyPrivateX11::HotkeyErrorHandler::handleError(Display* display,
XErrorEvent* error)
{
switch (error->error_code) {
case BadAccess:
case BadValue:
case BadWindow:
if (error->request_code == 33 || // grab key
error->request_code == 34) { // ungrab key
hasError = true;
errorString =
QHotkeyPrivateX11::formatX11Error(display, error->error_code);
return 1;
}
Q_FALLTHROUGH();
// fall through
default:
return 0;
}
}