Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set(libinputactions_SRCS
libinputactions/actions/ActionExecutor.cpp
libinputactions/actions/ActionGroup.cpp
libinputactions/actions/CommandAction.cpp
libinputactions/actions/ContextMenuAction.cpp
libinputactions/actions/CustomAction.cpp
libinputactions/actions/InputAction.cpp
libinputactions/actions/PlasmaGlobalShortcutAction.cpp
Expand Down Expand Up @@ -49,6 +50,7 @@ set(libinputactions_SRCS
libinputactions/handlers/TriggerHandler.cpp
libinputactions/helpers/Math.cpp
libinputactions/helpers/QDBusConnection.cpp
libinputactions/helpers/QDBusInterface.cpp
libinputactions/helpers/QProcess.cpp
libinputactions/helpers/QString.cpp
libinputactions/helpers/QThread.cpp
Expand Down
133 changes: 133 additions & 0 deletions src/libinputactions/actions/ContextMenuAction.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
Input Actions - Input handler that executes user-defined actions
Copyright (C) 2024-2026 Marcin Woźniak

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 3 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 <https://www.gnu.org/licenses/>.
*/

#include "ContextMenuAction.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QUuid>
#include <libinputactions/actions/ActionExecutor.h>
#include <libinputactions/helpers/QThread.h>
#include <libinputactions/interfaces/OverlayManager.h>

namespace InputActions
{

ActionMenuItem::ActionMenuItem(std::vector<std::unique_ptr<Action>> actions)
: m_actions(std::move(actions))
, m_id(QUuid::createUuid().toString(QUuid::WithoutBraces))
{
}

QJsonObject ActionMenuItem::toJson() const
{
return QJsonObject{
{"type", "action"},
{"text", m_text},
{"icon", m_icon},
{"id", m_id},
};
}

QJsonObject SectionMenuItem::toJson() const
{
return QJsonObject{
{"type", "section"},
{"text", m_text},
};
}

QJsonObject SeparatorMenuItem::toJson() const
{
return QJsonObject{
{"type", "separator"},
};
}

SubMenuMenuItem::SubMenuMenuItem(std::vector<std::unique_ptr<MenuItem>> items)
: m_items(std::move(items))
{
}

ContextMenuAction::~ContextMenuAction()
{
for (const auto &future : m_futures) {
future->cancel();
}
}

QJsonObject SubMenuMenuItem::toJson() const
{
QJsonArray items;
for (const auto &item : m_items) {
items.append(item->toJson());
}
return QJsonObject{
{"type", "menu"},
{"text", m_text},
{"icon", m_icon},
{"items", items},
};
}

ContextMenuAction::ContextMenuAction(std::vector<std::unique_ptr<MenuItem>> items)
: m_items(std::move(items))
{
QJsonArray jsonItems;
std::vector<ActionMenuItem *> actions;
for (const auto &item : m_items) {
extractActions(*item, actions);
jsonItems.append(item->toJson());
}
m_json = QJsonDocument(jsonItems).toJson(QJsonDocument::Compact);

for (auto *action : actions) {
m_actions[action->id()] = action;
}
}

void ContextMenuAction::doExecute(const ActionExecutionArguments &args)
{
QThreadHelpers::runOnThread(QThreadHelpers::mainThread(), [this]() {
auto future = std::make_shared<QFuture<QString>>(g_overlayManager->showContextMenu(m_json));
m_futures.push_back(future);
future->then([this, future = future.get()](const QString &actionId) {
if (m_actions.contains(actionId)) {
for (auto &action : m_actions.at(actionId)->actions()) {
g_actionExecutor->execute(*action);
}
}

std::erase_if(m_futures, [future](const auto &value) {
return value.get() == future;
});
});
});
}

void ContextMenuAction::extractActions(MenuItem &item, std::vector<ActionMenuItem *> &result)
{
if (auto *actionItem = dynamic_cast<ActionMenuItem *>(&item)) {
result.push_back(actionItem);
} else if (auto *subMenuItem = dynamic_cast<SubMenuMenuItem *>(&item)) {
for (const auto &item : subMenuItem->items()) {
extractActions(*item, result);
}
}
}

}
123 changes: 123 additions & 0 deletions src/libinputactions/actions/ContextMenuAction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
Input Actions - Input handler that executes user-defined actions
Copyright (C) 2024-2026 Marcin Woźniak

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 3 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 <https://www.gnu.org/licenses/>.
*/

#pragma once

#include "Action.h"

namespace InputActions
{

class MenuItem
{
public:
virtual ~MenuItem() = default;

virtual QJsonObject toJson() const = 0;

protected:
MenuItem() = default;
};

class ActionMenuItem : public MenuItem
{
public:
ActionMenuItem(std::vector<std::unique_ptr<Action>> actions);

const std::vector<std::unique_ptr<Action>> &actions() const { return m_actions; }

const QString &text() const { return m_text; }
void setText(QString value) { m_text = std::move(value); }

const QString &icon() const { return m_icon; }
void setIcon(QString value) { m_icon = std::move(value); }

const QString &id() const { return m_id; }

virtual QJsonObject toJson() const override;

private:
std::vector<std::unique_ptr<Action>> m_actions;
QString m_text;
QString m_icon;
QString m_id;
};

class SectionMenuItem : public MenuItem
{
public:
SectionMenuItem() = default;

const QString &text() const { return m_text; }
void setText(QString value) { m_text = std::move(value); }

virtual QJsonObject toJson() const override;

private:
QString m_text;
};

class SeparatorMenuItem : public MenuItem
{
public:
SeparatorMenuItem() = default;

virtual QJsonObject toJson() const override;
};

class SubMenuMenuItem : public MenuItem
{
public:
SubMenuMenuItem(std::vector<std::unique_ptr<MenuItem>> items);

const std::vector<std::unique_ptr<MenuItem>> &items() const { return m_items; }

const QString &text() const { return m_text; }
void setText(QString value) { m_text = std::move(value); }

const QString &icon() const { return m_icon; }
void setIcon(QString value) { m_icon = std::move(value); }

virtual QJsonObject toJson() const override;

private:
std::vector<std::unique_ptr<MenuItem>> m_items;
QString m_text;
QString m_icon;
};

class ContextMenuAction : public Action
{
public:
ContextMenuAction(std::vector<std::unique_ptr<MenuItem>> items);
~ContextMenuAction() override;

protected:
void doExecute(const ActionExecutionArguments &args) override;

private:
static void extractActions(MenuItem &item, std::vector<ActionMenuItem *> &result);

std::vector<std::unique_ptr<MenuItem>> m_items;
std::map<QString, ActionMenuItem *> m_actions;
QString m_json;

std::vector<std::shared_ptr<QFuture<QString>>> m_futures;
};

}
3 changes: 2 additions & 1 deletion src/libinputactions/config/ConfigIssue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ OverlayProgramDoesNotExistConfigException::OverlayProgramDoesNotExistConfigExcep

QString OverlayProgramDoesNotExistConfigException::message() const
{
return QString("Failed to find the inputactions-overlay program. It must be installed in one of the directories specified in the PATH environment variable.");
return QString("This feature requires the overlay, which appears to not be installed. Ensure 'inputactions-overlay' is present in one of the directories"
" specified in the PATH environment variable.");
}

UncaughtScriptErrorConfigException::UncaughtScriptErrorConfigException(const Node *node, QJSValue error)
Expand Down
22 changes: 16 additions & 6 deletions src/libinputactions/config/ConfigLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ QFuture<void> ConfigLoader::doLoad(const ConfigLoadSettings &settings)
qCDebug(INPUTACTIONS, "Reloading config");
const auto rawConfig = settings.empty ? "" : m_configProvider.currentConfig();

m_overlayRequested = false;
g_configIssueManager->clearIssues();
g_variableRegistry = std::make_shared<VariableRegistry>();
g_inputActions->registerGlobalVariables(g_variableRegistry.get());
Expand Down Expand Up @@ -229,7 +230,7 @@ QFuture<std::shared_ptr<ConfigData>> ConfigLoader::createConfig(const QString &r
.onFailed([root](const PromiseException &error) {
throw UncaughtScriptErrorConfigException(root.get(), error.value()); // TODO correct node
})
.then([root]() {
.then([this, root]() {
g_scriptingEngine->coreModule().variableRegistry()->disableRegistration();

auto config = std::make_shared<ConfigData>();
Expand Down Expand Up @@ -261,9 +262,10 @@ QFuture<std::shared_ptr<ConfigData>> ConfigLoader::createConfig(const QString &r

if (const auto *overlayNode = root->mapAt("overlay")) {
if (const auto *mouseStrokeNode = overlayNode->mapAt("mouse_stroke")) {
loadMember(config->enableMouseStrokeOverlay, mouseStrokeNode->at("enable"));
if (config->enableMouseStrokeOverlay && !g_overlayManager->overlayProgramExists()) {
throw OverlayProgramDoesNotExistConfigException(overlayNode);
const auto *enableNode = mouseStrokeNode->at("enable");
loadMember(config->enableMouseStrokeOverlay, enableNode);
if (config->enableMouseStrokeOverlay) {
requestOverlay(enableNode);
}
}
}
Expand All @@ -284,8 +286,8 @@ QFuture<void> ConfigLoader::activateConfig(std::shared_ptr<ConfigData> config, b

auto *scriptingConfig = g_scriptingEngine->coreModule().config();

return scriptingConfig->aboutToBeActivatedSignal().emitAsync(false).then([initialize, scriptingConfig, config]() {
g_overlayManager->setEnabled(config->enableMouseStrokeOverlay);
return scriptingConfig->aboutToBeActivatedSignal().emitAsync(false).then([this, initialize, scriptingConfig, config]() {
g_overlayManager->setEnabled(m_overlayRequested);
g_overlayManager->setMouseStrokeOverlayEnabled(config->enableMouseStrokeOverlay);

g_globalConfig->setAllowExternalVariableAccess(config->allowExternalVariableAccess);
Expand All @@ -312,6 +314,14 @@ QFuture<void> ConfigLoader::activateConfig(std::shared_ptr<ConfigData> config, b
});
}

void ConfigLoader::requestOverlay(const Node *node)
{
if (!g_overlayManager->overlayProgramExists()) {
throw OverlayProgramDoesNotExistConfigException(node);
}
m_overlayRequested = true;
}

void ConfigLoader::onConfigChanged()
{
if (!m_allowNonEmptyConfigs || !g_globalConfig->autoReload()) {
Expand Down
8 changes: 8 additions & 0 deletions src/libinputactions/config/ConfigLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace InputActions
{

struct ConfigData;
class Node;

struct ConfigLoadSettings
{
Expand Down Expand Up @@ -58,6 +59,12 @@ class ConfigLoader : public QObject
*/
void setAllowNonEmptyConfigs(bool value) { m_allowNonEmptyConfigs = value; }

/**
* Requests that the overlay be enabled.
* @throws OverlayProgramDoesNotExistConfigException
*/
void requestOverlay(const Node *node);

private slots:
void onConfigChanged();

Expand All @@ -67,6 +74,7 @@ private slots:
QFuture<void> activateConfig(std::shared_ptr<ConfigData> config, bool initialize);

QFuture<void> m_currentFuture;
bool m_overlayRequested{};

ConfigProvider m_configProvider;
bool m_allowNonEmptyConfigs = true;
Expand Down
Loading
Loading