Skip to content

Commit 11db77e

Browse files
authored
Merge pull request #20 from InputActions/scripting
add scripting
2 parents b037f66 + 738dde2 commit 11db77e

32 files changed

Lines changed: 1093 additions & 15 deletions

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
run: pacman -Syu --noconfirm
1818

1919
- name: Install dependencies
20-
run: pacman -S --needed --noconfirm base-devel git extra-cmake-modules qt6-tools yaml-cpp gtest
20+
run: pacman -S --needed --noconfirm base-devel git extra-cmake-modules qt6-declarative qt6-tools yaml-cpp gtest
2121

2222
- name: Check out repository
2323
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ include(KDECMakeSettings)
2323
find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
2424
Core
2525
DBus
26+
Qml
2627
)
2728

2829
find_package(PkgConfig REQUIRED)

src/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ set(libinputactions_SRCS
1818
libinputactions/config/parsers/flags.cpp
1919
libinputactions/config/parsers/NodeParser.h
2020
libinputactions/config/parsers/qt.cpp
21+
libinputactions/config/parsers/scripting.cpp
2122
libinputactions/config/parsers/separated-string.h
2223
libinputactions/config/parsers/std.cpp
2324
libinputactions/config/parsers/triggers.cpp
@@ -80,6 +81,13 @@ set(libinputactions_SRCS
8081
libinputactions/interfaces/TextInput.cpp
8182
libinputactions/interfaces/Window.h
8283
libinputactions/interfaces/WindowProvider.cpp
84+
libinputactions/scripting/modules/core/CoreModule.cpp
85+
libinputactions/scripting/modules/fs/File.cpp
86+
libinputactions/scripting/FunctionWrapper.cpp
87+
libinputactions/scripting/JSFunctionAction.cpp
88+
libinputactions/scripting/JSFunctionCondition.cpp
89+
libinputactions/scripting/Promise.cpp
90+
libinputactions/scripting/ScriptingEngine.cpp
8391
libinputactions/triggers/core/DirectionalMotionTriggerCore.cpp
8492
libinputactions/triggers/core/MotionTriggerCore.cpp
8593
libinputactions/triggers/core/StrokeTriggerCore.cpp
@@ -133,6 +141,7 @@ target_link_libraries(libinputactions PUBLIC
133141
libevdev-cpp
134142
Qt6::Core
135143
Qt6::DBus
144+
Qt6::Qml
136145
${LIBEVDEV_LIBRARIES}
137146
)
138147
target_compile_definitions(libinputactions PUBLIC TEST_VIRTUAL=$<IF:$<BOOL:${BUILD_TESTS}>,virtual,>)

src/libinputactions/InputActionsMain.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "interfaces/implementations/DBusPlasmaGlobalShortcutInvoker.h"
2323
#include "interfaces/implementations/FileConfigProvider.h"
2424
#include "interfaces/implementations/ProcessRunnerImpl.h"
25+
#include "scripting/ScriptingEngine.h"
2526
#include "variables/VariableRegistry.h"
2627
#include <QFile>
2728
#include <QStandardPaths>
@@ -53,6 +54,7 @@ InputActionsMain::~InputActionsMain()
5354
g_globalConfig.reset();
5455
g_configProvider.reset();
5556
g_inputBackend.reset();
57+
g_scriptingEngine.reset();
5658
g_strokeRecorder.reset();
5759
g_variableRegistry.reset();
5860
}
@@ -98,6 +100,7 @@ void InputActionsMain::setMissingImplementations()
98100
setMissingImplementation(g_configLoader);
99101
setMissingImplementation(g_globalConfig);
100102
setMissingImplementation(g_inputBackend);
103+
setMissingImplementation(g_scriptingEngine);
101104
setMissingImplementation(g_strokeRecorder);
102105
setMissingImplementation(g_variableRegistry);
103106
}

src/libinputactions/PointF.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ bool PointF::isNull() const
4848
return !m_x && !m_y;
4949
}
5050

51+
PointF PointF::clone() const
52+
{
53+
return {m_x, m_y};
54+
}
55+
5156
bool PointF::operator==(const PointF &other) const
5257
{
5358
return m_x == other.m_x && m_y == other.m_y;

src/libinputactions/PointF.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class PointF
4646

4747
Q_INVOKABLE double hypot() const;
4848

49+
Q_INVOKABLE PointF clone() const;
50+
4951
bool operator==(const PointF &other) const;
5052
bool operator!=(const PointF &other) const;
5153

src/libinputactions/config/ConfigIssue.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include "Node.h"
2222
#include <QRegularExpression>
2323
#include <libinputactions/ansi-escape-codes.h>
24+
#include <libinputactions/helpers/QString.h>
25+
#include <libinputactions/scripting/ScriptingEngine.h>
2426

2527
namespace InputActions
2628
{
@@ -183,6 +185,17 @@ QString UnusedPropertyConfigIssue::message() const
183185
return QString("Property '%1' does not exist or has no effect in this context.").arg(m_property);
184186
}
185187

188+
UncaughtScriptErrorConfigIssue::UncaughtScriptErrorConfigIssue(const Node *node, QJSValue error)
189+
: ConfigIssue(node)
190+
, m_message(g_scriptingEngine->errorToString(error))
191+
{
192+
}
193+
194+
QString UncaughtScriptErrorConfigIssue::message() const
195+
{
196+
return QString("Uncaught script error\n\n%1").arg(m_message);
197+
}
198+
186199
MissingRequiredPropertyConfigException::MissingRequiredPropertyConfigException(const Node *node, QString property)
187200
: ConfigException(node)
188201
, m_property(std::move(property))
@@ -194,6 +207,17 @@ QString MissingRequiredPropertyConfigException::message() const
194207
return QString("Required property '%1' was not specified.").arg(m_property);
195208
}
196209

210+
UncaughtScriptErrorConfigException::UncaughtScriptErrorConfigException(const Node *node, QJSValue error)
211+
: ConfigException(node)
212+
, m_message(g_scriptingEngine->errorToString(error))
213+
{
214+
}
215+
216+
QString UncaughtScriptErrorConfigException::message() const
217+
{
218+
return QString("Uncaught script error\n\n%1").arg(m_message);
219+
}
220+
197221
YamlCppConfigException::YamlCppConfigException(TextPosition position, QString message)
198222
: ConfigException(position)
199223
, m_message(std::move(message))
@@ -203,4 +227,14 @@ YamlCppConfigException::YamlCppConfigException(TextPosition position, QString me
203227
m_message += ".";
204228
}
205229

230+
ScriptingDisabledConfigException::ScriptingDisabledConfigException()
231+
: ConfigException(TextPosition())
232+
{
233+
}
234+
235+
QString ScriptingDisabledConfigException::message() const
236+
{
237+
return "Scripting is currently not available in the standalone implementation for security reasons. Remove all scripts from the configuration.";
238+
}
239+
206240
}

src/libinputactions/config/ConfigIssue.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#pragma once
2020

2121
#include "TextPosition.h"
22+
#include <QJSValue>
2223
#include <QString>
2324
#include <libinputactions/helpers/Copyable.h>
2425

@@ -121,6 +122,19 @@ class UnusedPropertyConfigIssue
121122
QString m_property;
122123
};
123124

125+
class UncaughtScriptErrorConfigIssue
126+
: public ConfigIssue
127+
, public virtual Copyable<UncaughtScriptErrorConfigIssue, ConfigIssue>
128+
{
129+
public:
130+
UncaughtScriptErrorConfigIssue(const Node *node, QJSValue error);
131+
132+
QString message() const override;
133+
134+
private:
135+
QString m_message;
136+
};
137+
124138
class DuplicateSetItemConfigException
125139
: public ConfigException
126140
, public virtual Copyable<DuplicateSetItemConfigException, ConfigIssue>
@@ -212,6 +226,19 @@ class MissingRequiredPropertyConfigException
212226
QString m_property;
213227
};
214228

229+
class UncaughtScriptErrorConfigException
230+
: public ConfigException
231+
, public virtual Copyable<UncaughtScriptErrorConfigException, ConfigIssue>
232+
{
233+
public:
234+
UncaughtScriptErrorConfigException(const Node *node, QJSValue error);
235+
236+
QString message() const override;
237+
238+
private:
239+
QString m_message;
240+
};
241+
215242
class YamlCppConfigException
216243
: public ConfigException
217244
, public virtual Copyable<YamlCppConfigException, ConfigIssue>
@@ -225,4 +252,14 @@ class YamlCppConfigException
225252
QString m_message;
226253
};
227254

255+
class ScriptingDisabledConfigException
256+
: public ConfigException
257+
, public virtual Copyable<ScriptingDisabledConfigException, ConfigIssue>
258+
{
259+
public:
260+
ScriptingDisabledConfigException();
261+
262+
QString message() const override;
263+
};
264+
228265
}

src/libinputactions/config/ConfigLoader.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "ConfigIssueManager.h"
2121
#include "GlobalConfig.h"
2222
#include "Node.h"
23+
#include "config/ConfigIssue.h"
2324
#include "interfaces/ConfigProvider.h"
2425
#include "parsers/containers.h"
2526
#include "parsers/core.h"
@@ -33,6 +34,7 @@
3334
#include <libinputactions/input/backends/LibevdevComplementaryInputBackend.h>
3435
#include <libinputactions/input/devices/InputDeviceRule.h>
3536
#include <libinputactions/interfaces/NotificationManager.h>
37+
#include <libinputactions/scripting/ScriptingEngine.h>
3638

3739
namespace InputActions
3840
{
@@ -61,14 +63,17 @@ void ConfigLoader::loadEmpty()
6163

6264
bool ConfigLoader::load(const ConfigLoadSettings &settings)
6365
{
66+
auto scriptingEngine = g_scriptingEngine;
6467
try {
6568
qCDebug(INPUTACTIONS, "Reloading config");
6669
const auto rawConfig = settings.config.value_or(g_configProvider->currentConfig());
6770

6871
g_configIssueManager = std::make_shared<ConfigIssueManager>(rawConfig);
72+
g_scriptingEngine = std::make_shared<ScriptingEngine>();
6973
auto config = createConfig(rawConfig);
7074
activateConfig(std::move(config), true);
7175
} catch (const ConfigException &e) {
76+
g_scriptingEngine = scriptingEngine;
7277
g_configIssueManager->addIssue(e);
7378
}
7479

@@ -97,6 +102,20 @@ Config ConfigLoader::createConfig(const QString &raw)
97102
}
98103

99104
Config config;
105+
106+
if (const auto *scriptingNode = root->mapAt("scripting")) {
107+
if (const auto *scriptsNode = scriptingNode->at("scripts")) {
108+
for (const auto *scriptNode : scriptsNode->sequenceItems()) {
109+
const auto *sourceNode = scriptNode->at("source", true);
110+
const auto source = sourceNode->as<QString>();
111+
const auto result = g_scriptingEngine->evaluate(sourceNode->as<QString>());
112+
if (result.isError()) {
113+
throw UncaughtScriptErrorConfigException(sourceNode, result);
114+
}
115+
}
116+
}
117+
}
118+
100119
loadMember(config.autoReload, root->at("autoreload"));
101120
loadMember(config.allowExternalVariableAccess, root->at("external_variable_access"));
102121
if (const auto *notificationsNode = root->mapAt("notifications")) {

src/libinputactions/config/parsers/core.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "containers.h"
2121
#include "flags.h"
2222
#include "globals.h"
23+
#include "scripting.h"
2324
#include "separated-string.h"
2425
#include "triggers.h"
2526
#include "utils.h"
@@ -39,6 +40,7 @@
3940
#include <libinputactions/conditions/VariableCondition.h>
4041
#include <libinputactions/config/ConfigIssue.h>
4142
#include <libinputactions/config/ConfigIssueManager.h>
43+
#include <libinputactions/config/ConfigLoader.h>
4244
#include <libinputactions/config/Node.h>
4345
#include <libinputactions/handlers/KeyboardTriggerHandler.h>
4446
#include <libinputactions/handlers/MouseTriggerHandler.h>
@@ -51,6 +53,9 @@
5153
#include <libinputactions/input/backends/InputBackend.h>
5254
#include <libinputactions/input/devices/InputDeviceRule.h>
5355
#include <libinputactions/interfaces/CursorShapeProvider.h>
56+
#include <libinputactions/scripting/JSFunctionAction.h>
57+
#include <libinputactions/scripting/JSFunctionCondition.h>
58+
#include <libinputactions/scripting/ScriptingEngine.h>
5459
#include <libinputactions/triggers/core/StrokeTriggerCore.h>
5560
#include <libinputactions/triggers/keyboard/KeyboardShortcutTrigger.h>
5661
#include <libinputactions/triggers/mouse/MouseTrigger.h>
@@ -149,6 +154,8 @@ void NodeParser<std::unique_ptr<Action>>::parse(const Node *node, std::unique_pt
149154
auto action = std::make_unique<CommandAction>(commandNode->as<Value<QString>>());
150155
loadSetter(action, &CommandAction::setWait, node->at("wait"));
151156
result = std::move(action);
157+
} else if (const auto *functionNode = node->at("function")) {
158+
result = std::make_unique<JSFunctionAction>(parseFunction(functionNode), functionNode->shared_from_this());
152159
} else if (const auto *inputNode = node->at("input")) {
153160
auto action = std::make_unique<InputAction>(inputNode->as<std::vector<InputActionItem>>());
154161
loadSetter(action, &InputAction::setDelay, node->at("delay"));
@@ -225,6 +232,9 @@ std::shared_ptr<Condition> parseCondition(const Node *node, const VariableRegist
225232
if (const auto *canReplaceTextNode = node->at("can_replace_text")) {
226233
return std::make_shared<CanReplaceTextCondition>(canReplaceTextNode->as<std::vector<TextSubstitutionRule>>(true));
227234
}
235+
if (const auto *functionNode = node->at("function")) {
236+
return std::make_shared<JSFunctionCondition>(parseFunction(functionNode), functionNode->shared_from_this());
237+
}
228238

229239
if (isLegacy(node)) {
230240
g_configIssueManager->addIssue(DeprecatedFeatureConfigIssue(node, DeprecatedFeature::LegacyConditions));

0 commit comments

Comments
 (0)