You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
10 KiB
246 lines
10 KiB
/******************************************************************** |
|
KSld - the KDE Screenlocker Daemon |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org> |
|
|
|
This program is free software; you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or |
|
(at your option) any later version. |
|
|
|
This program 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 General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
*********************************************************************/ |
|
#include "globalaccel.h" |
|
|
|
#include <KKeyServer> |
|
#include <netwm.h> |
|
|
|
#include <QDBusConnection> |
|
#include <QDBusMessage> |
|
#include <QDBusPendingReply> |
|
#include <QX11Info> |
|
|
|
#include <xcb/xcb.h> |
|
#include <xcb/xcb_keysyms.h> |
|
#include <X11/keysym.h> |
|
|
|
static const QString s_kglobalAccelService = QStringLiteral("org.kde.kglobalaccel"); |
|
static const QString s_componentInterface = QStringLiteral("org.kde.kglobalaccel.Component"); |
|
|
|
/** |
|
* Whitelist of the components which are allowed to get global shortcuts. |
|
* The DBus path of the component is the key for the whitelist. |
|
* The value for each key contains a list of unique shortcut names which are allowed. |
|
* This allows to not only restrict on component, but also restrict on the shortcuts. |
|
* E.g. plasmashell might accept media shortcuts, but not shortcuts for switching the activity. |
|
**/ |
|
static const QMap<QString, QStringList> s_shortcutWhitelist{ |
|
{QStringLiteral("/component/mediacontrol"), {QStringLiteral("stopmedia"), |
|
QStringLiteral("nextmedia"), |
|
QStringLiteral("previousmedia"), |
|
QStringLiteral("playpausemedia")}}, |
|
{QStringLiteral("/component/kmix"), {QStringLiteral("mute"), |
|
QStringLiteral("decrease_volume"), |
|
QStringLiteral("increase_volume")}}, |
|
{QStringLiteral("/component/kded5"), {QStringLiteral("Increase Screen Brightness"), |
|
QStringLiteral("Decrease Screen Brightness"), |
|
QStringLiteral("Increase Keyboard Brightness"), |
|
QStringLiteral("Decrease Keyboard Brightness")}}, |
|
{QStringLiteral("/component/KDE_Keyboard_Layout_Switcher"), {QStringLiteral("Switch to Next Keyboard Layout")}} |
|
}; |
|
|
|
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(); |
|
} |
|
|
|
GlobalAccel::GlobalAccel(QObject *parent) |
|
: QObject(parent) |
|
{ |
|
} |
|
|
|
void GlobalAccel::prepare() |
|
{ |
|
// recursion check |
|
if (m_updatingInformation) { |
|
return; |
|
} |
|
// first ensure that we don't have some left over |
|
release(); |
|
|
|
m_keySymbols = xcb_key_symbols_alloc(QX11Info::connection()); |
|
calculateGrabMasks(); |
|
|
|
// fetch all compnents from KGlobalAccel |
|
m_updatingInformation++; |
|
auto message = QDBusMessage::createMethodCall(s_kglobalAccelService, |
|
QStringLiteral("/kglobalaccel"), |
|
QStringLiteral("org.kde.KGlobalAccel"), |
|
QStringLiteral("allComponents")); |
|
QDBusPendingReply<QList<QDBusObjectPath>> async = QDBusConnection::sessionBus().asyncCall(message); |
|
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); |
|
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, &GlobalAccel::components); |
|
} |
|
|
|
void GlobalAccel::components(QDBusPendingCallWatcher *self) |
|
{ |
|
QDBusPendingReply<QList<QDBusObjectPath>> reply = *self; |
|
self->deleteLater(); |
|
if (!reply.isValid()) { |
|
m_updatingInformation--; |
|
return; |
|
} |
|
// go through all components, check whether they are in our whitelist |
|
// if they are whitelisted we check whether they are active |
|
for (const auto &path : reply.value()) { |
|
const QString objectPath = path.path(); |
|
bool whitelisted = false; |
|
for (auto it = s_shortcutWhitelist.begin(); it != s_shortcutWhitelist.end(); ++it) { |
|
if (objectPath == it.key()) { |
|
whitelisted = true; |
|
break; |
|
} |
|
} |
|
if (!whitelisted) { |
|
continue; |
|
} |
|
auto message = QDBusMessage::createMethodCall(s_kglobalAccelService, |
|
objectPath, |
|
s_componentInterface, |
|
QStringLiteral("isActive")); |
|
QDBusPendingReply<bool> async = QDBusConnection::sessionBus().asyncCall(message); |
|
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); |
|
m_updatingInformation++; |
|
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, |
|
[this, objectPath] (QDBusPendingCallWatcher *self) { |
|
QDBusPendingReply<bool> reply = *self; |
|
self->deleteLater(); |
|
// filter out inactive components |
|
if (!reply.isValid() || !reply.value()) { |
|
m_updatingInformation--; |
|
return; |
|
} |
|
|
|
// active, whitelisted component: get all shortcuts |
|
auto message = QDBusMessage::createMethodCall(s_kglobalAccelService, |
|
objectPath, |
|
s_componentInterface, |
|
QStringLiteral("allShortcutInfos")); |
|
QDBusPendingReply<QList<KGlobalShortcutInfo>> async = QDBusConnection::sessionBus().asyncCall(message); |
|
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); |
|
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, |
|
[this, objectPath] (QDBusPendingCallWatcher *self) { |
|
m_updatingInformation--; |
|
QDBusPendingReply<QList<KGlobalShortcutInfo>> reply = *self; |
|
self->deleteLater(); |
|
if (!reply.isValid()) { |
|
return; |
|
} |
|
// restrict to whitelist |
|
QList<KGlobalShortcutInfo> infos; |
|
auto whitelist = s_shortcutWhitelist.constFind(objectPath); |
|
if (whitelist == s_shortcutWhitelist.constEnd()) { |
|
// this should not happen, just for safety |
|
return; |
|
} |
|
const auto s = reply.value(); |
|
for (auto it = s.begin(); it != s.end(); ++it) { |
|
if (whitelist.value().contains((*it).uniqueName())) { |
|
infos.append(*it); |
|
} |
|
} |
|
m_shortcuts.insert(objectPath, infos); |
|
} |
|
); |
|
} |
|
); |
|
} |
|
m_updatingInformation--; |
|
} |
|
|
|
void GlobalAccel::release() |
|
{ |
|
m_shortcuts.clear(); |
|
if (m_keySymbols) { |
|
xcb_key_symbols_free(m_keySymbols); |
|
m_keySymbols = nullptr; |
|
} |
|
} |
|
|
|
bool GlobalAccel::checkKeyPress(xcb_key_press_event_t *event) |
|
{ |
|
if (!m_keySymbols) { |
|
return false; |
|
} |
|
// based and inspired from code in kglobalaccel_x11.cpp |
|
xcb_keycode_t keyCodeX = event->detail; |
|
uint16_t keyModX = event->state & (g_keyModMaskXAccel | KKeyServer::MODE_SWITCH); |
|
|
|
xcb_keysym_t keySymX = xcb_key_press_lookup_keysym(m_keySymbols, event, 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 (event->state & KKeyServer::modXNumLock()) { |
|
xcb_keysym_t sym = xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 0); |
|
// If this is a keypad key, |
|
if (sym >= XK_KP_Space && sym <= XK_KP_9) { |
|
switch (sym) { |
|
// 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: |
|
break; |
|
|
|
default: |
|
keyModX ^= KKeyServer::modXShift(); |
|
} |
|
} |
|
} |
|
|
|
int keyCodeQt; |
|
int keyModQt; |
|
KKeyServer::symXToKeyQt(keySymX, &keyCodeQt); |
|
KKeyServer::modXToQt(keyModX, &keyModQt); |
|
|
|
if (keyModQt & Qt::SHIFT && !KKeyServer::isShiftAsModifierAllowed(keyCodeQt)) { |
|
keyModQt &= ~Qt::SHIFT; |
|
} |
|
|
|
if ((keyModQt == 0 || keyModQt == Qt::SHIFT) && (keyCodeQt >= Qt::Key_Space && keyCodeQt <= Qt::Key_AsciiTilde)) { |
|
// security check: we don't allow shortcuts without modifier for "normal" keys |
|
// this is to prevent a malicious application to grab shortcuts for all keys |
|
// and by that being able to read out the keyboard |
|
return false; |
|
} |
|
|
|
const QKeySequence seq(keyCodeQt | keyModQt); |
|
// let's check whether we have a mapping shortcut |
|
for (auto it = m_shortcuts.begin(); it != m_shortcuts.end(); ++it) { |
|
for (const auto &info : it.value()) { |
|
if (info.keys().contains(seq)) { |
|
auto signal = QDBusMessage::createMethodCall(s_kglobalAccelService, it.key(), s_componentInterface, QStringLiteral("invokeShortcut")); |
|
signal.setArguments(QList<QVariant>{QVariant(info.uniqueName())}); |
|
QDBusConnection::sessionBus().asyncCall(signal); |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
}
|
|
|