diff --git a/autotests/integration/scripting/CMakeLists.txt b/autotests/integration/scripting/CMakeLists.txt index 854f7ae9f..29ea18480 100644 --- a/autotests/integration/scripting/CMakeLists.txt +++ b/autotests/integration/scripting/CMakeLists.txt @@ -1 +1,2 @@ integrationTest(NAME testScriptingScreenEdge SRCS screenedge_test.cpp) +integrationTest(WAYLAND_ONLY NAME testMinimizeAllScript SRCS minimizeall_test.cpp) diff --git a/autotests/integration/scripting/minimizeall_test.cpp b/autotests/integration/scripting/minimizeall_test.cpp new file mode 100644 index 000000000..df22708a1 --- /dev/null +++ b/autotests/integration/scripting/minimizeall_test.cpp @@ -0,0 +1,170 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2019 Vlad Zahorodnii + +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 2 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 . +*********************************************************************/ + +#include "kwin_wayland_test.h" + +#include "platform.h" +#include "screens.h" +#include "scripting/scripting.h" +#include "shell_client.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include + +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_minimizeall-0"); +static const QString s_scriptName = QStringLiteral("minimizeall"); + +class MinimizeAllScriptTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testMinimizeUnminimize(); +}; + +void MinimizeAllScriptTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + + qRegisterMetaType(); + qRegisterMetaType(); + + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(workspaceCreatedSpy.wait()); + QCOMPARE(screens()->count(), 2); + QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); + QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); + waylandServer()->initWorkspace(); +} + +static QString locateMainScript(const QString &pluginName) +{ + const QList offers = KPackage::PackageLoader::self()->findPackages( + QStringLiteral("KWin/Script"), + QStringLiteral("kwin/scripts"), + [&](const KPluginMetaData &metaData) { + return metaData.pluginId() == pluginName; + } + ); + if (offers.isEmpty()) { + return QString(); + } + const KPluginMetaData &metaData = offers.first(); + const QString mainScriptFileName = metaData.value(QStringLiteral("X-Plasma-MainScript")); + const QFileInfo metaDataFileInfo(metaData.fileName()); + return metaDataFileInfo.path() + QLatin1String("/contents/") + mainScriptFileName; +} + +void MinimizeAllScriptTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); + + Scripting::self()->loadScript(locateMainScript(s_scriptName), s_scriptName); + QTRY_VERIFY(Scripting::self()->isScriptLoaded(s_scriptName)); + + AbstractScript *script = Scripting::self()->findScript(s_scriptName); + QVERIFY(script); + QSignalSpy runningChangedSpy(script, &AbstractScript::runningChanged); + QVERIFY(runningChangedSpy.isValid()); + script->run(); + QTRY_COMPARE(runningChangedSpy.count(), 1); +} + +void MinimizeAllScriptTest::cleanup() +{ + Test::destroyWaylandConnection(); + + Scripting::self()->unloadScript(s_scriptName); + QTRY_VERIFY(!Scripting::self()->isScriptLoaded(s_scriptName)); +} + +void MinimizeAllScriptTest::testMinimizeUnminimize() +{ + // This test verifies that all windows are minimized when Meta+Shift+D + // is pressed, and unminimized when the shortcut is pressed once again. + + using namespace KWayland::Client; + + // Create a couple of test clients. + QScopedPointer surface1(Test::createSurface()); + QScopedPointer shellSurface1(Test::createXdgShellStableSurface(surface1.data())); + ShellClient *client1 = Test::renderAndWaitForShown(surface1.data(), QSize(100, 50), Qt::blue); + QVERIFY(client1); + QVERIFY(client1->isActive()); + QVERIFY(client1->isMinimizable()); + + QScopedPointer surface2(Test::createSurface()); + QScopedPointer shellSurface2(Test::createXdgShellStableSurface(surface2.data())); + ShellClient *client2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::red); + QVERIFY(client2); + QVERIFY(client2->isActive()); + QVERIFY(client2->isMinimizable()); + + // Minimize the windows. + quint32 timestamp = 1; + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_D, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_D, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + QTRY_VERIFY(client1->isMinimized()); + QTRY_VERIFY(client2->isMinimized()); + + // Unminimize the windows. + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + kwinApp()->platform()->keyboardKeyPressed(KEY_D, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_D, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + kwinApp()->platform()->keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + QTRY_VERIFY(!client1->isMinimized()); + QTRY_VERIFY(!client2->isMinimized()); + + // Destroy test clients. + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(client2)); + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(client1)); +} + +} + +WAYLANDTEST_MAIN(KWin::MinimizeAllScriptTest) +#include "minimizeall_test.moc" diff --git a/scripting/meta.cpp b/scripting/meta.cpp index 14064e5ba..7c43ae6f2 100644 --- a/scripting/meta.cpp +++ b/scripting/meta.cpp @@ -1,241 +1,256 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Rohan Prabhu 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 2 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 . *********************************************************************/ #include "meta.h" #include "client.h" #include using namespace KWin::MetaScripting; // Meta for QPoint object QScriptValue Point::toScriptValue(QScriptEngine* eng, const QPoint& point) { QScriptValue temp = eng->newObject(); temp.setProperty(QStringLiteral("x"), point.x()); temp.setProperty(QStringLiteral("y"), point.y()); return temp; } void Point::fromScriptValue(const QScriptValue& obj, QPoint& point) { QScriptValue x = obj.property(QStringLiteral("x"), QScriptValue::ResolveLocal); QScriptValue y = obj.property(QStringLiteral("y"), QScriptValue::ResolveLocal); if (!x.isUndefined() && !y.isUndefined()) { point.setX(x.toInt32()); point.setY(y.toInt32()); } } // End of meta for QPoint object // Meta for QSize object QScriptValue Size::toScriptValue(QScriptEngine* eng, const QSize& size) { QScriptValue temp = eng->newObject(); temp.setProperty(QStringLiteral("w"), size.width()); temp.setProperty(QStringLiteral("h"), size.height()); return temp; } void Size::fromScriptValue(const QScriptValue& obj, QSize& size) { QScriptValue w = obj.property(QStringLiteral("w"), QScriptValue::ResolveLocal); QScriptValue h = obj.property(QStringLiteral("h"), QScriptValue::ResolveLocal); if (!w.isUndefined() && !h.isUndefined()) { size.setWidth(w.toInt32()); size.setHeight(h.toInt32()); } } // End of meta for QSize object // Meta for QRect object. Just a temporary measure, hope to // add a much better wrapping of the QRect object soon QScriptValue Rect::toScriptValue(QScriptEngine* eng, const QRect& rect) { QScriptValue temp = eng->newObject(); temp.setProperty(QStringLiteral("x"), rect.x()); temp.setProperty(QStringLiteral("y"), rect.y()); temp.setProperty(QStringLiteral("width"), rect.width()); temp.setProperty(QStringLiteral("height"), rect.height()); return temp; } void Rect::fromScriptValue(const QScriptValue& obj, QRect &rect) { QScriptValue w = obj.property(QStringLiteral("width"), QScriptValue::ResolveLocal); QScriptValue h = obj.property(QStringLiteral("height"), QScriptValue::ResolveLocal); QScriptValue x = obj.property(QStringLiteral("x"), QScriptValue::ResolveLocal); QScriptValue y = obj.property(QStringLiteral("y"), QScriptValue::ResolveLocal); if (!w.isUndefined() && !h.isUndefined() && !x.isUndefined() && !y.isUndefined()) { rect.setX(x.toInt32()); rect.setY(y.toInt32()); rect.setWidth(w.toInt32()); rect.setHeight(h.toInt32()); } } // End of meta for QRect object +QScriptValue AbstractClient::toScriptValue(QScriptEngine *engine, const KAbstractClientRef &client) +{ + return engine->newQObject(client, QScriptEngine::QtOwnership, + QScriptEngine::ExcludeChildObjects | + QScriptEngine::ExcludeDeleteLater | + QScriptEngine::PreferExistingWrapperObject | + QScriptEngine::AutoCreateDynamicProperties); +} + +void AbstractClient::fromScriptValue(const QScriptValue &value, KWin::AbstractClient *&client) +{ + client = qobject_cast(value.toQObject()); +} + QScriptValue Client::toScriptValue(QScriptEngine *eng, const KClientRef &client) { return eng->newQObject(client, QScriptEngine::QtOwnership, QScriptEngine::ExcludeChildObjects | QScriptEngine::ExcludeDeleteLater | QScriptEngine::PreferExistingWrapperObject | QScriptEngine::AutoCreateDynamicProperties); } void Client::fromScriptValue(const QScriptValue &value, KWin::Client* &client) { client = qobject_cast(value.toQObject()); } QScriptValue Toplevel::toScriptValue(QScriptEngine *eng, const KToplevelRef &client) { return eng->newQObject(client, QScriptEngine::QtOwnership, QScriptEngine::ExcludeChildObjects | QScriptEngine::ExcludeDeleteLater | QScriptEngine::PreferExistingWrapperObject | QScriptEngine::AutoCreateDynamicProperties); } void Toplevel::fromScriptValue(const QScriptValue &value, KToplevelRef &client) { client = qobject_cast(value.toQObject()); } // Other helper functions void KWin::MetaScripting::registration(QScriptEngine* eng) { qScriptRegisterMetaType(eng, Point::toScriptValue, Point::fromScriptValue); qScriptRegisterMetaType(eng, Size::toScriptValue, Size::fromScriptValue); qScriptRegisterMetaType(eng, Rect::toScriptValue, Rect::fromScriptValue); + qScriptRegisterMetaType(eng, AbstractClient::toScriptValue, AbstractClient::fromScriptValue); qScriptRegisterMetaType(eng, Client::toScriptValue, Client::fromScriptValue); qScriptRegisterMetaType(eng, Toplevel::toScriptValue, Toplevel::fromScriptValue); qScriptRegisterSequenceMetaType(eng); qScriptRegisterSequenceMetaType< QList >(eng); qScriptRegisterSequenceMetaType< QList >(eng); } QScriptValue KWin::MetaScripting::configExists(QScriptContext* ctx, QScriptEngine* eng) { QHash scriptConfig = (((ctx->thisObject()).data()).toVariant()).toHash(); QVariant val = scriptConfig.value((ctx->argument(0)).toString(), QVariant()); return eng->toScriptValue(val.isValid()); } QScriptValue KWin::MetaScripting::getConfigValue(QScriptContext* ctx, QScriptEngine* eng) { int num = ctx->argumentCount(); QHash scriptConfig = (((ctx->thisObject()).data()).toVariant()).toHash(); /* * Handle config.get() separately. Compute and return immediately. */ if (num == 0) { QHash::const_iterator i; QScriptValue ret = eng->newArray(); for (i = scriptConfig.constBegin(); i != scriptConfig.constEnd(); ++i) { ret.setProperty(i.key(), eng->newVariant(i.value())); } return ret; } if ((num == 1) && !((ctx->argument(0)).isArray())) { QVariant val = scriptConfig.value((ctx->argument(0)).toString(), QVariant()); if (val.isValid()) { return eng->newVariant(val); } else { return QScriptValue(); } } else { QScriptValue ret = eng->newArray(); int j = 0; if ((ctx->argument(0)).isArray()) { bool simple = (num == 1) ? 0 : (ctx->argument(1)).toBool(); QScriptValue array = (ctx->argument(0)); int len = (array.property(QStringLiteral("length")).isValid()) ? array.property(QStringLiteral("length")).toNumber() : 0; for (int i = 0; i < len; i++) { QVariant val = scriptConfig.value(array.property(i).toString(), QVariant()); if (val.isValid()) { if (simple) { ret.setProperty(j, eng->newVariant(val)); } else { ret.setProperty(array.property(i).toString(), eng->newVariant(val)); } j++; } } } else { for (int i = 0; i < num; i++) { QVariant val = scriptConfig.value((ctx->argument(i)).toString(), QVariant()); if (val.isValid()) { ret.setProperty((ctx->argument(i)).toString(), eng->newVariant(val)); j = 1; } } } if (j == 0) { return QScriptValue(); } else { return ret; } } } void KWin::MetaScripting::supplyConfig(QScriptEngine* eng, const QVariant& scriptConfig) { QScriptValue configObject = eng->newObject(); configObject.setData(eng->newVariant(scriptConfig)); configObject.setProperty(QStringLiteral("get"), eng->newFunction(getConfigValue, 0), QScriptValue::Undeletable); configObject.setProperty(QStringLiteral("exists"), eng->newFunction(configExists, 0), QScriptValue::Undeletable); configObject.setProperty(QStringLiteral("loaded"), ((scriptConfig.toHash().empty()) ? eng->newVariant((bool)0) : eng->newVariant((bool)1)), QScriptValue::Undeletable); (eng->globalObject()).setProperty(QStringLiteral("config"), configObject); } void KWin::MetaScripting::supplyConfig(QScriptEngine* eng) { KWin::MetaScripting::supplyConfig(eng, QVariant(QHash())); } void KWin::MetaScripting::valueMerge(QScriptValue& first, QScriptValue second) { QScriptValueIterator value_it(second); while (value_it.hasNext()) { value_it.next(); first.setProperty(value_it.name(), value_it.value()); } } diff --git a/scripting/meta.h b/scripting/meta.h index 2626f3a4d..3982b7c81 100644 --- a/scripting/meta.h +++ b/scripting/meta.h @@ -1,127 +1,135 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Rohan Prabhu 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 2 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 . *********************************************************************/ #ifndef KWIN_SCRIPTING_META_H #define KWIN_SCRIPTING_META_H #include // forward declarations class QPoint; class QRect; class QScriptContext; class QSize; namespace KWin { +class AbstractClient; class Client; class Toplevel; } +typedef KWin::AbstractClient *KAbstractClientRef; typedef KWin::Client* KClientRef; typedef KWin::Toplevel* KToplevelRef; namespace KWin { namespace MetaScripting { /** * The toScriptValue and fromScriptValue functions used in qScriptRegisterMetaType. * Conversion functions for QPoint */ namespace Point { QScriptValue toScriptValue(QScriptEngine*, const QPoint&); void fromScriptValue(const QScriptValue&, QPoint&); } /** * The toScriptValue and fromScriptValue functions used in qScriptRegisterMetaType. * Conversion functions for QSize */ namespace Size { QScriptValue toScriptValue(QScriptEngine*, const QSize&); void fromScriptValue(const QScriptValue&, QSize&); } /** * The toScriptValue and fromScriptValue functions used in qScriptRegisterMetaType. * Conversion functions for QRect * TODO: QRect conversions have to be linked from plasma as they provide a lot more * features. As for QSize and QPoint, I don't really plan any such thing. */ namespace Rect { QScriptValue toScriptValue(QScriptEngine*, const QRect&); void fromScriptValue(const QScriptValue&, QRect&); } +namespace AbstractClient +{ +QScriptValue toScriptValue(QScriptEngine *engine, const KAbstractClientRef &client); +void fromScriptValue(const QScriptValue &value, KAbstractClientRef &client); +} + namespace Client { QScriptValue toScriptValue(QScriptEngine *eng, const KClientRef &client); void fromScriptValue(const QScriptValue &value, KClientRef& client); } namespace Toplevel { QScriptValue toScriptValue(QScriptEngine *eng, const KToplevelRef &client); void fromScriptValue(const QScriptValue &value, KToplevelRef& client); } /** * Merges the second QScriptValue in the first one. */ void valueMerge(QScriptValue&, QScriptValue); /** * Registers all the meta conversion to the provided QScriptEngine */ void registration(QScriptEngine* eng); /** * Functions for the JS function objects, config.exists and config.get. * Read scripting/IMPLIST for details on how they work */ QScriptValue configExists(QScriptContext*, QScriptEngine*); QScriptValue getConfigValue(QScriptContext*, QScriptEngine*); /** * Provide a config object to the given QScriptEngine depending * on the keys provided in the QVariant. The provided QVariant * MUST returns (true) on isHash() */ void supplyConfig(QScriptEngine*, const QVariant&); /** * For engines whose scripts have no associated configuration. */ void supplyConfig(QScriptEngine*); } } /** * Code linked from plasma for QTimer. */ QScriptValue constructTimerClass(QScriptEngine *eng); #endif diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index 87e585fee..66b6c3ccd 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -1,4 +1,12 @@ -kpackage_install_package(videowall videowall scripts kwin) -kpackage_install_package(synchronizeskipswitcher synchronizeskipswitcher scripts kwin) -kpackage_install_package(desktopchangeosd desktopchangeosd scripts kwin) -kpackage_install_package(minimizeall minimizeall scripts kwin) +function(add_kwin_script name) + kpackage_install_package(${name} ${name} scripts kwin) + + # Copy the script to the build directory so one can run tests without prior + # make install. FIXME: use add_custom_command. + file(COPY ${name} DESTINATION ${CMAKE_BINARY_DIR}/bin/kwin/scripts/) +endfunction() + +add_kwin_script(videowall) +add_kwin_script(synchronizeskipswitcher) +add_kwin_script(desktopchangeosd) +add_kwin_script(minimizeall)