diff --git a/autotests/integration/effects/scripted_effects_test.cpp b/autotests/integration/effects/scripted_effects_test.cpp index 4e477b0aa..ab7667bcb 100644 --- a/autotests/integration/effects/scripted_effects_test.cpp +++ b/autotests/integration/effects/scripted_effects_test.cpp @@ -1,426 +1,486 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2018 David Edmundson 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 "scripting/scriptedeffect.h" #include "libkwineffects/anidata_p.h" #include "composite.h" #include "cursor.h" -#include "cursor.h" +#include "deleted.h" #include "effect_builtins.h" #include "effectloader.h" #include "effects.h" #include "kwin_wayland_test.h" #include "platform.h" #include "shell_client.h" #include "virtualdesktops.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_effects_scripts-0"); class ScriptedEffectsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testEffectsHandler(); void testEffectsContext(); void testShortcuts(); void testAnimations_data(); void testAnimations(); void testScreenEdge(); void testScreenEdgeTouch(); void testFullScreenEffect_data(); void testFullScreenEffect(); + void testKeepAlive_data(); + void testKeepAlive(); + private: ScriptedEffect *loadEffect(const QString &name); }; class ScriptedEffectWithDebugSpy : public KWin::ScriptedEffect { Q_OBJECT public: ScriptedEffectWithDebugSpy(); bool load(const QString &name); using AnimationEffect::state; signals: void testOutput(const QString &data); }; QScriptValue kwinEffectScriptTestOut(QScriptContext *context, QScriptEngine *engine) { auto *script = qobject_cast(context->callee().data().toQObject()); QString result; for (int i = 0; i < context->argumentCount(); ++i) { if (i > 0) { result.append(QLatin1Char(' ')); } result.append(context->argument(i).toString()); } emit script->testOutput(result); return engine->undefinedValue(); } ScriptedEffectWithDebugSpy::ScriptedEffectWithDebugSpy() : ScriptedEffect() { QScriptValue testHookFunc = engine()->newFunction(kwinEffectScriptTestOut); testHookFunc.setData(engine()->newQObject(this)); engine()->globalObject().setProperty(QStringLiteral("sendTestResponse"), testHookFunc); } bool ScriptedEffectWithDebugSpy::load(const QString &name) { const QString path = QFINDTESTDATA("./scripts/" + name + ".js"); if (!init(name, path)) { return false; } // inject our newly created effect to be registered with the EffectsHandlerImpl::loaded_effects // this is private API so some horrible code is used to find the internal effectloader // and register ourselves auto c = effects->children(); for (auto it = c.begin(); it != c.end(); ++it) { if (qstrcmp((*it)->metaObject()->className(), "KWin::EffectLoader") != 0) { continue; } QMetaObject::invokeMethod(*it, "effectLoaded", Q_ARG(KWin::Effect*, this), Q_ARG(QString, name)); break; } return (static_cast(effects)->isEffectLoaded(name)); } void ScriptedEffectsTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); ScriptedEffectLoader loader; // disable all effects - we don't want to have it interact with the rendering auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup plugins(config, QStringLiteral("Plugins")); const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); for (QString name : builtinNames) { plugins.writeEntry(name + QStringLiteral("Enabled"), false); } config->sync(); kwinApp()->setConfig(config); qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QVERIFY(Compositor::self()); KWin::VirtualDesktopManager::self()->setCount(2); } void ScriptedEffectsTest::init() { QVERIFY(Test::setupWaylandConnection()); } void ScriptedEffectsTest::cleanup() { Test::destroyWaylandConnection(); auto *e = static_cast(effects); while (!e->loadedEffects().isEmpty()) { const QString effect = e->loadedEffects().first(); e->unloadEffect(effect); QVERIFY(!e->isEffectLoaded(effect)); } KWin::VirtualDesktopManager::self()->setCurrent(1); } void ScriptedEffectsTest::testEffectsHandler() { // this triggers and tests some of the signals in EffectHandler, which is exposed to JS as context property "effects" auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); auto waitFor = [&effectOutputSpy, this](const QString &expected) { QVERIFY(effectOutputSpy.count() > 0 || effectOutputSpy.wait()); QCOMPARE(effectOutputSpy.first().first(), expected); effectOutputSpy.removeFirst(); }; QVERIFY(effect->load("effectsHandler")); // trigger windowAdded signal // create a window using namespace KWayland::Client; auto *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); auto *shellSurface = Test::createXdgShellV6Surface(surface, surface); QVERIFY(shellSurface); shellSurface->setTitle("WindowA"); auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); waitFor("windowAdded - WindowA"); waitFor("stackingOrder - 1 WindowA"); // windowMinimsed c->minimize(); waitFor("windowMinimized - WindowA"); c->unminimize(); waitFor("windowUnminimized - WindowA"); surface->deleteLater(); waitFor("windowClosed - WindowA"); // desktop management KWin::VirtualDesktopManager::self()->setCurrent(2); waitFor("desktopChanged - 1 2"); } void ScriptedEffectsTest::testEffectsContext() { // this tests misc non-objects exposed to the script engine: animationTime, displaySize, use of external enums auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load("effectContext")); QCOMPARE(effectOutputSpy[0].first(), "1280x1024"); QCOMPARE(effectOutputSpy[1].first(), "100"); QCOMPARE(effectOutputSpy[2].first(), "2"); QCOMPARE(effectOutputSpy[3].first(), "0"); } void ScriptedEffectsTest::testShortcuts() { // this tests method registerShortcut auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load("shortcutsTest")); QCOMPARE(effect->shortcutCallbacks().count(), 1); QAction *action = effect->shortcutCallbacks().keys()[0]; QCOMPARE(action->objectName(), "testShortcut"); QCOMPARE(action->text(), "Test Shortcut"); QCOMPARE(KGlobalAccel::self()->shortcut(action).first(), QKeySequence("Meta+Shift+Y")); action->trigger(); QCOMPARE(effectOutputSpy[0].first(), "shortcutTriggered"); } void ScriptedEffectsTest::testAnimations_data() { QTest::addColumn("file"); QTest::addColumn("animationCount"); QTest::newRow("single") << "animationTest" << 1; QTest::newRow("multi") << "animationTestMulti" << 2; } void ScriptedEffectsTest::testAnimations() { // this tests animate/set/cancel // methods take either an int or an array, as forced in the data above // also splits animate vs effects.animate(..) QFETCH(QString, file); QFETCH(int, animationCount); auto *effect = new ScriptedEffectWithDebugSpy; QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load(file)); // animated after window added connect using namespace KWayland::Client; auto *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); auto *shellSurface = Test::createXdgShellV6Surface(surface, surface); QVERIFY(shellSurface); shellSurface->setTitle("Window 1"); auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); // we are running the event loop during renderAndWaitForShown // some time will pass with the event loop running between the window being added and getting to here // anim.duration is an aboslute value, but retarget will update the duration based on time passed int timePassed = 0; { const AnimationEffect::AniMap state = effect->state(); QCOMPARE(state.count(), 1); QCOMPARE(state.firstKey(), c->effectWindow()); const auto &animationsForWindow = state.first().first; QCOMPARE(animationsForWindow.count(), animationCount); QCOMPARE(animationsForWindow[0].duration, 100); QCOMPARE(animationsForWindow[0].to, FPx2(1.4)); QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); QCOMPARE(animationsForWindow[0].curve.type(), QEasingCurve::OutQuad); QCOMPARE(animationsForWindow[0].keepAtTarget, false); timePassed = animationsForWindow[0].time; if (animationCount == 2) { QCOMPARE(animationsForWindow[1].duration, 100); QCOMPARE(animationsForWindow[1].to, FPx2(0.0)); QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); QCOMPARE(animationsForWindow[1].keepAtTarget, false); } } QCOMPARE(effectOutputSpy[0].first(), "true"); // window state changes, scale should be retargetted c->setMinimized(true); { const AnimationEffect::AniMap state = effect->state(); QCOMPARE(state.count(), 1); const auto &animationsForWindow = state.first().first; QCOMPARE(animationsForWindow.count(), animationCount); QCOMPARE(animationsForWindow[0].duration, 200 + timePassed); QCOMPARE(animationsForWindow[0].to, FPx2(1.5)); QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); QCOMPARE(animationsForWindow[0].keepAtTarget, false); if (animationCount == 2) { QCOMPARE(animationsForWindow[1].duration, 200 + timePassed); QCOMPARE(animationsForWindow[1].to, FPx2(1.5)); QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); QCOMPARE(animationsForWindow[1].keepAtTarget, false); } } c->setMinimized(false); { const AnimationEffect::AniMap state = effect->state(); QCOMPARE(state.count(), 0); } } void ScriptedEffectsTest::testScreenEdge() { // this test checks registerScreenEdge functions auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load("screenEdgeTest")); effect->borderActivated(KWin::ElectricTopRight); QCOMPARE(effectOutputSpy.count(), 1); } void ScriptedEffectsTest::testScreenEdgeTouch() { // this test checks registerTouchScreenEdge functions auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); QVERIFY(effect->load("screenEdgeTouchTest")); auto actions = effect->findChildren(QString(), Qt::FindDirectChildrenOnly); actions[0]->trigger(); QCOMPARE(effectOutputSpy.count(), 1); } void ScriptedEffectsTest::testFullScreenEffect_data() { QTest::addColumn("file"); QTest::newRow("single") << "fullScreenEffectTest"; QTest::newRow("multi") << "fullScreenEffectTestMulti"; QTest::newRow("global") << "fullScreenEffectTestGlobal"; } void ScriptedEffectsTest::testFullScreenEffect() { QFETCH(QString, file); auto *effectMain = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean QSignalSpy effectOutputSpy(effectMain, &ScriptedEffectWithDebugSpy::testOutput); QSignalSpy fullScreenEffectActiveSpy(effects, &EffectsHandler::hasActiveFullScreenEffectChanged); QSignalSpy isActiveFullScreenEffectSpy(effectMain, &ScriptedEffect::isActiveFullScreenEffectChanged); QVERIFY(effectMain->load(file)); //load any random effect from another test to confirm fullscreen effect state is correctly //shown as being someone else auto effectOther = new ScriptedEffectWithDebugSpy(); QVERIFY(effectOther->load("screenEdgeTouchTest")); QSignalSpy isActiveFullScreenEffectSpyOther(effectOther, &ScriptedEffect::isActiveFullScreenEffectChanged); using namespace KWayland::Client; auto *surface = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface); auto *shellSurface = Test::createXdgShellV6Surface(surface, surface); QVERIFY(shellSurface); shellSurface->setTitle("Window 1"); auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); QVERIFY(c); QCOMPARE(workspace()->activeClient(), c); QCOMPARE(effects->hasActiveFullScreenEffect(), false); QCOMPARE(effectMain->isActiveFullScreenEffect(), false); //trigger animation KWin::VirtualDesktopManager::self()->setCurrent(2); QCOMPARE(effects->activeFullScreenEffect(), effectMain); QCOMPARE(effects->hasActiveFullScreenEffect(), true); QCOMPARE(fullScreenEffectActiveSpy.count(), 1); QCOMPARE(effectMain->isActiveFullScreenEffect(), true); QCOMPARE(isActiveFullScreenEffectSpy.count(), 1); QCOMPARE(effectOther->isActiveFullScreenEffect(), false); QCOMPARE(isActiveFullScreenEffectSpyOther.count(), 0); //after 500ms trigger another full screen animation QTest::qWait(500); KWin::VirtualDesktopManager::self()->setCurrent(1); QCOMPARE(effects->activeFullScreenEffect(), effectMain); //after 1000ms (+a safety margin for time based tests) we should still be the active full screen effect //despite first animation expiring QTest::qWait(500+100); QCOMPARE(effects->activeFullScreenEffect(), effectMain); //after 1500ms (+a safetey margin) we should have no full screen effect QTest::qWait(500+100); QCOMPARE(effects->activeFullScreenEffect(), nullptr); } +void ScriptedEffectsTest::testKeepAlive_data() +{ + QTest::addColumn("file"); + QTest::addColumn("keepAlive"); + + QTest::newRow("keep") << "keepAliveTest" << true; + QTest::newRow("don't keep") << "keepAliveTestDontKeep" << false; +} + +void ScriptedEffectsTest::testKeepAlive() +{ + // this test checks whether closed windows are kept alive + // when keepAlive property is set to true(false) + + QFETCH(QString, file); + QFETCH(bool, keepAlive); + + auto *effect = new ScriptedEffectWithDebugSpy; + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effectOutputSpy.isValid()); + QVERIFY(effect->load(file)); + + // create a window + using namespace KWayland::Client; + auto *surface = Test::createSurface(Test::waylandCompositor()); + QVERIFY(surface); + auto *shellSurface = Test::createXdgShellV6Surface(surface, surface); + QVERIFY(shellSurface); + auto *c = Test::renderAndWaitForShown(surface, QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeClient(), c); + + // no active animations at the beginning + QCOMPARE(effect->state().count(), 0); + + // trigger windowClosed signal + surface->deleteLater(); + QVERIFY(effectOutputSpy.count() == 1 || effectOutputSpy.wait()); + + if (keepAlive) { + QCOMPARE(effect->state().count(), 1); + + QTest::qWait(500); + QCOMPARE(effect->state().count(), 1); + + QTest::qWait(500 + 100); // 100ms is extra safety margin + QCOMPARE(effect->state().count(), 0); + } else { + // the test effect doesn't keep the window alive, so it should be + // removed immediately + QSignalSpy deletedRemovedSpy(workspace(), &Workspace::deletedRemoved); + QVERIFY(deletedRemovedSpy.isValid()); + QVERIFY(deletedRemovedSpy.count() == 1 || deletedRemovedSpy.wait(100)); // 100ms is less than duration of the animation + QCOMPARE(effect->state().count(), 0); + } +} WAYLANDTEST_MAIN(ScriptedEffectsTest) #include "scripted_effects_test.moc" diff --git a/autotests/integration/effects/scripts/keepAliveTest.js b/autotests/integration/effects/scripts/keepAliveTest.js new file mode 100644 index 000000000..cf3732dc5 --- /dev/null +++ b/autotests/integration/effects/scripts/keepAliveTest.js @@ -0,0 +1,13 @@ +"use strict"; + +effects.windowClosed.connect(function (window) { + animate({ + window: window, + type: Effect.Scale, + duration: 1000, + from: 1.0, + to: 0.0 + // by default, keepAlive is set to true + }); + sendTestResponse("triggered"); +}); diff --git a/autotests/integration/effects/scripts/keepAliveTestDontKeep.js b/autotests/integration/effects/scripts/keepAliveTestDontKeep.js new file mode 100644 index 000000000..72bdc5d66 --- /dev/null +++ b/autotests/integration/effects/scripts/keepAliveTestDontKeep.js @@ -0,0 +1,13 @@ +"use strict"; + +effects.windowClosed.connect(function (window) { + animate({ + window: window, + type: Effect.Scale, + duration: 1000, + from: 1.0, + to: 0.0, + keepAlive: false + }); + sendTestResponse("triggered"); +}); diff --git a/effects/dialogparent/package/contents/code/main.js b/effects/dialogparent/package/contents/code/main.js index 2ca3c0e19..9bf63de0d 100644 --- a/effects/dialogparent/package/contents/code/main.js +++ b/effects/dialogparent/package/contents/code/main.js @@ -1,142 +1,143 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin 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 . *********************************************************************/ /*global effect, effects, animate, cancel, set, animationTime, Effect, QEasingCurve */ /*jslint continue: true */ var dialogParentEffect = { duration: animationTime(300), windowAdded: function (window) { "use strict"; if (window === null || window.modal === false) { return; } dialogParentEffect.dialogGotModality(window) }, dialogGotModality: function (window) { "use strict"; var mainWindows = window.mainWindows(); for (var i = 0; i < mainWindows.length; i += 1) { var w = mainWindows[i]; if (w.dialogParentAnimation !== undefined) { continue; } dialogParentEffect.startAnimation(w, dialogParentEffect.duration); } }, startAnimation: function (window, duration) { "use strict"; if (window.visible === false) { return; } window.dialogParentAnimation = set({ window: window, duration: duration, animations: [{ type: Effect.Saturation, to: 0.4 }, { type: Effect.Brightness, to: 0.6 }] }); }, windowClosed: function (window) { "use strict"; dialogParentEffect.cancelAnimation(window); if (window.modal === false) { return; } dialogParentEffect.dialogLostModality(window); }, dialogLostModality: function (window) { "use strict"; var mainWindows = window.mainWindows(); for (var i = 0; i < mainWindows.length; i += 1) { var w = mainWindows[i]; if (w.dialogParentAnimation === undefined) { continue; } cancel(w.dialogParentAnimation); w.dialogParentAnimation = undefined; animate({ window: w, duration: dialogParentEffect.duration, + keepAlive: false, animations: [{ type: Effect.Saturation, from: 0.4, to: 1.0 }, { type: Effect.Brightness, from: 0.6, to: 1.0 }] }); } }, cancelAnimation: function (window) { "use strict"; if (window.dialogParentAnimation !== undefined) { cancel(window.dialogParentAnimation); window.dialogParentAnimation = undefined; } }, desktopChanged: function () { "use strict"; var i, windows, window; windows = effects.stackingOrder; for (i = 0; i < windows.length; i += 1) { window = windows[i]; dialogParentEffect.cancelAnimation(window); dialogParentEffect.restartAnimation(window); } }, modalDialogChanged: function(dialog) { "use strict"; if (dialog.modal === false) dialogParentEffect.dialogLostModality(dialog); else if (dialog.modal === true) dialogParentEffect.dialogGotModality(dialog); }, restartAnimation: function (window) { "use strict"; if (window === null || window.findModal() === null) { return; } dialogParentEffect.startAnimation(window, 1); }, init: function () { "use strict"; var i, windows; effects.windowAdded.connect(dialogParentEffect.windowAdded); effects.windowClosed.connect(dialogParentEffect.windowClosed); effects.windowMinimized.connect(dialogParentEffect.cancelAnimation); effects.windowUnminimized.connect(dialogParentEffect.restartAnimation); effects.windowModalityChanged.connect(dialogParentEffect.modalDialogChanged) effects['desktopChanged(int,int)'].connect(dialogParentEffect.desktopChanged); effects.desktopPresenceChanged.connect(dialogParentEffect.cancelAnimation); effects.desktopPresenceChanged.connect(dialogParentEffect.restartAnimation); // start animation windows = effects.stackingOrder; for (i = 0; i < windows.length; i += 1) { dialogParentEffect.restartAnimation(windows[i]); } } }; dialogParentEffect.init(); diff --git a/libkwineffects/anidata.cpp b/libkwineffects/anidata.cpp index 443b67197..cd9f5c249 100644 --- a/libkwineffects/anidata.cpp +++ b/libkwineffects/anidata.cpp @@ -1,100 +1,114 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Thomas Lübking +Copyright (C) 2018 Vlad Zagorodniy 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 "anidata_p.h" #include "logging_p.h" QDebug operator<<(QDebug dbg, const KWin::AniData &a) { dbg.nospace() << a.debugInfo(); return dbg.space(); } using namespace KWin; static const int Gaussian = 46; FullScreenEffectLock::FullScreenEffectLock(Effect *effect) { effects->setActiveFullScreenEffect(effect); } FullScreenEffectLock::~FullScreenEffectLock() { effects->setActiveFullScreenEffect(nullptr); } +KeepAliveLock::KeepAliveLock(EffectWindow *w) + : m_window(w) +{ + m_window->refWindow(); +} + +KeepAliveLock::~KeepAliveLock() +{ + m_window->unrefWindow(); +} + AniData::AniData() : attribute(AnimationEffect::Opacity) , customCurve(0) // Linear , time(0) , duration(0) , meta(0) , startTime(0) , windowType((NET::WindowTypeMask)0) , waitAtSource(false) , keepAtTarget(false) + , keepAlive(true) { } AniData::AniData(AnimationEffect::Attribute a, int meta_, int ms, const FPx2 &to_, QEasingCurve curve_, int delay, const FPx2 &from_, bool waitAtSource_, bool keepAtTarget_, - FullScreenEffectLockPtr fullScreenEffectLock_) + FullScreenEffectLockPtr fullScreenEffectLock_, bool keepAlive) : attribute(a) , curve(curve_) , from(from_) , to(to_) , time(0) , duration(ms) , meta(meta_) , startTime(AnimationEffect::clock() + delay) , windowType((NET::WindowTypeMask)0) , fullScreenEffectLock(fullScreenEffectLock_) , waitAtSource(waitAtSource_) , keepAtTarget(keepAtTarget_) + , keepAlive(keepAlive) { } static QString attributeString(KWin::AnimationEffect::Attribute attribute) { switch (attribute) { case KWin::AnimationEffect::Opacity: return QStringLiteral("Opacity"); case KWin::AnimationEffect::Brightness: return QStringLiteral("Brightness"); case KWin::AnimationEffect::Saturation: return QStringLiteral("Saturation"); case KWin::AnimationEffect::Scale: return QStringLiteral("Scale"); case KWin::AnimationEffect::Translation: return QStringLiteral("Translation"); case KWin::AnimationEffect::Rotation: return QStringLiteral("Rotation"); case KWin::AnimationEffect::Position: return QStringLiteral("Position"); case KWin::AnimationEffect::Size: return QStringLiteral("Size"); case KWin::AnimationEffect::Clip: return QStringLiteral("Clip"); default: return QStringLiteral(" "); } } QString AniData::debugInfo() const { return QLatin1String("Animation: ") + attributeString(attribute) + QLatin1String("\n From: ") + from.toString() + QLatin1String("\n To: ") + to.toString() + QLatin1String("\n Started: ") + QString::number(AnimationEffect::clock() - startTime) + QLatin1String("ms ago\n") + QLatin1String( " Duration: ") + QString::number(duration) + QLatin1String("ms\n") + QLatin1String( " Passed: ") + QString::number(time) + QLatin1String("ms\n") + QLatin1String( " Applying: ") + QString::number(windowType) + QLatin1Char('\n'); } diff --git a/libkwineffects/anidata_p.h b/libkwineffects/anidata_p.h index eafd6f0fb..95e92c1c1 100644 --- a/libkwineffects/anidata_p.h +++ b/libkwineffects/anidata_p.h @@ -1,71 +1,91 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Thomas Lübking +Copyright (C) 2018 Vlad Zagorodniy 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 ANIDATA_H #define ANIDATA_H #include "kwinanimationeffect.h" #include #include namespace KWin { /** * Wraps effects->setActiveFullScreenEffect for the duration of it's lifespan */ class FullScreenEffectLock { public: FullScreenEffectLock(Effect *effect); ~FullScreenEffectLock(); private: Q_DISABLE_COPY(FullScreenEffectLock) }; typedef QSharedPointer FullScreenEffectLockPtr; +/** + * Keeps windows alive during animation after they got closed + **/ +class KeepAliveLock +{ +public: + KeepAliveLock(EffectWindow *w); + ~KeepAliveLock(); + +private: + EffectWindow *m_window; + Q_DISABLE_COPY(KeepAliveLock) +}; +typedef QSharedPointer KeepAliveLockPtr; + class KWINEFFECTS_EXPORT AniData { public: AniData(); AniData(AnimationEffect::Attribute a, int meta, int ms, const FPx2 &to, - QEasingCurve curve, int delay, const FPx2 &from, bool waitAtSource, bool keepAtTarget = false, FullScreenEffectLockPtr=FullScreenEffectLockPtr()); + QEasingCurve curve, int delay, const FPx2 &from, bool waitAtSource, + bool keepAtTarget = false, FullScreenEffectLockPtr=FullScreenEffectLockPtr(), + bool keepAlive = true); inline void addTime(int t) { time += t; } inline bool isOneDimensional() const { return from[0] == from[1] && to[0] == to[1]; } quint64 id{0}; QString debugInfo() const; AnimationEffect::Attribute attribute; QEasingCurve curve; int customCurve; FPx2 from, to; int time, duration; uint meta; qint64 startTime; NET::WindowTypeMask windowType; QSharedPointer fullScreenEffectLock; bool waitAtSource, keepAtTarget; + bool keepAlive; + KeepAliveLockPtr keepAliveLock; }; } // namespace QDebug operator<<(QDebug dbg, const KWin::AniData &a); #endif // ANIDATA_H diff --git a/libkwineffects/kwinanimationeffect.cpp b/libkwineffects/kwinanimationeffect.cpp index 5f0d8dc37..144996f22 100644 --- a/libkwineffects/kwinanimationeffect.cpp +++ b/libkwineffects/kwinanimationeffect.cpp @@ -1,968 +1,969 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Thomas Lübking 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 "kwinanimationeffect.h" #include "anidata_p.h" #include #include #include #include QDebug operator<<(QDebug dbg, const KWin::FPx2 &fpx2) { dbg.nospace() << fpx2[0] << "," << fpx2[1] << QString(fpx2.isValid() ? QStringLiteral(" (valid)") : QStringLiteral(" (invalid)")); return dbg.space(); } namespace KWin { QElapsedTimer AnimationEffect::s_clock; class AnimationEffectPrivate { public: AnimationEffectPrivate() { m_animated = m_damageDirty = m_animationsTouched = m_isInitialized = false; m_justEndedAnimation = 0; } AnimationEffect::AniMap m_animations; - EffectWindowList m_zombies; static quint64 m_animCounter; quint64 m_justEndedAnimation; // protect against cancel QWeakPointer m_fullScreenEffectLock; bool m_animated, m_damageDirty, m_needSceneRepaint, m_animationsTouched, m_isInitialized; }; } using namespace KWin; quint64 AnimationEffectPrivate::m_animCounter = 0; AnimationEffect::AnimationEffect() : d_ptr(new AnimationEffectPrivate()) { Q_D(AnimationEffect); d->m_animated = false; if (!s_clock.isValid()) s_clock.start(); /* this is the same as the QTimer::singleShot(0, SLOT(init())) kludge * defering the init and esp. the connection to the windowClosed slot */ QMetaObject::invokeMethod( this, "init", Qt::QueuedConnection ); } AnimationEffect::~AnimationEffect() { delete d_ptr; } void AnimationEffect::init() { Q_D(AnimationEffect); if (d->m_isInitialized) return; // not more than once, please d->m_isInitialized = true; /* by connecting the signal from a slot AFTER the inheriting class constructor had the chance to * connect it we can provide auto-referencing of animated and closed windows, since at the time * our slot will be called, the slot of the subclass has been (SIGNAL/SLOT connections are FIFO) * and has pot. started an animation so we have the window in our hash :) */ connect ( effects, SIGNAL(windowClosed(KWin::EffectWindow*)), SLOT(_windowClosed(KWin::EffectWindow*)) ); connect ( effects, SIGNAL(windowDeleted(KWin::EffectWindow*)), SLOT(_windowDeleted(KWin::EffectWindow*)) ); } bool AnimationEffect::isActive() const { Q_D(const AnimationEffect); return !d->m_animations.isEmpty(); } #define RELATIVE_XY(_FIELD_) const bool relative[2] = { static_cast(metaData(Relative##_FIELD_##X, meta)), \ static_cast(metaData(Relative##_FIELD_##Y, meta)) } void AnimationEffect::validate(Attribute a, uint &meta, FPx2 *from, FPx2 *to, const EffectWindow *w) const { if (a < NonFloatBase) { if (a == Scale) { QRect area = effects->clientArea(ScreenArea , w); if (from && from->isValid()) { RELATIVE_XY(Source); from->set(relative[0] ? (*from)[0] * area.width() / w->width() : (*from)[0], relative[1] ? (*from)[1] * area.height() / w->height() : (*from)[1]); } if (to && to->isValid()) { RELATIVE_XY(Target); to->set(relative[0] ? (*to)[0] * area.width() / w->width() : (*to)[0], relative[1] ? (*to)[1] * area.height() / w->height() : (*to)[1] ); } } else if (a == Rotation) { if (from && !from->isValid()) { setMetaData(SourceAnchor, metaData(TargetAnchor, meta), meta); from->set(0.0,0.0); } if (to && !to->isValid()) { setMetaData(TargetAnchor, metaData(SourceAnchor, meta), meta); to->set(0.0,0.0); } } if (from && !from->isValid()) from->set(1.0,1.0); if (to && !to->isValid()) to->set(1.0,1.0); } else if (a == Position) { QRect area = effects->clientArea(ScreenArea , w); QPoint pt = w->geometry().bottomRight(); // cannot be < 0 ;-) if (from) { if (from->isValid()) { RELATIVE_XY(Source); from->set(relative[0] ? area.x() + (*from)[0] * area.width() : (*from)[0], relative[1] ? area.y() + (*from)[1] * area.height() : (*from)[1]); } else { from->set(pt.x(), pt.y()); setMetaData(SourceAnchor, AnimationEffect::Bottom|AnimationEffect::Right, meta); } } if (to) { if (to->isValid()) { RELATIVE_XY(Target); to->set(relative[0] ? area.x() + (*to)[0] * area.width() : (*to)[0], relative[1] ? area.y() + (*to)[1] * area.height() : (*to)[1]); } else { to->set(pt.x(), pt.y()); setMetaData( TargetAnchor, AnimationEffect::Bottom|AnimationEffect::Right, meta ); } } } else if (a == Size) { QRect area = effects->clientArea(ScreenArea , w); if (from) { if (from->isValid()) { RELATIVE_XY(Source); from->set(relative[0] ? (*from)[0] * area.width() : (*from)[0], relative[1] ? (*from)[1] * area.height() : (*from)[1]); } else { from->set(w->width(), w->height()); } } if (to) { if (to->isValid()) { RELATIVE_XY(Target); to->set(relative[0] ? (*to)[0] * area.width() : (*to)[0], relative[1] ? (*to)[1] * area.height() : (*to)[1]); } else { to->set(w->width(), w->height()); } } } else if (a == Translation) { QRect area = w->rect(); if (from) { if (from->isValid()) { RELATIVE_XY(Source); from->set(relative[0] ? (*from)[0] * area.width() : (*from)[0], relative[1] ? (*from)[1] * area.height() : (*from)[1]); } else { from->set(0.0, 0.0); } } if (to) { if (to->isValid()) { RELATIVE_XY(Target); to->set(relative[0] ? (*to)[0] * area.width() : (*to)[0], relative[1] ? (*to)[1] * area.height() : (*to)[1]); } else { to->set(0.0, 0.0); } } } else if (a == Clip) { if (from && !from->isValid()) { from->set(1.0,1.0); setMetaData(SourceAnchor, metaData(TargetAnchor, meta), meta); } if (to && !to->isValid()) { to->set(1.0,1.0); setMetaData(TargetAnchor, metaData(SourceAnchor, meta), meta); } } else if (a == CrossFadePrevious) { if (from && !from->isValid()) { from->set(0.0); } if (to && !to->isValid()) { to->set(1.0); } } } -quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect) +quint64 AnimationEffect::p_animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect, bool keepAlive) { const bool waitAtSource = from.isValid(); validate(a, meta, &from, &to, w); if (a == CrossFadePrevious) w->referencePreviousWindowPixmap(); Q_D(AnimationEffect); if (!d->m_isInitialized) init(); // needs to ensure the window gets removed if deleted in the same event cycle if (d->m_animations.isEmpty()) { connect (effects, SIGNAL(windowGeometryShapeChanged(KWin::EffectWindow*,QRect)), SLOT(_expandedGeometryChanged(KWin::EffectWindow*,QRect))); connect (effects, SIGNAL(windowStepUserMovedResized(KWin::EffectWindow*,QRect)), SLOT(_expandedGeometryChanged(KWin::EffectWindow*,QRect))); connect (effects, SIGNAL(windowPaddingChanged(KWin::EffectWindow*,QRect)), SLOT(_expandedGeometryChanged(KWin::EffectWindow*,QRect))); } AniMap::iterator it = d->m_animations.find(w); if (it == d->m_animations.end()) it = d->m_animations.insert(w, QPair, QRect>(QList(), QRect())); FullScreenEffectLockPtr fullscreen; if (fullScreenEffect) { if (d->m_fullScreenEffectLock.isNull()) { fullscreen = FullScreenEffectLockPtr::create(this); d->m_fullScreenEffectLock = fullscreen.toWeakRef(); } else { fullscreen = d->m_fullScreenEffectLock.toStrongRef(); } } - it->first.append(AniData(a, meta, ms, to, curve, delay, from, waitAtSource, keepAtTarget, fullscreen)); + it->first.append(AniData(a, meta, ms, to, curve, delay, from, waitAtSource, keepAtTarget, fullscreen, keepAlive)); quint64 ret_id = ++d->m_animCounter; it->first.last().id = ret_id; it->second = QRect(); d->m_animationsTouched = true; if (delay > 0) { QTimer::singleShot(delay, this, SLOT(triggerRepaint())); const QSize &s = effects->virtualScreenSize(); if (waitAtSource) w->addLayerRepaint(0, 0, s.width(), s.height()); } else { triggerRepaint(); } return ret_id; } bool AnimationEffect::retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime) { Q_D(AnimationEffect); if (animationId == d->m_justEndedAnimation) return false; // this is just ending, do not try to retarget it for (AniMap::iterator entry = d->m_animations.begin(), mapEnd = d->m_animations.end(); entry != mapEnd; ++entry) { for (QList::iterator anim = entry->first.begin(), animEnd = entry->first.end(); anim != animEnd; ++anim) { if (anim->id == animationId) { anim->from.set(interpolated(*anim, 0), interpolated(*anim, 1)); validate(anim->attribute, anim->meta, nullptr, &newTarget, entry.key()); anim->to.set(newTarget[0], newTarget[1]); anim->duration = anim->time + newRemainingTime; return true; } } } return false; // no animation found } bool AnimationEffect::cancel(quint64 animationId) { Q_D(AnimationEffect); if (animationId == d->m_justEndedAnimation) return true; // this is just ending, do not try to cancel it but fake success for (AniMap::iterator entry = d->m_animations.begin(), mapEnd = d->m_animations.end(); entry != mapEnd; ++entry) { for (QList::iterator anim = entry->first.begin(), animEnd = entry->first.end(); anim != animEnd; ++anim) { if (anim->id == animationId) { entry->first.erase(anim); // remove the animation if (entry->first.isEmpty()) { // no other animations on the window, release it. - const int i = d->m_zombies.indexOf(entry.key()); - if ( i > -1 ) { - d->m_zombies.removeAt( i ); - entry.key()->unrefWindow(); - } d->m_animations.erase(entry); } if (d->m_animations.isEmpty()) disconnectGeometryChanges(); d->m_animationsTouched = true; // could be called from animationEnded return true; } } } return false; } void AnimationEffect::prePaintScreen( ScreenPrePaintData& data, int time ) { Q_D(AnimationEffect); if (d->m_animations.isEmpty()) { effects->prePaintScreen(data, time); return; } d->m_animationsTouched = false; AniMap::iterator entry = d->m_animations.begin(), mapEnd = d->m_animations.end(); d->m_animated = false; // short int transformed = 0; while (entry != mapEnd) { bool invalidateLayerRect = false; QList::iterator anim = entry->first.begin(), animEnd = entry->first.end(); int animCounter = 0; while (anim != animEnd) { if (anim->startTime > clock()) { if (!anim->waitAtSource) { ++anim; ++animCounter; continue; } } else { anim->addTime(time); } if (anim->time < anim->duration || anim->keepAtTarget) { // if (anim->attribute != Brightness && anim->attribute != Saturation && anim->attribute != Opacity) // transformed = true; d->m_animated = true; ++anim; ++animCounter; } else { EffectWindow *oldW = entry.key(); AniData *aData = &(*anim); if (aData->attribute == KWin::AnimationEffect::CrossFadePrevious) { oldW->unreferencePreviousWindowPixmap(); effects->addRepaint(oldW->expandedGeometry()); } d->m_justEndedAnimation = anim->id; animationEnded(oldW, anim->attribute, anim->meta); d->m_justEndedAnimation = 0; // NOTICE animationEnded is an external call and might have called "::animate" // as a result our iterators could now point random junk on the heap // so we've to restore the former states, ie. find our window list and animation if (d->m_animationsTouched) { d->m_animationsTouched = false; entry = d->m_animations.begin(), mapEnd = d->m_animations.end(); while (entry.key() != oldW && entry != mapEnd) ++entry; Q_ASSERT(entry != mapEnd); // usercode should not delete animations from animationEnded (not even possible atm.) anim = entry->first.begin(), animEnd = entry->first.end(); Q_ASSERT(animCounter < entry->first.count()); for (int i = 0; i < animCounter; ++i) ++anim; } anim = entry->first.erase(anim); invalidateLayerRect = d->m_damageDirty = true; animEnd = entry->first.end(); } } if (entry->first.isEmpty()) { - const int i = d->m_zombies.indexOf(entry.key()); - if ( i > -1 ) { - d->m_zombies.removeAt( i ); - entry.key()->unrefWindow(); - } data.paint |= entry->second; // d->m_damageDirty = true; // TODO likely no longer required entry = d->m_animations.erase(entry); mapEnd = d->m_animations.end(); } else { if (invalidateLayerRect) *const_cast(&(entry->second)) = QRect(); // invalidate ++entry; } } // janitorial... if (d->m_animations.isEmpty()) { disconnectGeometryChanges(); - if (!d->m_zombies.isEmpty()) { // this is actually not supposed to happen - foreach (EffectWindow *w, d->m_zombies) - w->unrefWindow(); - d->m_zombies.clear(); - } } effects->prePaintScreen(data, time); } static int xCoord(const QRect &r, int flag) { if (flag & AnimationEffect::Left) return r.x(); else if (flag & AnimationEffect::Right) return r.right(); else return r.x() + r.width()/2; } static int yCoord(const QRect &r, int flag) { if (flag & AnimationEffect::Top) return r.y(); else if (flag & AnimationEffect::Bottom) return r.bottom(); else return r.y() + r.height()/2; } QRect AnimationEffect::clipRect(const QRect &geo, const AniData &anim) const { QRect clip = geo; FPx2 ratio = anim.from + progress(anim) * (anim.to - anim.from); if (anim.from[0] < 1.0 || anim.to[0] < 1.0) { clip.setWidth(clip.width() * ratio[0]); } if (anim.from[1] < 1.0 || anim.to[1] < 1.0) { clip.setHeight(clip.height() * ratio[1]); } const QRect center = geo.adjusted(clip.width()/2, clip.height()/2, -(clip.width()+1)/2, -(clip.height()+1)/2 ); const int x[2] = { xCoord(center, metaData(SourceAnchor, anim.meta)), xCoord(center, metaData(TargetAnchor, anim.meta)) }; const int y[2] = { yCoord(center, metaData(SourceAnchor, anim.meta)), yCoord(center, metaData(TargetAnchor, anim.meta)) }; const QPoint d(x[0] + ratio[0]*(x[1]-x[0]), y[0] + ratio[1]*(y[1]-y[0])); clip.moveTopLeft(QPoint(d.x() - clip.width()/2, d.y() - clip.height()/2)); return clip; } void AnimationEffect::clipWindow(const EffectWindow *w, const AniData &anim, WindowQuadList &quads) const { return; const QRect geo = w->expandedGeometry(); QRect clip = AnimationEffect::clipRect(geo, anim); WindowQuadList filtered; if (clip.left() != geo.left()) { quads = quads.splitAtX(clip.left()); foreach (const WindowQuad &quad, quads) { if (quad.right() >= clip.left()) filtered << quad; } quads = filtered; filtered.clear(); } if (clip.right() != geo.right()) { quads = quads.splitAtX(clip.left()); foreach (const WindowQuad &quad, quads) { if (quad.right() <= clip.right()) filtered << quad; } quads = filtered; filtered.clear(); } if (clip.top() != geo.top()) { quads = quads.splitAtY(clip.top()); foreach (const WindowQuad &quad, quads) { if (quad.top() >= clip.top()) filtered << quad; } quads = filtered; filtered.clear(); } if (clip.bottom() != geo.bottom()) { quads = quads.splitAtY(clip.bottom()); foreach (const WindowQuad &quad, quads) { if (quad.bottom() <= clip.bottom()) filtered << quad; } quads = filtered; } } void AnimationEffect::disconnectGeometryChanges() { disconnect (effects,SIGNAL(windowGeometryShapeChanged(KWin::EffectWindow*,QRect)), this, SLOT(_expandedGeometryChanged(KWin::EffectWindow*,QRect))); disconnect (effects,SIGNAL(windowStepUserMovedResized(KWin::EffectWindow*,QRect)), this, SLOT(_expandedGeometryChanged(KWin::EffectWindow*,QRect))); disconnect (effects,SIGNAL(windowPaddingChanged(KWin::EffectWindow*,QRect)), this, SLOT(_expandedGeometryChanged(KWin::EffectWindow*,QRect))); } void AnimationEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time ) { Q_D(AnimationEffect); if ( d->m_animated ) { AniMap::const_iterator entry = d->m_animations.constFind( w ); if ( entry != d->m_animations.constEnd() ) { bool isUsed = false; for (QList::const_iterator anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim) { if (anim->startTime > clock() && !anim->waitAtSource) continue; isUsed = true; if (anim->attribute == Opacity || anim->attribute == CrossFadePrevious) data.setTranslucent(); else if (!(anim->attribute == Brightness || anim->attribute == Saturation)) { data.setTransformed(); data.mask |= PAINT_WINDOW_TRANSFORMED; if (anim->attribute == Clip) clipWindow(w, *anim, data.quads); } } if ( isUsed ) { if ( w->isMinimized() ) w->enablePainting( EffectWindow::PAINT_DISABLED_BY_MINIMIZE ); else if ( w->isDeleted() ) w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DELETE ); else if ( !w->isOnCurrentDesktop() ) w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP ); // if( !w->isPaintingEnabled() && !effects->activeFullScreenEffect() ) // effects->addLayerRepaint(w->expandedGeometry()); } } } effects->prePaintWindow( w, data, time ); } static inline float geometryCompensation(int flags, float v) { if (flags & (AnimationEffect::Left|AnimationEffect::Top)) return 0.0; // no compensation required if (flags & (AnimationEffect::Right|AnimationEffect::Bottom)) return 1.0 - v; // full compensation return 0.5 * (1.0 - v); // half compensation } void AnimationEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) { Q_D(AnimationEffect); if ( d->m_animated ) { AniMap::const_iterator entry = d->m_animations.constFind( w ); if ( entry != d->m_animations.constEnd() ) { for ( QList::const_iterator anim = entry->first.constBegin(); anim != entry->first.constEnd(); ++anim ) { if (anim->startTime > clock() && !anim->waitAtSource) continue; switch (anim->attribute) { case Opacity: data.multiplyOpacity(interpolated(*anim)); break; case Brightness: data.multiplyBrightness(interpolated(*anim)); break; case Saturation: data.multiplySaturation(interpolated(*anim)); break; case Scale: { const QSize sz = w->geometry().size(); float f1(1.0), f2(0.0); if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // scale x f1 = interpolated(*anim, 0); f2 = geometryCompensation( anim->meta & AnimationEffect::Horizontal, f1 ); data.translate(f2 * sz.width()); data.setXScale(data.xScale() * f1); } if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // scale y if (!anim->isOneDimensional()) { f1 = interpolated(*anim, 1); f2 = geometryCompensation( anim->meta & AnimationEffect::Vertical, f1 ); } else if ( ((anim->meta & AnimationEffect::Vertical)>>1) != (anim->meta & AnimationEffect::Horizontal) ) f2 = geometryCompensation( anim->meta & AnimationEffect::Vertical, f1 ); data.translate(0.0, f2 * sz.height()); data.setYScale(data.yScale() * f1); } break; } case Clip: region = clipRect(w->expandedGeometry(), *anim); break; case Translation: data += QPointF(interpolated(*anim, 0), interpolated(*anim, 1)); break; case Size: { FPx2 dest = anim->from + progress(*anim) * (anim->to - anim->from); const QSize sz = w->geometry().size(); float f; if (anim->from[0] >= 0.0 && anim->to[0] >= 0.0) { // resize x f = dest[0]/sz.width(); data.translate(geometryCompensation( anim->meta & AnimationEffect::Horizontal, f ) * sz.width()); data.setXScale(data.xScale() * f); } if (anim->from[1] >= 0.0 && anim->to[1] >= 0.0) { // resize y f = dest[1]/sz.height(); data.translate(0.0, geometryCompensation( anim->meta & AnimationEffect::Vertical, f ) * sz.height()); data.setYScale(data.yScale() * f); } break; } case Position: { const QRect geo = w->geometry(); const float prgrs = progress(*anim); if ( anim->from[0] >= 0.0 && anim->to[0] >= 0.0 ) { float dest = interpolated(*anim, 0); const int x[2] = { xCoord(geo, metaData(SourceAnchor, anim->meta)), xCoord(geo, metaData(TargetAnchor, anim->meta)) }; data.translate(dest - (x[0] + prgrs*(x[1] - x[0]))); } if ( anim->from[1] >= 0.0 && anim->to[1] >= 0.0 ) { float dest = interpolated(*anim, 1); const int y[2] = { yCoord(geo, metaData(SourceAnchor, anim->meta)), yCoord(geo, metaData(TargetAnchor, anim->meta)) }; data.translate(0.0, dest - (y[0] + prgrs*(y[1] - y[0]))); } break; } case Rotation: { data.setRotationAxis((Qt::Axis)metaData(Axis, anim->meta)); const float prgrs = progress(*anim); data.setRotationAngle(anim->from[0] + prgrs*(anim->to[0] - anim->from[0])); const QRect geo = w->rect(); const uint sAnchor = metaData(SourceAnchor, anim->meta), tAnchor = metaData(TargetAnchor, anim->meta); QPointF pt(xCoord(geo, sAnchor), yCoord(geo, sAnchor)); if (tAnchor != sAnchor) { QPointF pt2(xCoord(geo, tAnchor), yCoord(geo, tAnchor)); pt += static_cast(prgrs)*(pt2 - pt); } data.setRotationOrigin(QVector3D(pt)); break; } case Generic: genericAnimation(w, data, progress(*anim), anim->meta); break; case CrossFadePrevious: data.setCrossFadeProgress(progress(*anim)); break; default: break; } } } } effects->paintWindow( w, mask, region, data ); } void AnimationEffect::postPaintScreen() { Q_D(AnimationEffect); if ( d->m_animated ) { if (d->m_damageDirty) updateLayerRepaints(); if (d->m_needSceneRepaint) { effects->addRepaintFull(); } else { AniMap::const_iterator it = d->m_animations.constBegin(), end = d->m_animations.constEnd(); for (; it != end; ++it) { bool addRepaint = false; QList::const_iterator anim = it->first.constBegin(); for (; anim != it->first.constEnd(); ++anim) { if (anim->startTime > clock()) continue; if (anim->time < anim->duration) { addRepaint = true; break; } } if (addRepaint) { it.key()->addLayerRepaint(it->second); } } } } effects->postPaintScreen(); } float AnimationEffect::interpolated( const AniData &a, int i ) const { if (a.startTime > clock()) return a.from[i]; if (a.time < a.duration) return a.from[i] + a.curve.valueForProgress( ((float)a.time)/a.duration )*(a.to[i] - a.from[i]); return a.to[i]; // we're done and "waiting" at the target value } float AnimationEffect::progress( const AniData &a ) const { if (a.startTime > clock()) return 0.0; if (a.time < a.duration) return a.curve.valueForProgress( ((float)a.time)/a.duration ); return 1.0; // we're done and "waiting" at the target value } // TODO - get this out of the header - the functionpointer usage of QEasingCurve somehow sucks ;-) // qreal AnimationEffect::qecGaussian(qreal progress) // exp(-5*(2*x-1)^2) // { // progress = 2*progress - 1; // progress *= -5*progress; // return qExp(progress); // } int AnimationEffect::metaData( MetaType type, uint meta ) { switch (type) { case SourceAnchor: return ((meta>>5) & 0x1f); case TargetAnchor: return (meta& 0x1f); case RelativeSourceX: case RelativeSourceY: case RelativeTargetX: case RelativeTargetY: { const int shift = 10 + type - RelativeSourceX; return ((meta>>shift) & 1); } case Axis: return ((meta>>10) & 3); default: return 0; } } void AnimationEffect::setMetaData( MetaType type, uint value, uint &meta ) { switch (type) { case SourceAnchor: meta &= ~(0x1f<<5); meta |= ((value & 0x1f)<<5); break; case TargetAnchor: meta &= ~(0x1f); meta |= (value & 0x1f); break; case RelativeSourceX: case RelativeSourceY: case RelativeTargetX: case RelativeTargetY: { const int shift = 10 + type - RelativeSourceX; if (value) meta |= (1<m_animations.constBegin(), mapEnd = d->m_animations.constEnd(); entry != mapEnd; ++entry) *const_cast(&(entry->second)) = QRect(); updateLayerRepaints(); if (d->m_needSceneRepaint) { effects->addRepaintFull(); } else { AniMap::const_iterator it = d->m_animations.constBegin(), end = d->m_animations.constEnd(); for (; it != end; ++it) { it.key()->addLayerRepaint(it->second); } } } static float fixOvershoot(float f, const AniData &d, short int dir, float s = 1.1) { switch(d.curve.type()) { case QEasingCurve::InOutElastic: case QEasingCurve::InOutBack: return f * s; case QEasingCurve::InElastic: case QEasingCurve::OutInElastic: case QEasingCurve::OutBack: return (dir&2) ? f * s : f; case QEasingCurve::OutElastic: case QEasingCurve::InBack: return (dir&1) ? f * s : f; default: return f; } } void AnimationEffect::updateLayerRepaints() { Q_D(AnimationEffect); d->m_needSceneRepaint = false; for (AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd(); entry != mapEnd; ++entry) { if (!entry->second.isNull()) continue; float f[2] = {1.0, 1.0}; float t[2] = {0.0, 0.0}; bool createRegion = false; QList rects; QRect *layerRect = const_cast(&(entry->second)); for (QList::const_iterator anim = entry->first.constBegin(), animEnd = entry->first.constEnd(); anim != animEnd; ++anim) { if (anim->startTime > clock()) continue; switch (anim->attribute) { case Opacity: case Brightness: case Saturation: case CrossFadePrevious: createRegion = true; break; case Rotation: createRegion = false; *layerRect = QRect(QPoint(0, 0), effects->virtualScreenSize()); goto region_creation; // sic! no need to do anything else case Generic: d->m_needSceneRepaint = true; // we don't know whether this will change visual stacking order return; // sic! no need to do anything else case Translation: case Position: { createRegion = true; QRect r(entry.key()->geometry()); int x[2] = {0,0}; int y[2] = {0,0}; if (anim->attribute == Translation) { x[0] = anim->from[0]; x[1] = anim->to[0]; y[0] = anim->from[1]; y[1] = anim->to[1]; } else { if ( anim->from[0] >= 0.0 && anim->to[0] >= 0.0 ) { x[0] = anim->from[0] - xCoord(r, metaData(SourceAnchor, anim->meta)); x[1] = anim->to[0] - xCoord(r, metaData(TargetAnchor, anim->meta)); } if ( anim->from[1] >= 0.0 && anim->to[1] >= 0.0 ) { y[0] = anim->from[1] - yCoord(r, metaData(SourceAnchor, anim->meta)); y[1] = anim->to[1] - yCoord(r, metaData(TargetAnchor, anim->meta)); } } r = entry.key()->expandedGeometry(); rects << r.translated(x[0], y[0]) << r.translated(x[1], y[1]); break; } case Clip: createRegion = true; break; case Size: case Scale: { createRegion = true; const QSize sz = entry.key()->geometry().size(); float fx = qMax(fixOvershoot(anim->from[0], *anim, 1), fixOvershoot(anim->to[0], *anim, 2)); // float fx = qMax(interpolated(*anim,0), anim->to[0]); if (fx >= 0.0) { if (anim->attribute == Size) fx /= sz.width(); f[0] *= fx; t[0] += geometryCompensation( anim->meta & AnimationEffect::Horizontal, fx ) * sz.width(); } // float fy = qMax(interpolated(*anim,1), anim->to[1]); float fy = qMax(fixOvershoot(anim->from[1], *anim, 1), fixOvershoot(anim->to[1], *anim, 2)); if (fy >= 0.0) { if (anim->attribute == Size) fy /= sz.height(); if (!anim->isOneDimensional()) { f[1] *= fy; t[1] += geometryCompensation( anim->meta & AnimationEffect::Vertical, fy ) * sz.height(); } else if ( ((anim->meta & AnimationEffect::Vertical)>>1) != (anim->meta & AnimationEffect::Horizontal) ) { f[1] *= fx; t[1] += geometryCompensation( anim->meta & AnimationEffect::Vertical, fx ) * sz.height(); } } break; } } } region_creation: if (createRegion) { const QRect geo = entry.key()->expandedGeometry(); if (rects.isEmpty()) rects << geo; QList::const_iterator r, rEnd = rects.constEnd(); for ( r = rects.constBegin(); r != rEnd; ++r) { // transform const_cast(&(*r))->setSize(QSize(qRound(r->width()*f[0]), qRound(r->height()*f[1]))); const_cast(&(*r))->translate(t[0], t[1]); // "const_cast" - don't do that at home, kids ;-) } QRect rect = rects.at(0); if (rects.count() > 1) { for ( r = rects.constBegin() + 1; r != rEnd; ++r) // unite rect |= *r; const int dx = 110*(rect.width() - geo.width())/100 + 1 - rect.width() + geo.width(); const int dy = 110*(rect.height() - geo.height())/100 + 1 - rect.height() + geo.height(); rect.adjust(-dx,-dy,dx,dy); // fix pot. overshoot } *layerRect = rect; } } d->m_damageDirty = false; } void AnimationEffect::_expandedGeometryChanged(KWin::EffectWindow *w, const QRect &old) { Q_UNUSED(old) Q_D(AnimationEffect); AniMap::const_iterator entry = d->m_animations.constFind(w); if (entry != d->m_animations.constEnd()) { *const_cast(&(entry->second)) = QRect(); updateLayerRepaints(); if (!entry->second.isNull()) // actually got updated, ie. is in use - ensure it get's a repaint w->addLayerRepaint(entry->second); } } void AnimationEffect::_windowClosed( EffectWindow* w ) { Q_D(AnimationEffect); - if (d->m_animations.contains(w) && !d->m_zombies.contains(w)) { - w->refWindow(); - d->m_zombies << w; + + auto it = d->m_animations.find(w); + if (it == d->m_animations.end()) { + return; + } + + KeepAliveLockPtr keepAliveLock; + + QList &animations = (*it).first; + for (auto animationIt = animations.begin(); + animationIt != animations.end(); + ++animationIt) { + if (!(*animationIt).keepAlive) { + continue; + } + + if (keepAliveLock.isNull()) { + keepAliveLock = KeepAliveLockPtr::create(w); + } + + (*animationIt).keepAliveLock = keepAliveLock; } } void AnimationEffect::_windowDeleted( EffectWindow* w ) { Q_D(AnimationEffect); - d->m_zombies.removeAll( w ); // TODO this line is a workaround for a bug in KWin 4.8.0 & 4.8.1 d->m_animations.remove( w ); } QString AnimationEffect::debug(const QString &/*parameter*/) const { Q_D(const AnimationEffect); QString dbg; if (d->m_animations.isEmpty()) dbg = QStringLiteral("No window is animated"); else { AniMap::const_iterator entry = d->m_animations.constBegin(), mapEnd = d->m_animations.constEnd(); for (; entry != mapEnd; ++entry) { QString caption = entry.key()->isDeleted() ? QStringLiteral("[Deleted]") : entry.key()->caption(); if (caption.isEmpty()) caption = QStringLiteral("[Untitled]"); dbg += QLatin1String("Animating window: ") + caption + QLatin1Char('\n'); QList::const_iterator anim = entry->first.constBegin(), animEnd = entry->first.constEnd(); for (; anim != animEnd; ++anim) dbg += anim->debugInfo(); } } return dbg; } AnimationEffect::AniMap AnimationEffect::state() const { Q_D(const AnimationEffect); return d->m_animations; } #include "moc_kwinanimationeffect.cpp" diff --git a/libkwineffects/kwinanimationeffect.h b/libkwineffects/kwinanimationeffect.h index ce46f3449..a71bf1908 100644 --- a/libkwineffects/kwinanimationeffect.h +++ b/libkwineffects/kwinanimationeffect.h @@ -1,240 +1,241 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 Thomas Lübking 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 ANIMATION_EFFECT_H #define ANIMATION_EFFECT_H #include #include #include #include #include namespace KWin { class KWINEFFECTS_EXPORT FPx2 { public: FPx2() { f[0] = f[1] = 0.0; valid = false; } explicit FPx2(float v) { f[0] = f[1] = v; valid = true; } FPx2(float v1, float v2) { f[0] = v1; f[1] = v2; valid = true; } FPx2(const FPx2 &other) { f[0] = other.f[0]; f[1] = other.f[1]; valid = other.valid; } explicit FPx2(const QPoint &other) { f[0] = other.x(); f[1] = other.y(); valid = true; } explicit FPx2(const QPointF &other) { f[0] = other.x(); f[1] = other.y(); valid = true; } explicit FPx2(const QSize &other) { f[0] = other.width(); f[1] = other.height(); valid = true; } explicit FPx2(const QSizeF &other) { f[0] = other.width(); f[1] = other.height(); valid = true; } inline void invalidate() { valid = false; } inline bool isValid() const { return valid; } inline float operator[](int n) const { return f[n]; } inline QString toString() const { QString ret; if (valid) ret = QString::number(f[0]) + QLatin1Char(',') + QString::number(f[1]); else ret = QString(); return ret; } inline FPx2 &operator+=(const FPx2 &other) { f[0] += other[0]; f[1] += other[1]; return *this; } inline FPx2 &operator-=(const FPx2 &other) { f[0] -= other[0]; f[1] -= other[1]; return *this; } inline FPx2 &operator*=(float fl) { f[0] *= fl; f[1] *= fl; return *this; } inline FPx2 &operator/=(float fl) { f[0] /= fl; f[1] /= fl; return *this; } friend inline bool operator==(const FPx2 &f1, const FPx2 &f2) { return f1[0] == f2[0] && f1[1] == f2[1]; } friend inline bool operator!=(const FPx2 &f1, const FPx2 &f2) { return f1[0] != f2[0] || f1[1] != f2[1]; } friend inline const FPx2 operator+(const FPx2 &f1, const FPx2 &f2) { return FPx2( f1[0] + f2[0], f1[1] + f2[1] ); } friend inline const FPx2 operator-(const FPx2 &f1, const FPx2 &f2) { return FPx2( f1[0] - f2[0], f1[1] - f2[1] ); } friend inline const FPx2 operator*(const FPx2 &f, float fl) { return FPx2( f[0] * fl, f[1] * fl ); } friend inline const FPx2 operator*(float fl, const FPx2 &f) { return FPx2( f[0] * fl, f[1] *fl ); } friend inline const FPx2 operator-(const FPx2 &f) { return FPx2( -f[0], -f[1] ); } friend inline const FPx2 operator/(const FPx2 &f, float fl) { return FPx2( f[0] / fl, f[1] / fl ); } inline void set(float v) { f[0] = v; valid = true; } inline void set(float v1, float v2) { f[0] = v1; f[1] = v2; valid = true; } private: float f[2]; bool valid; }; class AniData; class AnimationEffectPrivate; class KWINEFFECTS_EXPORT AnimationEffect : public Effect { Q_OBJECT Q_ENUMS(Anchor) Q_ENUMS(Attribute) Q_ENUMS(MetaType) public: typedef QMap< EffectWindow*, QPair, QRect> > AniMap; enum Anchor { Left = 1<<0, Top = 1<<1, Right = 1<<2, Bottom = 1<<3, Horizontal = Left|Right, Vertical = Top|Bottom, Mouse = 1<<4 }; enum Attribute { Opacity = 0, Brightness, Saturation, Scale, Rotation, Position, Size, Translation, Clip, Generic, CrossFadePrevious, NonFloatBase = Position }; enum MetaType { SourceAnchor, TargetAnchor, RelativeSourceX, RelativeSourceY, RelativeTargetX, RelativeTargetY, Axis }; /** * Whenever you intend to connect to the EffectsHandler::windowClosed() signal, do so when reimplementing the constructor. * Do *not* add private slots named _windowClosed( EffectWindow* w ) or _windowDeleted( EffectWindow* w ) !! * The AnimationEffect connects them right *after* the construction. * If you shadow the _windowDeleted slot (it doesn't matter that it's a private slot!), this will lead to segfaults. * If you shadow _windowClosed() or connect your slot to EffectsHandler::windowClosed() after _windowClosed() was connected, animations for closing windows will fail. */ AnimationEffect(); ~AnimationEffect(); bool isActive() const; /** * Set and get predefined metatypes. * The first 24 bits are reserved for the AnimationEffect class - you can use the last 8 bits for custom hints. * In case you transform a Generic attribute, all 32 bits are yours and you can use them as you want and read them in your genericAnimation() implementation. */ static int metaData(MetaType type, uint meta ); static void setMetaData(MetaType type, uint value, uint &meta ); /** * Reimplemented from KWIn::Effect */ QString debug(const QString ¶meter) const; virtual void prePaintScreen( ScreenPrePaintData& data, int time ); virtual void prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time ); virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); virtual void postPaintScreen(); /** * Gaussian (bumper) animation curve for QEasingCurve */ static qreal qecGaussian(qreal progress) { progress = 2*progress - 1; progress *= -5*progress; return qExp(progress); } static inline qint64 clock() { return s_clock.elapsed(); } protected: /** * The central function of this class - call it to create an animated transition of any supported attribute * @param w - The EffectWindow to manipulate * @param a - The @enum Attribute to manipulate * @param meta - Basically a wildcard to carry various extra information, eg. the anchor, relativity or rotation axis. You will probably use require it when performing Generic animations. * @param ms - How long the transition will last * @param to - The target value. FPx2 is an agnostic two component float type (like QPointF or QSizeF, but without requiring to be either and supporting an invalid state) * @param shape - How the animation progresses, eg. Linear progresses constantly while Exponential start slow and becomes very fast in the end * @param delay - When the animation will start compared to "now" (the window will remain at the "from" position until then) * @param from - The starting value, the default is invalid, ie. the attribute for the window is not transformed in the beginning * @param fullScreen - Sets this effect as the active full screen effect for the duration of the animation + * @param keepAlive - Whether closed windows should be kept alive during animation * @return an ID that you can use to cancel a running animation */ - quint64 animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false) - { return p_animate(w, a, meta, ms, to, curve, delay, from, false, fullScreen); } + quint64 animate( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false, bool keepAlive = true) + { return p_animate(w, a, meta, ms, to, curve, delay, from, false, fullScreen, keepAlive); } /** * Equal to ::animate() with one important difference: * The target value for the attribute is kept until you ::cancel() this animation * @return an ID that you need to use to cancel this manipulation */ - quint64 set( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false) - { return p_animate(w, a, meta, ms, to, curve, delay, from, true, fullScreen); } + quint64 set( EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve = QEasingCurve(), int delay = 0, FPx2 from = FPx2(), bool fullScreen = false, bool keepAlive = true) + { return p_animate(w, a, meta, ms, to, curve, delay, from, true, fullScreen, keepAlive); } /** * this allows to alter the target (but not type or curve) of a running animation * with the ID @param animationId * @param newTarget alters the "to" parameter of the animation * If @param newRemainingTime allows to lengthen (or shorten) the remaining time * of the animation. By default (-1) the remaining time remains unchanged * * Please use @function cancel to cancel an animation rather than altering it. * NOTICE that you can NOT retarget an animation that just has just @function animationEnded ! * @return whether there was such animation and it could be altered */ bool retarget(quint64 animationId, FPx2 newTarget, int newRemainingTime = -1); /** * Called whenever an animation end, passes the transformed @class EffectWindow @enum Attribute and originally supplied @param meta * You can reimplement it to keep a constant transformation for the window (ie. keep it a this opacity or position) or to start another animation */ virtual void animationEnded( EffectWindow *, Attribute, uint meta ) {Q_UNUSED(meta);} /** * Cancel a running animation. @return true if an animation for @p animationId was found (and canceled) * NOTICE that there is NO animated reset of the original value. You'll have to provide that with a second animation * NOTICE as well that this will eventually release a Deleted window. * If you intend to run another animation on the (Deleted) window, you have to do that before cancelling the old animation (to keep the window around) */ bool cancel(quint64 animationId); /** * Called if the transformed @enum Attribute is Generic. You should reimplement it if you transform this "Attribute". * You could use the meta information to eg. support more than one additional animations */ virtual void genericAnimation( EffectWindow *w, WindowPaintData &data, float progress, uint meta ) {Q_UNUSED(w); Q_UNUSED(data); Q_UNUSED(progress); Q_UNUSED(meta);} //Internal for unit tests AniMap state() const; private: - quint64 p_animate(EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect); + quint64 p_animate(EffectWindow *w, Attribute a, uint meta, int ms, FPx2 to, QEasingCurve curve, int delay, FPx2 from, bool keepAtTarget, bool fullScreenEffect, bool keepAlive); QRect clipRect(const QRect &windowRect, const AniData&) const; void clipWindow(const EffectWindow *, const AniData &, WindowQuadList &) const; float interpolated( const AniData&, int i = 0 ) const; float progress( const AniData& ) const; void disconnectGeometryChanges(); void updateLayerRepaints(); void validate(Attribute a, uint &meta, FPx2 *from, FPx2 *to, const EffectWindow *w) const; private Q_SLOTS: void init(); void triggerRepaint(); void _windowClosed( KWin::EffectWindow* w ); void _windowDeleted( KWin::EffectWindow* w ); void _expandedGeometryChanged(KWin::EffectWindow *w, const QRect &old); private: static QElapsedTimer s_clock; AnimationEffectPrivate * const d_ptr; Q_DECLARE_PRIVATE(AnimationEffect) }; } // namespace QDebug operator<<(QDebug dbg, const KWin::FPx2 &fpx2); Q_DECLARE_METATYPE(KWin::FPx2) #endif // ANIMATION_EFFECT_H diff --git a/scripting/scriptedeffect.cpp b/scripting/scriptedeffect.cpp index d3837ea32..730146e56 100644 --- a/scripting/scriptedeffect.cpp +++ b/scripting/scriptedeffect.cpp @@ -1,715 +1,737 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 Martin Gräßlin 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 "scriptedeffect.h" #include "meta.h" #include "scriptingutils.h" #include "workspace_wrapper.h" #include "../screens.h" #include "../screenedge.h" #include "scripting_logging.h" // KDE #include #include #include // Qt #include #include #include #include typedef KWin::EffectWindow* KEffectWindowRef; Q_DECLARE_METATYPE(KSharedConfigPtr) namespace KWin { QScriptValue kwinEffectScriptPrint(QScriptContext *context, QScriptEngine *engine) { ScriptedEffect *script = qobject_cast(context->callee().data().toQObject()); QString result; for (int i = 0; i < context->argumentCount(); ++i) { if (i > 0) { result.append(QLatin1Char(' ')); } result.append(context->argument(i).toString()); } qCDebug(KWIN_SCRIPTING) << script->scriptFile() << ":" << result; return engine->undefinedValue(); } QScriptValue kwinEffectScriptAnimationTime(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() != 1) { return engine->undefinedValue(); } if (!context->argument(0).isNumber()) { return engine->undefinedValue(); } return Effect::animationTime(context->argument(0).toInteger()); } QScriptValue kwinEffectDisplayWidth(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) Q_UNUSED(engine) return screens()->displaySize().width(); } QScriptValue kwinEffectDisplayHeight(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) Q_UNUSED(engine) return screens()->displaySize().height(); } QScriptValue kwinScriptGlobalShortcut(QScriptContext *context, QScriptEngine *engine) { return globalShortcut(context, engine); } QScriptValue kwinScriptScreenEdge(QScriptContext *context, QScriptEngine *engine) { 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, FullScreen = 1<<4 }; + enum { + Type = 1<<0, + Curve = 1<<1, + Delay = 1<<2, + Duration = 1<<3, + FullScreen = 1<<4, + KeepAlive = 1<<5 + }; AnimationEffect::Attribute type; QEasingCurve::Type curve; FPx2 from; FPx2 to; int delay; uint duration; uint set; uint metaData; bool fullScreenEffect; + bool keepAlive; }; AnimationSettings animationSettingsFromObject(QScriptValue &object) { AnimationSettings settings; settings.set = 0; settings.metaData = 0; settings.to = qscriptvalue_cast(object.property(QStringLiteral("to"))); settings.from = qscriptvalue_cast(object.property(QStringLiteral("from"))); QScriptValue duration = object.property(QStringLiteral("duration")); if (duration.isValid() && duration.isNumber()) { settings.duration = duration.toUInt32(); settings.set |= AnimationSettings::Duration; } else { settings.duration = 0; } QScriptValue delay = object.property(QStringLiteral("delay")); if (delay.isValid() && delay.isNumber()) { settings.delay = delay.toInt32(); settings.set |= AnimationSettings::Delay; } else { settings.delay = 0; } QScriptValue curve = object.property(QStringLiteral("curve")); if (curve.isValid() && curve.isNumber()) { settings.curve = static_cast(curve.toInt32()); settings.set |= AnimationSettings::Curve; } else { settings.curve = QEasingCurve::Linear; } QScriptValue type = object.property(QStringLiteral("type")); if (type.isValid() && type.isNumber()) { settings.type = static_cast(type.toInt32()); settings.set |= AnimationSettings::Type; } else { settings.type = static_cast(-1); } QScriptValue isFullScreen = object.property(QStringLiteral("fullScreen")); if (isFullScreen.isValid() && isFullScreen.isBool()) { settings.fullScreenEffect = isFullScreen.toBool(); settings.set |= AnimationSettings::FullScreen; } else { settings.fullScreenEffect = false; } + QScriptValue keepAlive = object.property(QStringLiteral("keepAlive")); + if (keepAlive.isValid() && keepAlive.isBool()) { + settings.keepAlive = keepAlive.toBool(); + settings.set |= AnimationSettings::KeepAlive; + } else { + settings.keepAlive = true; + } + return settings; } QList animationSettings(QScriptContext *context, ScriptedEffect *effect, EffectWindow **window) { QList settings; if (!effect) { context->throwError(QScriptContext::ReferenceError, QStringLiteral("Internal Scripted KWin Effect error")); return settings; } if (context->argumentCount() != 1) { context->throwError(QScriptContext::SyntaxError, QStringLiteral("Exactly one argument expected")); return settings; } if (!context->argument(0).isObject()) { context->throwError(QScriptContext::TypeError, QStringLiteral("Argument needs to be an object")); return settings; } QScriptValue object = context->argument(0); QScriptValue windowProperty = object.property(QStringLiteral("window")); if (!windowProperty.isValid() || !windowProperty.isObject()) { context->throwError(QScriptContext::TypeError, QStringLiteral("Window property missing in animation options")); return settings; } *window = qobject_cast(windowProperty.toQObject()); settings << animationSettingsFromObject(object); // global QScriptValue animations = object.property(QStringLiteral("animations")); // array if (animations.isValid()) { if (!animations.isArray()) { context->throwError(QScriptContext::TypeError, QStringLiteral("Animations provided but not an array")); settings.clear(); return settings; } const int length = static_cast(animations.property(QStringLiteral("length")).toInteger()); for (int i=0; ithrowError(QScriptContext::TypeError, QStringLiteral("Type property missing in animation options")); continue; } if (!(set & AnimationSettings::Duration)) { context->throwError(QScriptContext::TypeError, QStringLiteral("Duration property missing in animation options")); continue; } // Complete local animations from global settings if (!(s.set & AnimationSettings::Duration)) { s.duration = settings.at(0).duration; } if (!(s.set & AnimationSettings::Curve)) { s.curve = settings.at(0).curve; } if (!(s.set & AnimationSettings::Delay)) { s.delay = settings.at(0).delay; } if (!(s.set & AnimationSettings::FullScreen)) { s.fullScreenEffect = settings.at(0).fullScreenEffect; } + if (!(s.set & AnimationSettings::KeepAlive)) { + s.keepAlive = settings.at(0).keepAlive; + } s.metaData = 0; typedef QMap MetaTypeMap; static MetaTypeMap metaTypes({ {AnimationEffect::SourceAnchor, QStringLiteral("sourceAnchor")}, {AnimationEffect::TargetAnchor, QStringLiteral("targetAnchor")}, {AnimationEffect::RelativeSourceX, QStringLiteral("relativeSourceX")}, {AnimationEffect::RelativeSourceY, QStringLiteral("relativeSourceY")}, {AnimationEffect::RelativeTargetX, QStringLiteral("relativeTargetX")}, {AnimationEffect::RelativeTargetY, QStringLiteral("relativeTargetY")}, {AnimationEffect::Axis, QStringLiteral("axis")} }); for (MetaTypeMap::const_iterator it = metaTypes.constBegin(), end = metaTypes.constEnd(); it != end; ++it) { QScriptValue metaVal = value.property(*it); if (metaVal.isValid() && metaVal.isNumber()) { AnimationEffect::setMetaData(it.key(), metaVal.toInt32(), s.metaData); } } settings << s; } } } if (settings.count() == 1) { const uint set = settings.at(0).set; if (!(set & AnimationSettings::Type)) { context->throwError(QScriptContext::TypeError, QStringLiteral("Type property missing in animation options")); settings.clear(); } if (!(set & AnimationSettings::Duration)) { context->throwError(QScriptContext::TypeError, QStringLiteral("Duration property missing in animation options")); settings.clear(); } } else if (!(settings.at(0).set & AnimationSettings::Type)) { // invalid global settings.removeAt(0); // -> get rid of it, only used to complete the others } return settings; } QScriptValue kwinEffectAnimate(QScriptContext *context, QScriptEngine *engine) { ScriptedEffect *effect = qobject_cast(context->callee().data().toQObject()); EffectWindow *window; QList settings = animationSettings(context, effect, &window); if (settings.empty()) { context->throwError(QScriptContext::TypeError, QStringLiteral("No animations provided")); return engine->undefinedValue(); } if (!window) { context->throwError(QScriptContext::TypeError, QStringLiteral("Window property does not contain an EffectWindow")); return engine->undefinedValue(); } QScriptValue array = engine->newArray(settings.length()); int i = 0; foreach (const AnimationSettings &setting, settings) { array.setProperty(i, (uint)effect->animate(window, setting.type, setting.duration, setting.to, setting.from, setting.metaData, setting.curve, setting.delay, - setting.fullScreenEffect)); + setting.fullScreenEffect, + setting.keepAlive)); ++i; } return array; } QScriptValue kwinEffectSet(QScriptContext *context, QScriptEngine *engine) { ScriptedEffect *effect = qobject_cast(context->callee().data().toQObject()); EffectWindow *window; QList settings = animationSettings(context, effect, &window); if (settings.empty()) { context->throwError(QScriptContext::TypeError, QStringLiteral("No animations provided")); return engine->undefinedValue(); } if (!window) { context->throwError(QScriptContext::TypeError, QStringLiteral("Window property does not contain an EffectWindow")); return engine->undefinedValue(); } QList animIds; foreach (const AnimationSettings &setting, settings) { animIds << QVariant(effect->set(window, setting.type, setting.duration, setting.to, setting.from, setting.metaData, setting.curve, - setting.delay)); + setting.delay, + setting.fullScreenEffect, + setting.keepAlive)); } return engine->newVariant(animIds); } QList animations(const QVariant &v, bool *ok) { QList animIds; *ok = false; if (v.isValid()) { quint64 animId = v.toULongLong(ok); if (*ok) animIds << animId; } if (!*ok) { // may still be a variantlist of variants being quint64 QList list = v.toList(); if (!list.isEmpty()) { foreach (const QVariant &vv, list) { quint64 animId = vv.toULongLong(ok); if (*ok) animIds << animId; } *ok = !animIds.isEmpty(); } } return animIds; } QScriptValue fpx2ToScriptValue(QScriptEngine *eng, const KWin::FPx2 &fpx2) { QScriptValue val = eng->newObject(); val.setProperty(QStringLiteral("value1"), fpx2[0]); val.setProperty(QStringLiteral("value2"), fpx2[1]); return val; } void fpx2FromScriptValue(const QScriptValue &value, KWin::FPx2 &fpx2) { if (value.isNull()) { fpx2 = FPx2(); return; } if (value.isNumber()) { fpx2 = FPx2(value.toNumber()); return; } if (value.isObject()) { QScriptValue value1 = value.property(QStringLiteral("value1")); QScriptValue value2 = value.property(QStringLiteral("value2")); if (!value1.isValid() || !value2.isValid() || !value1.isNumber() || !value2.isNumber()) { qCDebug(KWIN_SCRIPTING) << "Cannot cast scripted FPx2 to C++"; fpx2 = FPx2(); return; } fpx2 = FPx2(value1.toNumber(), value2.toNumber()); } } QScriptValue kwinEffectRetarget(QScriptContext *context, QScriptEngine *engine) { ScriptedEffect *effect = qobject_cast(context->callee().data().toQObject()); if (context->argumentCount() < 2 || context->argumentCount() > 3) { context->throwError(QScriptContext::SyntaxError, QStringLiteral("2 or 3 arguments expected")); return engine->undefinedValue(); } QVariant v = context->argument(0).toVariant(); bool ok = false; QList animIds = animations(v, &ok); if (!ok) { context->throwError(QScriptContext::TypeError, QStringLiteral("Argument needs to be one or several quint64")); return engine->undefinedValue(); } FPx2 target; fpx2FromScriptValue(context->argument(1), target); ok = false; const int remainingTime = context->argumentCount() == 3 ? context->argument(2).toVariant().toInt() : -1; foreach (const quint64 &animId, animIds) { ok = effect->retarget(animId, target, remainingTime); if (!ok) { break; } } return QScriptValue(ok); } QScriptValue kwinEffectCancel(QScriptContext *context, QScriptEngine *engine) { ScriptedEffect *effect = qobject_cast(context->callee().data().toQObject()); if (context->argumentCount() != 1) { context->throwError(QScriptContext::SyntaxError, QStringLiteral("Exactly one argument expected")); return engine->undefinedValue(); } QVariant v = context->argument(0).toVariant(); bool ok = false; QList animIds = animations(v, &ok); if (!ok) { context->throwError(QScriptContext::TypeError, QStringLiteral("Argument needs to be one or several quint64")); return engine->undefinedValue(); } foreach (const quint64 &animId, animIds) { ok |= engine->newVariant(effect->cancel(animId)).toBool(); } return engine->newVariant(ok); } QScriptValue effectWindowToScriptValue(QScriptEngine *eng, const KEffectWindowRef &window) { return eng->newQObject(window, QScriptEngine::QtOwnership, QScriptEngine::ExcludeChildObjects | QScriptEngine::ExcludeDeleteLater | QScriptEngine::PreferExistingWrapperObject); } void effectWindowFromScriptValue(const QScriptValue &value, EffectWindow* &window) { window = qobject_cast(value.toQObject()); } ScriptedEffect *ScriptedEffect::create(const KPluginMetaData &effect) { const QString name = effect.pluginId(); const QString scriptName = effect.value(QStringLiteral("X-Plasma-MainScript")); if (scriptName.isEmpty()) { qCDebug(KWIN_SCRIPTING) << "X-Plasma-MainScript not set"; return nullptr; } const QString scriptFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(KWIN_NAME "/effects/") + name + QLatin1String("/contents/") + scriptName); if (scriptFile.isNull()) { qCDebug(KWIN_SCRIPTING) << "Could not locate the effect script"; return nullptr; } return ScriptedEffect::create(name, scriptFile, effect.value(QStringLiteral("X-KDE-Ordering")).toInt()); } ScriptedEffect *ScriptedEffect::create(const QString& effectName, const QString& pathToScript, int chainPosition) { ScriptedEffect *effect = new ScriptedEffect(); if (!effect->init(effectName, pathToScript)) { delete effect; return nullptr; } effect->m_chainPosition = chainPosition; return effect; } bool ScriptedEffect::supported() { return effects->animationsSupported(); } ScriptedEffect::ScriptedEffect() : AnimationEffect() , m_engine(new QScriptEngine(this)) , m_scriptFile(QString()) , m_config(nullptr) , m_chainPosition(0) { Q_ASSERT(effects); connect(m_engine, SIGNAL(signalHandlerException(QScriptValue)), SLOT(signalHandlerException(QScriptValue))); connect(effects, &EffectsHandler::activeFullScreenEffectChanged, this, [this]() { Effect* fullScreenEffect = effects->activeFullScreenEffect(); if (fullScreenEffect == m_activeFullScreenEffect) { return; } if (m_activeFullScreenEffect == this || fullScreenEffect == this) { emit isActiveFullScreenEffectChanged(); } m_activeFullScreenEffect = fullScreenEffect; }); } ScriptedEffect::~ScriptedEffect() { } bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript) { QFile scriptFile(pathToScript); if (!scriptFile.open(QIODevice::ReadOnly)) { qCDebug(KWIN_SCRIPTING) << "Could not open script file: " << pathToScript; return false; } m_effectName = effectName; m_scriptFile = pathToScript; // does the effect contain an KConfigXT file? const QString kconfigXTFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String(KWIN_NAME "/effects/") + m_effectName + QLatin1String("/contents/config/main.xml")); if (!kconfigXTFile.isNull()) { KConfigGroup cg = QCoreApplication::instance()->property("config").value()->group(QStringLiteral("Effect-%1").arg(m_effectName)); QFile xmlFile(kconfigXTFile); m_config = new KConfigLoader(cg, &xmlFile, this); m_config->load(); } QScriptValue effectsObject = m_engine->newQObject(effects, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater); m_engine->globalObject().setProperty(QStringLiteral("effects"), effectsObject, QScriptValue::Undeletable); m_engine->globalObject().setProperty(QStringLiteral("Effect"), m_engine->newQMetaObject(&ScriptedEffect::staticMetaObject)); #ifndef KWIN_UNIT_TEST m_engine->globalObject().setProperty(QStringLiteral("KWin"), m_engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject)); #endif m_engine->globalObject().setProperty(QStringLiteral("QEasingCurve"), m_engine->newQMetaObject(&QEasingCurve::staticMetaObject)); m_engine->globalObject().setProperty(QStringLiteral("effect"), m_engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater), QScriptValue::Undeletable); MetaScripting::registration(m_engine); qScriptRegisterMetaType(m_engine, effectWindowToScriptValue, effectWindowFromScriptValue); qScriptRegisterMetaType(m_engine, fpx2ToScriptValue, fpx2FromScriptValue); qScriptRegisterSequenceMetaType >(m_engine); // add our print QScriptValue printFunc = m_engine->newFunction(kwinEffectScriptPrint); printFunc.setData(m_engine->newQObject(this)); m_engine->globalObject().setProperty(QStringLiteral("print"), printFunc); // add our animationTime QScriptValue animationTimeFunc = m_engine->newFunction(kwinEffectScriptAnimationTime); animationTimeFunc.setData(m_engine->newQObject(this)); m_engine->globalObject().setProperty(QStringLiteral("animationTime"), animationTimeFunc); // add displayWidth and displayHeight QScriptValue displayWidthFunc = m_engine->newFunction(kwinEffectDisplayWidth); m_engine->globalObject().setProperty(QStringLiteral("displayWidth"), displayWidthFunc); QScriptValue displayHeightFunc = m_engine->newFunction(kwinEffectDisplayHeight); m_engine->globalObject().setProperty(QStringLiteral("displayHeight"), displayHeightFunc); // 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)); m_engine->globalObject().setProperty(QStringLiteral("animate"), animateFunc); // and the set variant QScriptValue setFunc = m_engine->newFunction(kwinEffectSet); setFunc.setData(m_engine->newQObject(this)); m_engine->globalObject().setProperty(QStringLiteral("set"), setFunc); // retarget QScriptValue retargetFunc = m_engine->newFunction(kwinEffectRetarget); retargetFunc.setData(m_engine->newQObject(this)); m_engine->globalObject().setProperty(QStringLiteral("retarget"), retargetFunc); // cancel... QScriptValue cancelFunc = m_engine->newFunction(kwinEffectCancel); cancelFunc.setData(m_engine->newQObject(this)); m_engine->globalObject().setProperty(QStringLiteral("cancel"), cancelFunc); QScriptValue ret = m_engine->evaluate(QString::fromUtf8(scriptFile.readAll())); if (ret.isError()) { signalHandlerException(ret); return false; } scriptFile.close(); return true; } void ScriptedEffect::animationEnded(KWin::EffectWindow *w, Attribute a, uint meta) { AnimationEffect::animationEnded(w, a, meta); emit animationEnded(w, 0); } bool ScriptedEffect::isActiveFullScreenEffect() const { return effects->activeFullScreenEffect() == this; } void ScriptedEffect::signalHandlerException(const QScriptValue &value) { if (value.isError()) { qCDebug(KWIN_SCRIPTING) << "KWin Effect script encountered an error at [Line " << m_engine->uncaughtExceptionLineNumber() << "]"; qCDebug(KWIN_SCRIPTING) << "Message: " << value.toString(); QScriptValueIterator iter(value); while (iter.hasNext()) { iter.next(); qCDebug(KWIN_SCRIPTING) << " " << iter.name() << ": " << iter.value().toString(); } } } -quint64 ScriptedEffect::animate(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, uint metaData, int curve, int delay, bool fullScreen) +quint64 ScriptedEffect::animate(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, uint metaData, int curve, int delay, bool fullScreen, bool keepAlive) { QEasingCurve qec; if (curve < QEasingCurve::Custom) qec.setType(static_cast(curve)); else if (curve == GaussianCurve) qec.setCustomType(qecGaussian); - return AnimationEffect::animate(w, a, metaData, ms, to, qec, delay, from, fullScreen); + return AnimationEffect::animate(w, a, metaData, ms, to, qec, delay, from, fullScreen, keepAlive); } -quint64 ScriptedEffect::set(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, uint metaData, int curve, int delay, bool fullScreen) +quint64 ScriptedEffect::set(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, uint metaData, int curve, int delay, bool fullScreen, bool keepAlive) { QEasingCurve qec; if (curve < QEasingCurve::Custom) qec.setType(static_cast(curve)); else if (curve == GaussianCurve) qec.setCustomType(qecGaussian); - return AnimationEffect::set(w, a, metaData, ms, to, qec, delay, from, fullScreen); + return AnimationEffect::set(w, a, metaData, ms, to, qec, delay, from, fullScreen, keepAlive); } bool ScriptedEffect::retarget(quint64 animationId, KWin::FPx2 newTarget, int newRemainingTime) { return AnimationEffect::retarget(animationId, newTarget, newRemainingTime); } bool ScriptedEffect::isGrabbed(EffectWindow* w, ScriptedEffect::DataRole grabRole) { void *e = w->data(static_cast(grabRole)).value(); if (e) { return e != this; } else { return false; } } void ScriptedEffect::reconfigure(ReconfigureFlags flags) { AnimationEffect::reconfigure(flags); if (m_config) { m_config->read(); } emit configChanged(); } void ScriptedEffect::registerShortcut(QAction *a, QScriptValue callback) { m_shortcutCallbacks.insert(a, callback); connect(a, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered())); } void ScriptedEffect::globalShortcutTriggered() { callGlobalShortcutCallback(this, sender()); } bool ScriptedEffect::borderActivated(ElectricBorder edge) { screenEdgeActivated(this, edge); return true; } QVariant ScriptedEffect::readConfig(const QString &key, const QVariant defaultValue) { if (!m_config) { return defaultValue; } 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.end()) { return false; } delete it.value(); m_touchScreenEdgeCallbacks.erase(it); return true; } QScriptEngine *ScriptedEffect::engine() const { return m_engine; } } // namespace diff --git a/scripting/scriptedeffect.h b/scripting/scriptedeffect.h index 8a30d0cde..0b667bb1b 100644 --- a/scripting/scriptedeffect.h +++ b/scripting/scriptedeffect.h @@ -1,142 +1,142 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 Martin Gräßlin 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_SCRIPTEDEFFECT_H #define KWIN_SCRIPTEDEFFECT_H #include class KConfigLoader; class KPluginMetaData; class QScriptEngine; class QScriptValue; namespace KWin { class KWIN_EXPORT ScriptedEffect : public KWin::AnimationEffect { Q_OBJECT Q_ENUMS(DataRole) Q_ENUMS(Qt::Axis) Q_ENUMS(Anchor) Q_ENUMS(MetaType) Q_ENUMS(EasingCurve) /** * True if we are the active fullscreen effect */ Q_PROPERTY(bool isActiveFullScreenEffect READ isActiveFullScreenEffect NOTIFY isActiveFullScreenEffectChanged) public: // copied from kwineffects.h enum DataRole { // Grab roles are used to force all other animations to ignore the window. // The value of the data is set to the Effect's `this` value. WindowAddedGrabRole = 1, WindowClosedGrabRole, WindowMinimizedGrabRole, WindowUnminimizedGrabRole, WindowForceBlurRole, ///< For fullscreen effects to enforce blurring of windows, WindowBlurBehindRole, ///< For single windows to blur behind WindowForceBackgroundContrastRole, ///< For fullscreen effects to enforce the background contrast, WindowBackgroundContrastRole, ///< For single windows to enable Background contrast LanczosCacheRole }; enum EasingCurve { GaussianCurve = 128 }; const QString &scriptFile() const { return m_scriptFile; } virtual void reconfigure(ReconfigureFlags flags); int requestedEffectChainPosition() const override { return m_chainPosition; } QString activeConfig() const; void setActiveConfig(const QString &name); static ScriptedEffect *create(const QString &effectName, const QString &pathToScript, int chainPosition); static ScriptedEffect *create(const KPluginMetaData &effect); static bool supported(); virtual ~ScriptedEffect(); /** * Whether another effect has grabbed the @p w with the given @p grabRole. * @param w The window to check * @param grabRole The grab role to check * @returns @c true if another window has grabbed the effect, @c false otherwise **/ Q_SCRIPTABLE bool isGrabbed(KWin::EffectWindow *w, DataRole grabRole); /** * Reads the value from the configuration data for the given key. * @param key The key to search for * @param defaultValue The value to return if the key is not found * @returns The config value if present **/ Q_SCRIPTABLE QVariant readConfig(const QString &key, const QVariant defaultValue = QVariant()); void registerShortcut(QAction *a, QScriptValue callback); const QHash &shortcutCallbacks() const { return m_shortcutCallbacks; } QHash > &screenEdgeCallbacks() { return m_screenEdgeCallbacks; } bool isActiveFullScreenEffect() const; bool registerTouchScreenCallback(int edge, QScriptValue callback); bool unregisterTouchScreenCallback(int edge); public Q_SLOTS: //curve should be of type QEasingCurve::type or ScriptedEffect::EasingCurve - quint64 animate(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false); - quint64 set(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false); + quint64 animate(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false, bool keepAlive = true); + quint64 set(KWin::EffectWindow *w, Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from = KWin::FPx2(), uint metaData = 0, int curve = QEasingCurve::Linear, int delay = 0, bool fullScreen = false, bool keepAlive = true); bool retarget(quint64 animationId, KWin::FPx2 newTarget, int newRemainingTime = -1); bool cancel(quint64 animationId) { return AnimationEffect::cancel(animationId); } virtual bool borderActivated(ElectricBorder border); Q_SIGNALS: /** * Signal emitted whenever the effect's config changed. **/ void configChanged(); void animationEnded(KWin::EffectWindow *w, quint64 animationId); void isActiveFullScreenEffectChanged(); protected: ScriptedEffect(); QScriptEngine *engine() const; bool init(const QString &effectName, const QString &pathToScript); void animationEnded(KWin::EffectWindow *w, Attribute a, uint meta); private Q_SLOTS: void signalHandlerException(const QScriptValue &value); void globalShortcutTriggered(); private: QScriptEngine *m_engine; QString m_effectName; QString m_scriptFile; QHash m_shortcutCallbacks; QHash > m_screenEdgeCallbacks; KConfigLoader *m_config; int m_chainPosition; QHash m_touchScreenEdgeCallbacks; Effect *m_activeFullScreenEffect = nullptr; }; } #endif // KWIN_SCRIPTEDEFFECT_H