356 lines
13 KiB
C++
356 lines
13 KiB
C++
// SPDX-License-Identifier: MIT
|
|
|
|
#include "keyboardlayout.h"
|
|
#include "taskselection.h"
|
|
|
|
#include <QAction>
|
|
#include <QCommandLineOption>
|
|
#include <QCommandLineParser>
|
|
#include <QDBusConnection>
|
|
#include <QDBusInterface>
|
|
#include <QGuiApplication>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QScreen>
|
|
#include <QSet>
|
|
#include <QWaylandClientExtensionTemplate>
|
|
|
|
#include "qwayland-keystate.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include <KConfigGroup>
|
|
#include <KGlobalAccel>
|
|
#include <KGlobalShortcutInfo>
|
|
#include <KSharedConfig>
|
|
|
|
#include <abstracttasksmodel.h>
|
|
#include <activityinfo.h>
|
|
#include <tasksmodel.h>
|
|
|
|
namespace
|
|
{
|
|
constexpr auto configFile = "plasma-org.kde.plasma.desktop-appletsrc";
|
|
constexpr auto componentId = "se.ajpanton.plasma-task-group-shortcuts";
|
|
constexpr auto stateFile = "plasma-task-group-shortcutsrc";
|
|
|
|
KConfigGroup displacedShortcutGroup(int number)
|
|
{
|
|
const auto config = KSharedConfig::openConfig(QString::fromLatin1(stateFile));
|
|
return KConfigGroup(config, QStringLiteral("Displaced shortcut %1").arg(number));
|
|
}
|
|
|
|
void rememberDisplacedShortcuts(int number, const QList<QKeySequence> &sequences)
|
|
{
|
|
KConfigGroup group = displacedShortcutGroup(number);
|
|
QJsonArray shortcuts = QJsonDocument::fromJson(group.readEntry("Shortcuts", QByteArray())).array();
|
|
QSet<QString> savedActions;
|
|
for (const QJsonValue &value : std::as_const(shortcuts)) {
|
|
const QJsonObject shortcut = value.toObject();
|
|
savedActions.insert(shortcut.value(QStringLiteral("component")).toString()
|
|
+ QLatin1Char('\n')
|
|
+ shortcut.value(QStringLiteral("action")).toString());
|
|
}
|
|
|
|
QStringList capturedSequences = group.readEntry("Sequences", QStringList());
|
|
for (const QKeySequence &sequence : sequences) {
|
|
const QString sequenceName = sequence.toString(QKeySequence::PortableText);
|
|
if (capturedSequences.contains(sequenceName)) {
|
|
continue;
|
|
}
|
|
capturedSequences.append(sequenceName);
|
|
|
|
for (const KGlobalShortcutInfo &shortcut : KGlobalAccel::globalShortcutsByKey(sequence)) {
|
|
const QString actionId = shortcut.componentUniqueName() + QLatin1Char('\n') + shortcut.uniqueName();
|
|
if (shortcut.componentUniqueName() == QLatin1String(componentId)
|
|
|| savedActions.contains(actionId)) {
|
|
continue;
|
|
}
|
|
|
|
QJsonArray keys;
|
|
for (const QKeySequence &key : shortcut.keys()) {
|
|
keys.append(key.toString(QKeySequence::PortableText));
|
|
}
|
|
shortcuts.append(QJsonObject{
|
|
{QStringLiteral("component"), shortcut.componentUniqueName()},
|
|
{QStringLiteral("componentName"), shortcut.componentFriendlyName()},
|
|
{QStringLiteral("action"), shortcut.uniqueName()},
|
|
{QStringLiteral("actionName"), shortcut.friendlyName()},
|
|
{QStringLiteral("keys"), keys},
|
|
});
|
|
savedActions.insert(actionId);
|
|
}
|
|
}
|
|
|
|
group.writeEntry("Saved", true);
|
|
group.writeEntry("Sequences", capturedSequences);
|
|
group.writeEntry("Shortcuts", QJsonDocument(shortcuts).toJson(QJsonDocument::Compact));
|
|
group.sync();
|
|
}
|
|
|
|
void restoreDisplacedShortcuts()
|
|
{
|
|
for (int number = 1; number <= 9; ++number) {
|
|
KConfigGroup group = displacedShortcutGroup(number);
|
|
if (!group.readEntry("Saved", false)) {
|
|
continue;
|
|
}
|
|
|
|
const QJsonArray shortcuts = QJsonDocument::fromJson(group.readEntry("Shortcuts", QByteArray())).array();
|
|
for (const QJsonValue &value : shortcuts) {
|
|
const QJsonObject shortcut = value.toObject();
|
|
QAction action;
|
|
action.setObjectName(shortcut.value(QStringLiteral("action")).toString());
|
|
action.setText(shortcut.value(QStringLiteral("actionName")).toString());
|
|
action.setProperty("componentName", shortcut.value(QStringLiteral("component")).toString());
|
|
action.setProperty("componentDisplayName", shortcut.value(QStringLiteral("componentName")).toString());
|
|
action.setProperty("isConfigurationAction", true);
|
|
|
|
QList<QKeySequence> keys;
|
|
for (const QJsonValue &key : shortcut.value(QStringLiteral("keys")).toArray()) {
|
|
keys.append(QKeySequence::fromString(key.toString(), QKeySequence::PortableText));
|
|
}
|
|
KGlobalAccel::self()->setShortcut(&action, keys, KGlobalAccel::NoAutoloading);
|
|
}
|
|
group.deleteGroup();
|
|
group.sync();
|
|
}
|
|
KGlobalAccel::cleanComponent(QString::fromLatin1(componentId));
|
|
}
|
|
|
|
KConfigGroup taskManagerConfig(const KSharedConfig::Ptr &config)
|
|
{
|
|
const KConfigGroup containments(config, QStringLiteral("Containments"));
|
|
KConfigGroup fallback;
|
|
for (const QString &containmentId : containments.groupList()) {
|
|
const KConfigGroup containment = containments.group(containmentId);
|
|
const KConfigGroup applets = containment.group(QStringLiteral("Applets"));
|
|
for (const QString &appletId : applets.groupList()) {
|
|
const KConfigGroup applet = applets.group(appletId);
|
|
if (applet.readEntry("plugin") == QLatin1String("org.kde.plasma.taskmanager")) {
|
|
const KConfigGroup general = applet.group(QStringLiteral("Configuration")).group(QStringLiteral("General"));
|
|
if (containment.readEntry("lastScreen", -1) == 0) {
|
|
return general;
|
|
}
|
|
if (!fallback.isValid()) {
|
|
fallback = general;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
class ModifierState : public QWaylandClientExtensionTemplate<ModifierState>,
|
|
public QtWayland::org_kde_kwin_keystate
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
ModifierState()
|
|
: QWaylandClientExtensionTemplate(5)
|
|
{
|
|
connect(this, &QWaylandClientExtension::activeChanged, this, [this] {
|
|
if (isActive()) {
|
|
fetchStates();
|
|
}
|
|
});
|
|
initialize();
|
|
}
|
|
|
|
bool shiftPressed() const
|
|
{
|
|
return m_shiftPressed;
|
|
}
|
|
|
|
Q_SIGNALS:
|
|
void released();
|
|
|
|
private:
|
|
void org_kde_kwin_keystate_stateChanged(uint32_t key, uint32_t state) override
|
|
{
|
|
if (key == key_shift) {
|
|
m_shiftPressed = state == state_pressed;
|
|
} else if (key == key_meta) {
|
|
const bool pressed = state == state_pressed;
|
|
if (m_metaPressed && !pressed) {
|
|
Q_EMIT released();
|
|
}
|
|
m_metaPressed = pressed;
|
|
}
|
|
}
|
|
|
|
bool m_metaPressed = false;
|
|
bool m_shiftPressed = false;
|
|
};
|
|
|
|
class ShortcutManager : public QObject
|
|
{
|
|
Q_OBJECT
|
|
Q_CLASSINFO("D-Bus Interface", "se.ajpanton.PlasmaTaskGroupShortcuts.Manager")
|
|
|
|
public:
|
|
explicit ShortcutManager(QObject *parent = nullptr)
|
|
: QObject(parent)
|
|
, m_config(KSharedConfig::openConfig(QString::fromLatin1(configFile)))
|
|
{
|
|
m_model.setGroupInline(false);
|
|
reloadConfig();
|
|
|
|
connect(&m_activityInfo, &TaskManager::ActivityInfo::currentActivityChanged, this, [this] {
|
|
m_model.setActivity(m_activityInfo.currentActivity());
|
|
});
|
|
connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &ShortcutManager::updateScreen);
|
|
connect(&m_modifierState, &ModifierState::released, this, [this] {
|
|
m_lastShortcut = -1;
|
|
});
|
|
updateScreen();
|
|
|
|
for (int number = 1; number <= 9; ++number) {
|
|
auto *action = new QAction(this);
|
|
action->setObjectName(QStringLiteral("activate task group %1").arg(number));
|
|
action->setText(QStringLiteral("Activate Task Group %1").arg(number));
|
|
|
|
const QList<QKeySequence> sequences = numberRowShortcuts(number);
|
|
rememberDisplacedShortcuts(number, sequences);
|
|
for (const QKeySequence &sequence : sequences) {
|
|
KGlobalAccel::stealShortcutSystemwide(sequence);
|
|
}
|
|
KGlobalAccel::self()->setDefaultShortcut(action, sequences);
|
|
KGlobalAccel::self()->setShortcut(action, sequences, KGlobalAccel::NoAutoloading);
|
|
|
|
connect(action, &QAction::triggered, this, [this, number] {
|
|
useShortcut(number - 1, m_modifierState.shiftPressed());
|
|
});
|
|
}
|
|
}
|
|
|
|
public Q_SLOTS:
|
|
Q_SCRIPTABLE void restoreShortcutsAndQuit()
|
|
{
|
|
for (QAction *action : findChildren<QAction *>()) {
|
|
KGlobalAccel::self()->removeAllShortcuts(action);
|
|
}
|
|
restoreDisplacedShortcuts();
|
|
QCoreApplication::quit();
|
|
}
|
|
|
|
private:
|
|
void reloadConfig()
|
|
{
|
|
m_config->reparseConfiguration();
|
|
const KConfigGroup config = taskManagerConfig(m_config);
|
|
if (!config.isValid()) {
|
|
qWarning("No Plasma Task Manager configuration found");
|
|
return;
|
|
}
|
|
|
|
m_model.setLauncherList(config.readEntry("launchers", QStringList()));
|
|
m_model.setFilterByCurrentVirtualDesktop(config.readEntry("showOnlyCurrentDesktop", true));
|
|
m_model.setFilterByScreen(config.readEntry("showOnlyCurrentScreen", false));
|
|
m_model.setFilterByActivity(config.readEntry("showOnlyCurrentActivity", true));
|
|
m_model.setFilterNotMinimized(config.readEntry("showOnlyMinimized", false));
|
|
const int sortingStrategy = config.readEntry("sortingStrategy", 1);
|
|
m_model.setSortMode(static_cast<TaskManager::TasksModel::SortMode>(sortingStrategy));
|
|
m_separateLaunchers = sortingStrategy != TaskManager::TasksModel::SortManual
|
|
|| config.readEntry("separateLaunchers", true);
|
|
m_model.setSeparateLaunchers(m_separateLaunchers);
|
|
m_model.setLaunchInPlace(false);
|
|
m_model.setHideActivatedLaunchers(config.readEntry("hideLauncherOnStart", true));
|
|
m_model.setGroupMode(static_cast<TaskManager::TasksModel::GroupMode>(config.readEntry("groupingStrategy", 1)));
|
|
m_model.setGroupingWindowTasksThreshold(-1);
|
|
m_model.setGroupingAppIdBlacklist(config.readEntry("groupingAppIdBlacklist", QStringList()));
|
|
m_model.setGroupingLauncherUrlBlacklist(config.readEntry("groupingLauncherUrlBlacklist", QStringList()));
|
|
m_model.setActivity(m_activityInfo.currentActivity());
|
|
}
|
|
|
|
void updateScreen()
|
|
{
|
|
if (const QScreen *screen = QGuiApplication::primaryScreen()) {
|
|
m_model.setScreenGeometry(screen->geometry());
|
|
}
|
|
}
|
|
|
|
void useShortcut(int row, bool shifted)
|
|
{
|
|
reloadConfig();
|
|
const QVector<int> tasks = logicalTaskRows(m_model, m_separateLaunchers);
|
|
if (row >= tasks.size()) {
|
|
return;
|
|
}
|
|
|
|
const bool continuing = row == m_lastShortcut;
|
|
if (!continuing) {
|
|
m_cyclePosition = 0;
|
|
} else {
|
|
m_cyclePosition += shifted ? -1 : 1;
|
|
}
|
|
m_lastShortcut = row;
|
|
|
|
if (shifted && !continuing) {
|
|
m_model.requestNewInstance(m_model.index(tasks.at(row), 0));
|
|
return;
|
|
}
|
|
|
|
const QModelIndex task = taskToActivate(m_model, tasks.at(row), m_cyclePosition);
|
|
if (task.isValid()) {
|
|
m_model.requestActivate(task);
|
|
}
|
|
}
|
|
|
|
KSharedConfig::Ptr m_config;
|
|
TaskManager::ActivityInfo m_activityInfo;
|
|
TaskManager::TasksModel m_model;
|
|
ModifierState m_modifierState;
|
|
bool m_separateLaunchers = true;
|
|
int m_lastShortcut = -1;
|
|
int m_cyclePosition = 0;
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const bool restoring = std::any_of(argv + 1, argv + argc, [](const char *argument) {
|
|
return qstrcmp(argument, "--restore-shortcuts") == 0;
|
|
});
|
|
if (restoring) {
|
|
qputenv("QT_QPA_PLATFORM", "offscreen");
|
|
}
|
|
|
|
QGuiApplication application(argc, argv);
|
|
QCoreApplication::setQuitLockEnabled(false);
|
|
QCoreApplication::setApplicationName(QString::fromLatin1(componentId));
|
|
QGuiApplication::setApplicationDisplayName(QStringLiteral("Plasma Task Group Shortcuts"));
|
|
QGuiApplication::setDesktopFileName(QString::fromLatin1(componentId));
|
|
|
|
QCommandLineParser parser;
|
|
parser.addHelpOption();
|
|
QCommandLineOption restoreOption(QStringLiteral("restore-shortcuts"),
|
|
QStringLiteral("Restore the shortcuts displaced by this tool and exit."));
|
|
parser.addOption(restoreOption);
|
|
parser.process(application);
|
|
|
|
if (parser.isSet(restoreOption)) {
|
|
QDBusInterface running(QString::fromLatin1(componentId), QStringLiteral("/Manager"),
|
|
QStringLiteral("se.ajpanton.PlasmaTaskGroupShortcuts.Manager"));
|
|
if (running.isValid()) {
|
|
running.call(QDBus::Block, QStringLiteral("restoreShortcutsAndQuit"));
|
|
return 0;
|
|
}
|
|
restoreDisplacedShortcuts();
|
|
return 0;
|
|
}
|
|
|
|
if (!QDBusConnection::sessionBus().registerService(QString::fromLatin1(componentId))) {
|
|
return 0;
|
|
}
|
|
ShortcutManager manager;
|
|
QDBusConnection::sessionBus().registerObject(QStringLiteral("/Manager"), &manager,
|
|
QDBusConnection::ExportScriptableSlots);
|
|
return application.exec();
|
|
}
|
|
|
|
#include "main.moc"
|