1
0
mirror of https://github.com/KDE/latte-dock.git synced 2024-12-26 23:21:37 +03:00
latte-dock/app/globalshortcuts.cpp
2018-01-14 23:58:48 +02:00

672 lines
23 KiB
C++

/*
* Copyright 2016 Smith AR <audoban@openmailbox.org>
* Michail Vourlakos <mvourlakos@gmail.com>
*
* This file is part of Latte-Dock
*
* Latte-Dock 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.
*
* Latte-Dock 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 "globalshortcuts.h"
#include <QAction>
#include <QDebug>
#include <QQuickItem>
#include <QMetaMethod>
#include <QX11Info>
#include <KActionCollection>
#include <KGlobalAccel>
#include <KLocalizedString>
#include <KPluginMetaData>
#include <Plasma/Applet>
#include <Plasma/Containment>
// X11
#include <X11/keysym.h>
#include <X11/keysymdef.h>
#include <X11/Xlib.h>
#include <array>
//this code is used by activityswitcher in plasma in order to check if the
//user has release all the modifier keys from the globalshortcut
namespace {
bool isPlatformX11()
{
static const bool isX11 = QX11Info::isPlatformX11();
return isX11;
}
// Taken from kwin/tabbox/tabbox.cpp
Display *x11_display()
{
static Display *s_display = nullptr;
if (!s_display) {
s_display = QX11Info::display();
}
return s_display;
}
bool x11_areKeySymXsDepressed(bool bAll, const uint keySyms[], int nKeySyms)
{
char keymap[32];
XQueryKeymap(x11_display(), keymap);
for (int iKeySym = 0; iKeySym < nKeySyms; iKeySym++) {
uint keySymX = keySyms[ iKeySym ];
uchar keyCodeX = XKeysymToKeycode(x11_display(), keySymX);
int i = keyCodeX / 8;
char mask = 1 << (keyCodeX - (i * 8));
// Abort if bad index value,
if (i < 0 || i >= 32)
return false;
// If ALL keys passed need to be depressed,
if (bAll) {
if ((keymap[i] & mask) == 0)
return false;
} else {
// If we are looking for ANY key press, and this key is depressed,
if (keymap[i] & mask)
return true;
}
}
// If we were looking for ANY key press, then none was found, return false,
// If we were looking for ALL key presses, then all were found, return true.
return bAll;
}
bool x11_areModKeysDepressed(const QKeySequence &seq)
{
uint rgKeySyms[10];
int nKeySyms = 0;
if (seq.isEmpty()) {
return false;
}
int mod = seq[seq.count() - 1] & Qt::KeyboardModifierMask;
if (mod & Qt::SHIFT) {
rgKeySyms[nKeySyms++] = XK_Shift_L;
rgKeySyms[nKeySyms++] = XK_Shift_R;
}
if (mod & Qt::CTRL) {
rgKeySyms[nKeySyms++] = XK_Control_L;
rgKeySyms[nKeySyms++] = XK_Control_R;
}
if (mod & Qt::ALT) {
rgKeySyms[nKeySyms++] = XK_Alt_L;
rgKeySyms[nKeySyms++] = XK_Alt_R;
}
if (mod & Qt::META) {
// It would take some code to determine whether the Win key
// is associated with Super or Meta, so check for both.
// See bug #140023 for details.
rgKeySyms[nKeySyms++] = XK_Super_L;
rgKeySyms[nKeySyms++] = XK_Super_R;
rgKeySyms[nKeySyms++] = XK_Meta_L;
rgKeySyms[nKeySyms++] = XK_Meta_R;
}
return x11_areKeySymXsDepressed(false, rgKeySyms, nKeySyms);
}
}
namespace Latte {
GlobalShortcuts::GlobalShortcuts(QObject *parent)
: QObject(parent)
{
m_corona = qobject_cast<DockCorona *>(parent);
if (m_corona) {
init();
}
m_hideDockTimer.setSingleShot(true);
if (isPlatformX11()) {
//in X11 the timer is a poller that checks to see if the modifier keys
//from user global shortcut have been released
m_hideDockTimer.setInterval(300);
} else {
//on wayland in acting just as simple timer that hides the dock afterwards
m_hideDockTimer.setInterval(3000);
}
connect(&m_hideDockTimer, &QTimer::timeout, this, &GlobalShortcuts::hideDockTimerSlot);
}
GlobalShortcuts::~GlobalShortcuts()
{
}
void GlobalShortcuts::init()
{
KActionCollection *generalActions = new KActionCollection(m_corona);
//show-hide the main dock in the primary screen
QAction *showAction = generalActions->addAction(QStringLiteral("show latte dock"));
showAction->setText(i18n("Show Latte Dock"));
showAction->setShortcut(QKeySequence(Qt::META + '`'));
KGlobalAccel::setGlobalShortcut(showAction, QKeySequence(Qt::META + '`'));
connect(showAction, &QAction::triggered, this, [this]() {
showDock();
});
//show-cycle between Latte settings windows
QAction *settingsAction = generalActions->addAction(QStringLiteral("show settings window"));
settingsAction->setText(i18n("Show Settings Window"));
KGlobalAccel::setGlobalShortcut(settingsAction, QKeySequence(Qt::META + Qt::Key_A));
connect(settingsAction, &QAction::triggered, this, [this] {
showSettings();
});
//show the layouts editor
QAction *layoutsAction = generalActions->addAction(QStringLiteral("show layouts editor"));
layoutsAction->setText(i18n("Show Layouts Editor"));
layoutsAction->setShortcut(QKeySequence(Qt::META + Qt::Key_E));
KGlobalAccel::setGlobalShortcut(layoutsAction, QKeySequence(Qt::META + Qt::Key_E));
connect(layoutsAction, &QAction::triggered, this, [this]() {
showLayoutsEditor();
});
KActionCollection *taskbarActions = new KActionCollection(m_corona);
//activate actions [1-9]
for (int i = 1; i < 10; ++i) {
const int entryNumber = i;
const Qt::Key key = static_cast<Qt::Key>(Qt::Key_0 + i);
QAction *action = taskbarActions->addAction(QStringLiteral("activate task manager entry %1").arg(QString::number(entryNumber)));
action->setText(i18n("Activate Task Manager Entry %1", entryNumber));
action->setShortcut(QKeySequence(Qt::META + key));
KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + key));
connect(action, &QAction::triggered, this, [this, i] {
// qDebug() << "meta action...";
activateTaskManagerEntry(i, static_cast<Qt::Key>(Qt::META));
});
}
//! Array that is used to register correctly actions for task index>=10 and <19
std::array<Qt::Key, 10> keysAboveTen{ Qt::Key_0, Qt::Key_Z, Qt::Key_X, Qt::Key_C, Qt::Key_V, Qt::Key_B, Qt::Key_N, Qt::Key_M, Qt::Key_Comma, Qt::Key_Period };
//activate actions [10-19]
for (int i = 10; i < 20; ++i) {
QAction *action = taskbarActions->addAction(QStringLiteral("activate task manager entry %1").arg(QString::number(i)));
action->setText(i18n("Activate Task Manager Entry %1", i));
action->setShortcut(QKeySequence(Qt::META + keysAboveTen[i - 10]));
KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + keysAboveTen[i - 10]));
connect(action, &QAction::triggered, this, [this, i] {
activateTaskManagerEntry(i, static_cast<Qt::Key>(Qt::META));
});
}
//new instance actions [1-9]
for (int i = 1; i < 9; ++i) {
const int entryNumber = i;
const Qt::Key key = static_cast<Qt::Key>(Qt::Key_0 + i);
QAction *action = taskbarActions->addAction(QStringLiteral("new instance for task manager entry %1").arg(QString::number(entryNumber)));
action->setText(i18n("New Instance for Task Manager Entry %1", entryNumber));
KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + Qt::CTRL + key));
connect(action, &QAction::triggered, this, [this, i] {
// qDebug() << "meta + ctrl + action...";
activateTaskManagerEntry(i, static_cast<Qt::Key>(Qt::CTRL));
});
}
//new instance actions [10-19]
for (int i = 10; i < 20; ++i) {
QAction *action = taskbarActions->addAction(QStringLiteral("new instance for task manager entry %1").arg(QString::number(i)));
action->setText(i18n("New Instance for Task Manager Entry %1", i));
KGlobalAccel::setGlobalShortcut(action, QKeySequence(Qt::META + Qt::CTRL + keysAboveTen[i - 10]));
connect(action, &QAction::triggered, this, [this, i] {
activateTaskManagerEntry(i, static_cast<Qt::Key>(Qt::CTRL));
});
}
}
//! Activate launcher menu through dbus interface
void GlobalShortcuts::activateLauncherMenu()
{
QHash<const Plasma::Containment *, DockView *> *views = m_corona->layoutManager()->currentDockViews();
for (auto it = views->constBegin(), end = views->constEnd(); it != end; ++it) {
const auto applets = it.key()->applets();
for (auto applet : applets) {
const auto provides = applet->kPackage().metadata().value(QStringLiteral("X-Plasma-Provides"));
if (provides.contains(QLatin1String("org.kde.plasma.launchermenu"))) {
emit applet->activated();
return;
}
}
}
}
//! Activate task manager entry
void GlobalShortcuts::activateTaskManagerEntry(int index, Qt::Key modifier)
{
m_lastInvokedAction = dynamic_cast<QAction *>(sender());
auto activateTaskManagerEntryOnContainment = [this](const Plasma::Containment * c, int index, Qt::Key modifier) {
const auto &applets = c->applets();
for (auto *applet : applets) {
const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides"));
if (provides.contains(QLatin1String("org.kde.plasma.multitasking"))) {
if (QQuickItem *appletInterface = applet->property("_plasma_graphicObject").value<QQuickItem *>()) {
const auto &childItems = appletInterface->childItems();
if (childItems.isEmpty()) {
continue;
}
KPluginMetaData meta = applet->kPackage().metadata();
for (QQuickItem *item : childItems) {
if (auto *metaObject = item->metaObject()) {
// not using QMetaObject::invokeMethod to avoid warnings when calling
// this on applets that don't have it or other child items since this
// is pretty much trial and error.
// Also, "var" arguments are treated as QVariant in QMetaObject
int methodIndex = modifier == static_cast<Qt::Key>(Qt::META) ?
metaObject->indexOfMethod("activateTaskAtIndex(QVariant)") :
metaObject->indexOfMethod("newInstanceForTaskAtIndex(QVariant)");
int methodIndex2 = metaObject->indexOfMethod("setShowTasksNumbers(QVariant)");
if (methodIndex == -1 || (methodIndex2 == -1 && meta.pluginId() == "org.kde.latte.plasmoid")) {
continue;
}
m_tasksPlasmoid = item;
m_tasksMethodIndex = methodIndex2;
m_methodShowNumbers = metaObject->method(m_tasksMethodIndex);
QMetaMethod method = metaObject->method(methodIndex);
if (method.invoke(item, Q_ARG(QVariant, index))) {
if (methodIndex2 != -1) {
m_methodShowNumbers.invoke(item, Q_ARG(QVariant, true));
}
return true;
}
}
}
}
}
}
return false;
};
QHash<const Plasma::Containment *, DockView *> *views = m_corona->layoutManager()->currentDockViews();
// To avoid overly complex configuration, we'll try to get the 90% usecase to work
// which is activating a task on the task manager on a panel on the primary screen.
for (auto it = views->constBegin(), end = views->constEnd(); it != end; ++it) {
if (it.value()->screen() != qGuiApp->primaryScreen()) {
continue;
}
if (activateTaskManagerEntryOnContainment(it.key(), index, modifier)) {
m_hideDock = it.value();
m_hideDock->visibility()->setBlockHiding(true);
m_hideDockTimer.start();
return;
}
}
// we didn't find anything on primary, try all the panels
for (auto it = views->constBegin(), end = views->constEnd(); it != end; ++it) {
if (activateTaskManagerEntryOnContainment(it.key(), index, modifier)) {
m_hideDock = it.value();
m_hideDock->visibility()->setBlockHiding(true);
m_hideDockTimer.start();
return;
}
}
}
//! update badge for specific dock item
void GlobalShortcuts::updateDockItemBadge(QString identifier, QString value)
{
//qDebug() << "DBUS CALL ::: " << identifier << " - " << value;
auto updateBadgeForTaskInContainment = [this](const Plasma::Containment * c, QString identifier, QString value) {
const auto &applets = c->applets();
for (auto *applet : applets) {
KPluginMetaData meta = applet->kPackage().metadata();
if (meta.pluginId() == "org.kde.latte.plasmoid") {
if (QQuickItem *appletInterface = applet->property("_plasma_graphicObject").value<QQuickItem *>()) {
const auto &childItems = appletInterface->childItems();
if (childItems.isEmpty()) {
continue;
}
for (QQuickItem *item : childItems) {
if (auto *metaObject = item->metaObject()) {
// not using QMetaObject::invokeMethod to avoid warnings when calling
// this on applets that don't have it or other child items since this
// is pretty much trial and error.
// Also, "var" arguments are treated as QVariant in QMetaObject
int methodIndex = metaObject->indexOfMethod("updateBadge(QVariant,QVariant)");
if (methodIndex == -1) {
continue;
}
QMetaMethod method = metaObject->method(methodIndex);
if (method.invoke(item, Q_ARG(QVariant, identifier), Q_ARG(QVariant, value))) {
return true;
}
}
}
}
}
}
return false;
};
QHash<const Plasma::Containment *, DockView *> *views = m_corona->layoutManager()->currentDockViews();
// update badges in all Latte Tasks plasmoids
for (auto it = views->constBegin(), end = views->constEnd(); it != end; ++it) {
updateBadgeForTaskInContainment(it.key(), identifier, value);
}
}
void GlobalShortcuts::showDock()
{
m_lastInvokedAction = dynamic_cast<QAction *>(sender());
auto containsLattePlasmoid = [this](const Plasma::Containment * c) {
const auto &applets = c->applets();
for (auto *applet : applets) {
KPluginMetaData meta = applet->kPackage().metadata();
if (meta.pluginId() == "org.kde.latte.plasmoid") {
if (QQuickItem *appletInterface = applet->property("_plasma_graphicObject").value<QQuickItem *>()) {
const auto &childItems = appletInterface->childItems();
if (childItems.isEmpty()) {
continue;
}
for (QQuickItem *item : childItems) {
if (auto *metaObject = item->metaObject()) {
// not using QMetaObject::invokeMethod to avoid warnings when calling
// this on applets that don't have it or other child items since this
// is pretty much trial and error.
// Also, "var" arguments are treated as QVariant in QMetaObject
int methodIndex2 = metaObject->indexOfMethod("setShowTasksNumbers(QVariant)");
if (methodIndex2 == -1) {
continue;
}
m_tasksPlasmoid = item;
m_tasksMethodIndex = methodIndex2;
m_methodShowNumbers = metaObject->method(m_tasksMethodIndex);
if (m_methodShowNumbers.invoke(item, Q_ARG(QVariant, true))) {
return true;
}
}
}
}
}
}
return false;
};
QHash<const Plasma::Containment *, DockView *> *views = m_corona->layoutManager()->currentDockViews();
// To avoid overly complex configuration, we'll try to get the 90% usecase to work
// which is activating a task on the task manager on a panel on the primary screen.
for (auto it = views->constBegin(), end = views->constEnd(); it != end; ++it) {
if (it.value()->screen() != qGuiApp->primaryScreen()) {
continue;
}
if (containsLattePlasmoid(it.key())) {
m_hideDock = it.value();
m_hideDock->visibility()->setBlockHiding(true);
m_hideDockTimer.start();
return;
}
}
// we didn't find anything on primary, try all the panels
for (auto it = views->constBegin(), end = views->constEnd(); it != end; ++it) {
if (containsLattePlasmoid(it.key())) {
m_hideDock = it.value();
m_hideDock->visibility()->setBlockHiding(true);
m_hideDockTimer.start();
return;
}
}
}
void GlobalShortcuts::showLayoutsEditor()
{
m_corona->layoutManager()->showLayoutConfigDialog();
}
bool GlobalShortcuts::dockAtLowerScreenPriority(DockView *test, DockView *base)
{
if (!base || ! test) {
return true;
}
if (base->screen() == test->screen()) {
return false;
} else if (base->screen() != qGuiApp->primaryScreen() && test->screen() == qGuiApp->primaryScreen()) {
return false;
} else if (base->screen() == qGuiApp->primaryScreen() && test->screen() != qGuiApp->primaryScreen()) {
return true;
} else {
int basePriority = -1;
int testPriority = -1;
for (int i = 0; i < qGuiApp->screens().count(); ++i) {
if (base->screen() == qGuiApp->screens()[i]) {
basePriority = i;
}
if (test->screen() == qGuiApp->screens()[i]) {
testPriority = i;
}
}
if (testPriority <= basePriority) {
return true;
} else {
return false;
}
}
qDebug() << "dockAtLowerScreenPriority : shouldnt had reached here...";
return false;
}
bool GlobalShortcuts::dockAtLowerEdgePriority(DockView *test, DockView *base)
{
if (!base || ! test) {
return true;
}
QList<Plasma::Types::Location> edges{Plasma::Types::RightEdge, Plasma::Types::TopEdge,
Plasma::Types::LeftEdge, Plasma::Types::BottomEdge};
int testPriority = -1;
int basePriority = -1;
for (int i = 0; i < edges.count(); ++i) {
if (edges[i] == base->location()) {
basePriority = i;
}
if (edges[i] == test->location()) {
testPriority = i;
}
}
if (testPriority < basePriority)
return true;
else
return false;
}
void GlobalShortcuts::showSettings()
{
QList<DockView *> docks;
QHash<const Plasma::Containment *, DockView *> *views = m_corona->layoutManager()->currentDockViews();
//! create a docks list to sorted out
for (auto it = views->constBegin(), end = views->constEnd(); it != end; ++it) {
docks.append(it.value());
}
qDebug() << " -------- ";
for (int i = 0; i < docks.count(); ++i) {
qDebug() << i << ". " << docks[i]->screen()->name() << " - " << docks[i]->location();
}
//! sort the docks based on screens and edges priorities
//! docks on primary screen have higher priority and
//! for docks in the same screen the priority goes to
//! Bottom,Left,Top,Right
for (int i = 0; i < docks.size(); ++i) {
for (int j = 0; j < docks.size() - i - 1; ++j) {
if (dockAtLowerScreenPriority(docks[j], docks[j + 1])
|| (docks[j]->screen() == docks[j + 1]->screen()
&& dockAtLowerEdgePriority(docks[j], docks[j + 1]))) {
DockView *temp = docks[j + 1];
docks[j + 1] = docks[j];
docks[j] = temp;
}
}
}
qDebug() << " -------- sorted -----";
for (int i = 0; i < docks.count(); ++i) {
qDebug() << i << ". " << docks[i]->screen()->name() << " - " << docks[i]->location();
}
//! find which is the next dock to show its settings
if (docks.count() > 0) {
int openSettings = -1;
//! check if there is a dock with opened settings window
for (int i = 0; i < docks.size(); ++i) {
if (docks[i]->settingsWindowIsShown()) {
openSettings = i;
break;
}
}
if (openSettings >= 0 && docks.count() > 1) {
openSettings = openSettings + 1;
if (openSettings >= docks.size()) {
openSettings = 0;
}
docks[openSettings]->showSettingsWindow();
} else {
docks[0]->showSettingsWindow();
}
}
}
void GlobalShortcuts::hideDockTimerSlot()
{
if (!m_lastInvokedAction || !m_hideDock) {
return;
}
if (isPlatformX11()) {
if (!x11_areModKeysDepressed(m_lastInvokedAction->shortcut())) {
m_lastInvokedAction = Q_NULLPTR;
m_hideDock->visibility()->setBlockHiding(false);
m_hideDock = Q_NULLPTR;
if (m_tasksPlasmoid) {
m_methodShowNumbers.invoke(m_tasksPlasmoid, Q_ARG(QVariant, false));
m_tasksPlasmoid = Q_NULLPTR;
m_tasksMethodIndex = -1;
}
return;
}
m_hideDockTimer.start();
} else {
// TODO: This is needs to be fixed in wayland
m_lastInvokedAction = Q_NULLPTR;
m_hideDock->visibility()->setBlockHiding(false);
m_hideDock = Q_NULLPTR;
if (m_tasksPlasmoid) {
m_methodShowNumbers.invoke(m_tasksPlasmoid, Q_ARG(QVariant, false));
m_tasksPlasmoid = Q_NULLPTR;
m_tasksMethodIndex = -1;
}
}
}
}
#include "moc_globalshortcuts.cpp"