diff --git a/autotests/integration/scripting/screenedge_test.cpp b/autotests/integration/scripting/screenedge_test.cpp --- a/autotests/integration/scripting/screenedge_test.cpp +++ b/autotests/integration/scripting/screenedge_test.cpp @@ -49,6 +49,8 @@ void testEdge_data(); void testEdge(); + void testTouchEdge_data(); + void testTouchEdge(); void testEdgeUnregister(); void testDeclarativeTouchEdge(); @@ -100,7 +102,7 @@ void ScreenEdgeTest::cleanup() { // try to unload the script - const QStringList scripts = {QFINDTESTDATA("./scripts/screenedge.js"), QFINDTESTDATA("./scripts/screenedgeunregister.js")}; + const QStringList scripts = {QFINDTESTDATA("./scripts/screenedge.js"), QFINDTESTDATA("./scripts/screenedgeunregister.js"), QFINDTESTDATA("./scripts/touchScreenedge.js")}; for (const QString &script: scripts) { if (!script.isEmpty()) { if (Scripting::self()->isScriptLoaded(script)) { @@ -163,6 +165,60 @@ QVERIFY(workspace()->showingDesktop()); } +void ScreenEdgeTest::testTouchEdge_data() +{ + QTest::addColumn("edge"); + QTest::addColumn("triggerPos"); + QTest::addColumn("motionPos"); + + QTest::newRow("Top") << KWin::ElectricTop << QPoint(50, 0) << QPoint(50, 500); + QTest::newRow("Right") << KWin::ElectricRight << QPoint(1279, 50) << QPoint(500, 50); + QTest::newRow("Bottom") << KWin::ElectricBottom << QPoint(512, 1023) << QPoint(512, 500); + QTest::newRow("Left") << KWin::ElectricLeft << QPoint(0, 50) << QPoint(500, 50); + + //repeat a row to show previously unloading and re-registering works + QTest::newRow("Top") << KWin::ElectricTop << QPoint(512, 0) << QPoint(512, 500); +} + +void ScreenEdgeTest::testTouchEdge() +{ + const QString scriptToLoad = QFINDTESTDATA("./scripts/touchScreenedge.js"); + QVERIFY(!scriptToLoad.isEmpty()); + + // mock the config + auto config = kwinApp()->config(); + QFETCH(KWin::ElectricBorder, edge); + config->group(QLatin1String("Script-") + scriptToLoad).writeEntry("Edge", int(edge)); + config->sync(); + + QVERIFY(!Scripting::self()->isScriptLoaded(scriptToLoad)); + const int id = Scripting::self()->loadScript(scriptToLoad); + QVERIFY(id != -1); + QVERIFY(Scripting::self()->isScriptLoaded(scriptToLoad)); + auto s = Scripting::self()->findScript(scriptToLoad); + QVERIFY(s); + QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); + QVERIFY(runningChangedSpy.isValid()); + s->run(); + QVERIFY(runningChangedSpy.wait()); + QCOMPARE(runningChangedSpy.count(), 1); + QCOMPARE(runningChangedSpy.first().first().toBool(), true); + // triggering the edge will result in show desktop being triggered + QSignalSpy showDesktopSpy(workspace(), &Workspace::showingDesktopChanged); + QVERIFY(showDesktopSpy.isValid()); + + // trigger the edge + QFETCH(QPoint, triggerPos); + quint32 timestamp = 0; + kwinApp()->platform()->touchDown(0, triggerPos, timestamp++); + QFETCH(QPoint, motionPos); + kwinApp()->platform()->touchMotion(0, motionPos, timestamp++); + kwinApp()->platform()->touchUp(0, timestamp++); + QVERIFY(showDesktopSpy.wait()); + QCOMPARE(showDesktopSpy.count(), 1); + QVERIFY(workspace()->showingDesktop()); +} + void ScreenEdgeTest::triggerConfigReload() { workspace()->slotReconfigure(); } diff --git a/autotests/integration/scripting/scripts/touchScreenedge.js b/autotests/integration/scripting/scripts/touchScreenedge.js new file mode 100644 --- /dev/null +++ b/autotests/integration/scripting/scripts/touchScreenedge.js @@ -0,0 +1 @@ +registerTouchScreenEdge(readConfig("Edge", 1), function() { workspace.slotToggleShowDesktop(); }); diff --git a/autotests/test_scripted_effectloader.cpp b/autotests/test_scripted_effectloader.cpp --- a/autotests/test_scripted_effectloader.cpp +++ b/autotests/test_scripted_effectloader.cpp @@ -46,6 +46,10 @@ { } +void ScreenEdges::reserveTouch(ElectricBorder, QAction *) +{ +} + InputRedirection *InputRedirection::s_self = nullptr; void InputRedirection::registerShortcut(const QKeySequence &, QAction *) diff --git a/scripting/scriptedeffect.h b/scripting/scriptedeffect.h --- a/scripting/scriptedeffect.h +++ b/scripting/scriptedeffect.h @@ -91,6 +91,9 @@ return m_screenEdgeCallbacks; } + bool registerTouchScreenCallback(int edge, QScriptValue callback); + bool unregisterTouchScreenCallback(int edge); + public Q_SLOTS: quint64 animate(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, QEasingCurve::Type curve = QEasingCurve::Linear, int delay = 0); quint64 set(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, QEasingCurve::Type curve = QEasingCurve::Linear, int delay = 0); @@ -121,6 +124,7 @@ QHash > m_screenEdgeCallbacks; KConfigLoader *m_config; int m_chainPosition; + QHash m_touchScreenEdgeCallbacks; }; } diff --git a/scripting/scriptedeffect.cpp b/scripting/scriptedeffect.cpp --- a/scripting/scriptedeffect.cpp +++ b/scripting/scriptedeffect.cpp @@ -91,6 +91,16 @@ return registerScreenEdge(context, engine); } +QScriptValue kwinRegisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) +{ + return registerTouchScreenEdge(context, engine); +} + +QScriptValue kwinUnregisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) +{ + return unregisterTouchScreenEdge(context, engine); +} + struct AnimationSettings { enum { Type = 1<<0, Curve = 1<<1, Delay = 1<<2, Duration = 1<<3 }; AnimationEffect::Attribute type; @@ -518,6 +528,8 @@ // add global Shortcut registerGlobalShortcutFunction(this, m_engine, kwinScriptGlobalShortcut); registerScreenEdgeFunction(this, m_engine, kwinScriptScreenEdge); + registerTouchScreenEdgeFunction(this, m_engine, kwinRegisterTouchScreenEdge); + unregisterTouchScreenEdgeFunction(this, m_engine, kwinUnregisterTouchScreenEdge); // add the animate method QScriptValue animateFunc = m_engine->newFunction(kwinEffectAnimate); animateFunc.setData(m_engine->newQObject(this)); @@ -637,4 +649,32 @@ return m_config->property(key); } +bool ScriptedEffect::registerTouchScreenCallback(int edge, QScriptValue callback) +{ + if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) { + return false; + } + QAction *action = new QAction(this); + connect(action, &QAction::triggered, this, + [callback] { + QScriptValue invoke(callback); + invoke.call(); + } + ); + ScreenEdges::self()->reserveTouch(KWin::ElectricBorder(edge), action); + m_touchScreenEdgeCallbacks.insert(edge, action); + return true; +} + +bool ScriptedEffect::unregisterTouchScreenCallback(int edge) +{ + auto it = m_touchScreenEdgeCallbacks.find(edge); + if (it == m_touchScreenEdgeCallbacks.constEnd()) { + return false; + } + delete it.value(); + m_touchScreenEdgeCallbacks.erase(it); + return true; +} + } // namespace diff --git a/scripting/scripting.h b/scripting/scripting.h --- a/scripting/scripting.h +++ b/scripting/scripting.h @@ -229,6 +229,9 @@ return m_engine; } + bool registerTouchScreenCallback(int edge, QScriptValue callback); + bool unregisterTouchScreenCallback(int edge); + public Q_SLOTS: Q_SCRIPTABLE void run(); @@ -256,6 +259,7 @@ QScriptEngine *m_engine; bool m_starting; QScopedPointer m_agent; + QHash m_touchScreenEdgeCallbacks; }; class ScriptUnloaderAgent : public QScriptEngineAgent diff --git a/scripting/scripting.cpp b/scripting/scripting.cpp --- a/scripting/scripting.cpp +++ b/scripting/scripting.cpp @@ -160,6 +160,15 @@ return KWin::unregisterScreenEdge(context, engine); } +QScriptValue kwinRegisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) +{ + return KWin::registerTouchScreenEdge(context, engine); +} + +QScriptValue kwinUnregisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) +{ + return KWin::unregisterTouchScreenEdge(context, engine); +} QScriptValue kwinRegisterUserActionsMenu(QScriptContext *context, QScriptEngine *engine) { @@ -285,6 +294,8 @@ // add screen edge registerScreenEdgeFunction(this, engine, kwinRegisterScreenEdge); unregisterScreenEdgeFunction(this, engine, kwinUnregisterScreenEdge); + registerTouchScreenEdgeFunction(this, engine, kwinRegisterTouchScreenEdge); + unregisterTouchScreenEdgeFunction(this, engine, kwinUnregisterTouchScreenEdge); // add user actions menu register function registerUserActionsMenuFunction(this, engine, kwinRegisterUserActionsMenu); @@ -521,6 +532,34 @@ stop(); } +bool KWin::Script::registerTouchScreenCallback(int edge, QScriptValue callback) +{ + if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) { + return false; + } + QAction *action = new QAction(this); + connect(action, &QAction::triggered, this, + [callback] { + QScriptValue invoke(callback); + invoke.call(); + } + ); + ScreenEdges::self()->reserveTouch(KWin::ElectricBorder(edge), action); + m_touchScreenEdgeCallbacks.insert(edge, action); + return true; +} + +bool KWin::Script::unregisterTouchScreenCallback(int edge) +{ + auto it = m_touchScreenEdgeCallbacks.find(edge); + if (it == m_touchScreenEdgeCallbacks.constEnd()) { + return false; + } + delete it.value(); + m_touchScreenEdgeCallbacks.erase(it); + return true; +} + KWin::ScriptUnloaderAgent::ScriptUnloaderAgent(KWin::Script *script) : QScriptEngineAgent(script->engine()) , m_script(script) diff --git a/scripting/scriptingutils.h b/scripting/scriptingutils.h --- a/scripting/scriptingutils.h +++ b/scripting/scriptingutils.h @@ -196,6 +196,46 @@ } template +QScriptValue registerTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) +{ + auto script = qobject_cast(context->callee().data().toQObject()); + if (!script) { + return engine->undefinedValue(); + } + if (!validateParameters(context, 2, 2)) { + return engine->undefinedValue(); + } + if (!validateArgumentType(context)) { + return engine->undefinedValue(); + } + if (!context->argument(1).isFunction()) { + context->throwError(QScriptContext::SyntaxError, i18nc("KWin Scripting error thrown due to incorrect argument", + "Second argument to registerTouchScreenEdge needs to be a callback")); + } + const int edge = context->argument(0).toVariant().toInt(); + const auto ret = script->registerTouchScreenCallback(edge, context->argument(1)); + return engine->newVariant(ret); +} + +template +QScriptValue unregisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) +{ + auto script = qobject_cast(context->callee().data().toQObject()); + if (!script) { + return engine->undefinedValue(); + } + if (!validateParameters(context, 1, 1)) { + return engine->undefinedValue(); + } + if (!validateArgumentType(context)) { + return engine->undefinedValue(); + } + const int edge = context->argument(0).toVariant().toInt(); + const auto ret = script->unregisterTouchScreenCallback(edge); + return engine->newVariant(ret); +} + +template QScriptValue registerUserActionsMenu(QScriptContext *context, QScriptEngine *engine) { T script = qobject_cast(context->callee().data().toQObject()); @@ -303,6 +343,20 @@ engine->globalObject().setProperty(QStringLiteral("unregisterScreenEdge"), shortcutFunc); } +inline void registerTouchScreenEdgeFunction(QObject *parent, QScriptEngine *engine, QScriptEngine::FunctionSignature function) +{ + QScriptValue touchScreenFunc = engine->newFunction(function); + touchScreenFunc.setData(engine->newQObject(parent)); + engine->globalObject().setProperty(QStringLiteral("registerTouchScreenEdge"), touchScreenFunc); +} + +inline void unregisterTouchScreenEdgeFunction(QObject *parent, QScriptEngine *engine, QScriptEngine::FunctionSignature function) +{ + QScriptValue touchScreenFunc = engine->newFunction(function); + touchScreenFunc.setData(engine->newQObject(parent)); + engine->globalObject().setProperty(QStringLiteral("unregisterTouchScreenEdge"), touchScreenFunc); +} + inline void registerUserActionsMenuFunction(QObject *parent, QScriptEngine *engine, QScriptEngine::FunctionSignature function) { QScriptValue shortcutFunc = engine->newFunction(function);