7a7f9d1a34
The main difference is that the activation of an edge is no longer broadcasted to all effects and scripts, but instead a passed in slot of the Effect/Script is invoked. For this the EffectsHandler API is changed to take the Effect as an argument to (un)reserveElectricBorder. As callback slot the existing borderActivated is used. In addition the ScreenEdge monitors the object for beeing destroyed and unregisters the the edge automatically. This removes the need from the Effect to call unregister in the dtor. BUG: 309695 FIXED-IN: 4.11
497 lines
16 KiB
C++
497 lines
16 KiB
C++
/********************************************************************
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*********************************************************************/
|
|
|
|
#include "scriptedeffect.h"
|
|
#include "meta.h"
|
|
#include "scriptingutils.h"
|
|
#include "workspace_wrapper.h"
|
|
#ifdef KWIN_BUILD_SCREENEDGES
|
|
#include "../screenedge.h"
|
|
#endif
|
|
// KDE
|
|
#include <KDE/KConfigGroup>
|
|
#include <KDE/KDebug>
|
|
#include <KDE/KStandardDirs>
|
|
// Qt
|
|
#include <QtCore/QFile>
|
|
#include <QtScript/QScriptEngine>
|
|
#include <QtScript/QScriptValueIterator>
|
|
|
|
typedef KWin::EffectWindow* KEffectWindowRef;
|
|
|
|
Q_DECLARE_METATYPE(KWin::AnimationData*)
|
|
Q_SCRIPT_DECLARE_QMETAOBJECT(KWin::AnimationData, QObject*)
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
QScriptValue kwinEffectScriptPrint(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
ScriptedEffect *script = qobject_cast<ScriptedEffect*>(context->callee().data().toQObject());
|
|
QString result;
|
|
for (int i = 0; i < context->argumentCount(); ++i) {
|
|
if (i > 0) {
|
|
result.append(" ");
|
|
}
|
|
result.append(context->argument(i).toString());
|
|
}
|
|
kDebug(1212) << 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 Effect::displayWidth();
|
|
}
|
|
|
|
QScriptValue kwinEffectDisplayHeight(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
Q_UNUSED(context)
|
|
Q_UNUSED(engine)
|
|
return Effect::displayHeight();
|
|
}
|
|
|
|
QScriptValue kwinScriptGlobalShortcut(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
return globalShortcut<KWin::ScriptedEffect*>(context, engine);
|
|
}
|
|
|
|
QScriptValue kwinScriptScreenEdge(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
return registerScreenEdge<KWin::ScriptedEffect*>(context, engine);
|
|
}
|
|
|
|
struct AnimationSettings {
|
|
AnimationEffect::Attribute a;
|
|
QEasingCurve::Type curve;
|
|
bool curveSet;
|
|
FPx2 from;
|
|
FPx2 to;
|
|
int delay;
|
|
bool valid;
|
|
};
|
|
|
|
AnimationSettings animationSettingsFromObject(QScriptValue &object)
|
|
{
|
|
AnimationSettings settings;
|
|
settings.curve = QEasingCurve::Linear;
|
|
settings.valid = true;
|
|
settings.curveSet = false;
|
|
|
|
settings.to = qscriptvalue_cast<FPx2>(object.property("to"));
|
|
settings.from = qscriptvalue_cast<FPx2>(object.property("from"));
|
|
|
|
QScriptValue delay = object.property("delay");
|
|
if (delay.isValid() && delay.isNumber()) {
|
|
settings.delay = delay.toInt32();
|
|
} else {
|
|
settings.delay = 0;
|
|
}
|
|
|
|
QScriptValue curve = object.property("curve");
|
|
if (curve.isValid() && curve.isNumber()) {
|
|
settings.curve = static_cast<QEasingCurve::Type>(curve.toInt32());
|
|
settings.curveSet = true;
|
|
}
|
|
|
|
QScriptValue type = object.property("type");
|
|
if (!type.isValid() || !type.isNumber()) {
|
|
settings.valid = false;
|
|
}
|
|
settings.a = static_cast<AnimationEffect::Attribute>(type.toInt32());
|
|
|
|
return settings;
|
|
}
|
|
|
|
QScriptValue kwinEffectAnimate(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
ScriptedEffect *effect = qobject_cast<ScriptedEffect*>(context->callee().data().toQObject());
|
|
if (!effect) {
|
|
context->throwError(QScriptContext::ReferenceError, "Internal Scripted KWin Effect error");
|
|
return engine->undefinedValue();
|
|
}
|
|
if (context->argumentCount() != 1) {
|
|
context->throwError(QScriptContext::SyntaxError, "Exactly one argument expected");
|
|
return engine->undefinedValue();
|
|
}
|
|
if (!context->argument(0).isObject()) {
|
|
context->throwError(QScriptContext::TypeError, "Argument needs to be an object");
|
|
return engine->undefinedValue();
|
|
}
|
|
QScriptValue object = context->argument(0);
|
|
QScriptValue windowProperty = object.property("window");
|
|
if (!windowProperty.isValid() || !windowProperty.isObject()) {
|
|
context->throwError(QScriptContext::TypeError, "Window property missing in animation options");
|
|
return engine->undefinedValue();
|
|
}
|
|
EffectWindow *window = qobject_cast<EffectWindow*>(windowProperty.toQObject());
|
|
if (!window) {
|
|
context->throwError(QScriptContext::TypeError, "Window property does not contain an EffectWindow");
|
|
return engine->undefinedValue();
|
|
}
|
|
QScriptValue durationProperty = object.property("duration");
|
|
if (!durationProperty.isValid() || !durationProperty.isNumber()) {
|
|
context->throwError(QScriptContext::TypeError, "Duration property missing in animation options");
|
|
return engine->undefinedValue();
|
|
}
|
|
const int duration = durationProperty.toInt32();
|
|
|
|
QEasingCurve::Type curve = QEasingCurve::Linear;
|
|
QList<AnimationSettings> settings;
|
|
AnimationSettings globalSettings = animationSettingsFromObject(object);
|
|
if (globalSettings.valid) {
|
|
settings << globalSettings;
|
|
if (globalSettings.curveSet) {
|
|
curve = globalSettings.curve;
|
|
}
|
|
}
|
|
QScriptValue animations = object.property("animations");
|
|
if (animations.isValid()) {
|
|
if (!animations.isArray()) {
|
|
context->throwError(QScriptContext::TypeError, "Animations provided but not an array");
|
|
return engine->undefinedValue();
|
|
}
|
|
const int length = static_cast<int>(animations.property("length").toInteger());
|
|
for (int i=0; i<length; ++i) {
|
|
QScriptValue value = animations.property(QString::number(i));
|
|
if (!value.isValid()) {
|
|
continue;
|
|
}
|
|
if (value.isObject()) {
|
|
AnimationSettings s = animationSettingsFromObject(value);
|
|
if (s.valid) {
|
|
settings << s;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (settings.empty()) {
|
|
context->throwError(QScriptContext::TypeError, "No animations provided");
|
|
return engine->undefinedValue();
|
|
}
|
|
foreach (const AnimationSettings &setting, settings) {
|
|
effect->animate(window,
|
|
setting.a,
|
|
duration,
|
|
setting.to,
|
|
setting.from,
|
|
NULL,
|
|
setting.curveSet ? setting.curve : curve,
|
|
setting.delay);
|
|
}
|
|
|
|
return engine->newVariant(true);
|
|
}
|
|
|
|
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<EffectWindow*>(value.toQObject());
|
|
}
|
|
|
|
QScriptValue fpx2ToScriptValue(QScriptEngine *eng, const KWin::FPx2 &fpx2)
|
|
{
|
|
QScriptValue val = eng->newObject();
|
|
val.setProperty("value1", fpx2[0]);
|
|
val.setProperty("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("value1");
|
|
QScriptValue value2 = value.property("value2");
|
|
if (!value1.isValid() || !value2.isValid() || !value1.isNumber() || !value2.isNumber()) {
|
|
kDebug(1212) << "Cannot cast scripted FPx2 to C++";
|
|
fpx2 = FPx2();
|
|
return;
|
|
}
|
|
fpx2 = FPx2(value1.toNumber(), value2.toNumber());
|
|
}
|
|
}
|
|
|
|
ScriptedEffect *ScriptedEffect::create(const QString& effectName, const QString& pathToScript)
|
|
{
|
|
ScriptedEffect *effect = new ScriptedEffect();
|
|
if (!effect->init(effectName, pathToScript)) {
|
|
delete effect;
|
|
return NULL;
|
|
}
|
|
return effect;
|
|
}
|
|
|
|
ScriptedEffect::ScriptedEffect()
|
|
: AnimationEffect()
|
|
, m_engine(new QScriptEngine(this))
|
|
, m_scriptFile(QString())
|
|
{
|
|
connect(m_engine, SIGNAL(signalHandlerException(QScriptValue)), SLOT(signalHandlerException(QScriptValue)));
|
|
}
|
|
|
|
ScriptedEffect::~ScriptedEffect()
|
|
{
|
|
}
|
|
|
|
bool ScriptedEffect::init(const QString &effectName, const QString &pathToScript)
|
|
{
|
|
QFile scriptFile(pathToScript);
|
|
if (!scriptFile.open(QIODevice::ReadOnly)) {
|
|
kDebug(1212) << "Could not open script file: " << pathToScript;
|
|
return false;
|
|
}
|
|
m_effectName = effectName;
|
|
m_scriptFile = pathToScript;
|
|
|
|
QScriptValue effectsObject = m_engine->newQObject(effects, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater);
|
|
m_engine->globalObject().setProperty("effects", effectsObject, QScriptValue::Undeletable);
|
|
m_engine->globalObject().setProperty("Effect", m_engine->newQMetaObject(&ScriptedEffect::staticMetaObject));
|
|
m_engine->globalObject().setProperty("KWin", m_engine->newQMetaObject(&WorkspaceWrapper::staticMetaObject));
|
|
m_engine->globalObject().setProperty("QEasingCurve", m_engine->newQMetaObject(&QEasingCurve::staticMetaObject));
|
|
m_engine->globalObject().setProperty("effect", m_engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater), QScriptValue::Undeletable);
|
|
m_engine->globalObject().setProperty("AnimationData", m_engine->scriptValueFromQMetaObject<AnimationData>());
|
|
MetaScripting::registration(m_engine);
|
|
qScriptRegisterMetaType<KEffectWindowRef>(m_engine, effectWindowToScriptValue, effectWindowFromScriptValue);
|
|
qScriptRegisterMetaType<KWin::FPx2>(m_engine, fpx2ToScriptValue, fpx2FromScriptValue);
|
|
qScriptRegisterSequenceMetaType<QList< KWin::EffectWindow* > >(m_engine);
|
|
// add our print
|
|
QScriptValue printFunc = m_engine->newFunction(kwinEffectScriptPrint);
|
|
printFunc.setData(m_engine->newQObject(this));
|
|
m_engine->globalObject().setProperty("print", printFunc);
|
|
// add our animationTime
|
|
QScriptValue animationTimeFunc = m_engine->newFunction(kwinEffectScriptAnimationTime);
|
|
animationTimeFunc.setData(m_engine->newQObject(this));
|
|
m_engine->globalObject().setProperty("animationTime", animationTimeFunc);
|
|
// add displayWidth and displayHeight
|
|
QScriptValue displayWidthFunc = m_engine->newFunction(kwinEffectDisplayWidth);
|
|
m_engine->globalObject().setProperty("displayWidth", displayWidthFunc);
|
|
QScriptValue displayHeightFunc = m_engine->newFunction(kwinEffectDisplayHeight);
|
|
m_engine->globalObject().setProperty("displayHeight", displayHeightFunc);
|
|
// add global Shortcut
|
|
registerGlobalShortcutFunction(this, m_engine, kwinScriptGlobalShortcut);
|
|
registerScreenEdgeFunction(this, m_engine, kwinScriptScreenEdge);
|
|
// add the animate method
|
|
QScriptValue animateFunc = m_engine->newFunction(kwinEffectAnimate);
|
|
animateFunc.setData(m_engine->newQObject(this));
|
|
m_engine->globalObject().setProperty("animate", animateFunc);
|
|
|
|
QScriptValue ret = m_engine->evaluate(scriptFile.readAll());
|
|
|
|
if (ret.isError()) {
|
|
signalHandlerException(ret);
|
|
return false;
|
|
}
|
|
scriptFile.close();
|
|
return true;
|
|
}
|
|
|
|
void ScriptedEffect::signalHandlerException(const QScriptValue &value)
|
|
{
|
|
if (value.isError()) {
|
|
kDebug(1212) << "KWin Effect script encountered an error at [Line " << m_engine->uncaughtExceptionLineNumber() << "]";
|
|
kDebug(1212) << "Message: " << value.toString();
|
|
|
|
QScriptValueIterator iter(value);
|
|
while (iter.hasNext()) {
|
|
iter.next();
|
|
kDebug(1212) << " " << iter.name() << ": " << iter.value().toString();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptedEffect::animate(KWin::EffectWindow* w, KWin::AnimationEffect::Attribute a, int ms, KWin::FPx2 to, KWin::FPx2 from, KWin::AnimationData* data, QEasingCurve::Type curve, int delay)
|
|
{
|
|
uint meta = 0;
|
|
if (data) {
|
|
if (data->axis() != 0) {
|
|
AnimationEffect::setMetaData(AnimationEffect::Axis, data->axis() -1, meta);
|
|
}
|
|
if (data->sourceAnchor() != 0) {
|
|
AnimationEffect::setMetaData(AnimationEffect::SourceAnchor, data->sourceAnchor(), meta);
|
|
}
|
|
if (data->targetAnchor() != 0) {
|
|
AnimationEffect::setMetaData(AnimationEffect::TargetAnchor, data->targetAnchor(), meta);
|
|
}
|
|
if (data->relativeSourceX() != 0) {
|
|
AnimationEffect::setMetaData(AnimationEffect::RelativeSourceX, data->relativeSourceX(), meta);
|
|
}
|
|
if (data->relativeSourceY() != 0) {
|
|
AnimationEffect::setMetaData(AnimationEffect::RelativeSourceY, data->relativeSourceY(), meta);
|
|
}
|
|
if (data->relativeTargetX() != 0) {
|
|
AnimationEffect::setMetaData(AnimationEffect::RelativeTargetX, data->relativeTargetX(), meta);
|
|
}
|
|
if (data->relativeTargetY() != 0) {
|
|
AnimationEffect::setMetaData(AnimationEffect::RelativeTargetY, data->relativeTargetY(), meta);
|
|
}
|
|
}
|
|
AnimationEffect::animate(w, a, meta, ms, to, QEasingCurve(curve), delay, from);
|
|
}
|
|
|
|
bool ScriptedEffect::isGrabbed(EffectWindow* w, ScriptedEffect::DataRole grabRole)
|
|
{
|
|
void *e = w->data(static_cast<KWin::DataRole>(grabRole)).value<void*>();
|
|
if (e) {
|
|
return e != this;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ScriptedEffect::reconfigure(ReconfigureFlags flags)
|
|
{
|
|
AnimationEffect::reconfigure(flags);
|
|
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<KWin::ScriptedEffect*>(this, sender());
|
|
}
|
|
|
|
bool ScriptedEffect::borderActivated(ElectricBorder edge)
|
|
{
|
|
screenEdgeActivated(this, edge);
|
|
return true;
|
|
}
|
|
|
|
QVariant ScriptedEffect::readConfig(const QString &key, const QVariant defaultValue)
|
|
{
|
|
KConfigGroup cg = effects->effectConfig(m_effectName);
|
|
return cg.readEntry(key, defaultValue);
|
|
}
|
|
|
|
AnimationData::AnimationData (QObject* parent)
|
|
: QObject (parent)
|
|
, m_sourceAnchor((AnimationEffect::Anchor)0)
|
|
, m_targetAnchor((AnimationEffect::Anchor)0)
|
|
, m_relativeSourceX(0)
|
|
, m_relativeSourceY(0)
|
|
, m_relativeTargetX(0)
|
|
, m_relativeTargetY(0)
|
|
, m_axis((AnimationData::Axis)0)
|
|
{
|
|
}
|
|
|
|
AnimationData::Axis AnimationData::axis() const
|
|
{
|
|
return m_axis;
|
|
}
|
|
|
|
int AnimationData::relativeSourceX() const
|
|
{
|
|
return m_relativeSourceX;
|
|
}
|
|
|
|
int AnimationData::relativeSourceY() const
|
|
{
|
|
return m_relativeSourceY;
|
|
}
|
|
|
|
int AnimationData::relativeTargetX() const
|
|
{
|
|
return m_relativeTargetX;
|
|
}
|
|
|
|
int AnimationData::relativeTargetY() const
|
|
{
|
|
return m_relativeTargetY;
|
|
}
|
|
|
|
void AnimationData::setRelativeSourceX(int relativeSourceX)
|
|
{
|
|
m_relativeSourceX = relativeSourceX;
|
|
}
|
|
|
|
void AnimationData::setRelativeSourceY(int relativeSourceY)
|
|
{
|
|
m_relativeSourceY = relativeSourceY;
|
|
}
|
|
|
|
void AnimationData::setRelativeTargetX(int relativeTargetX)
|
|
{
|
|
m_relativeTargetX = relativeTargetX;
|
|
}
|
|
|
|
void AnimationData::setRelativeTargetY(int relativeTargetY)
|
|
{
|
|
m_relativeTargetY = relativeTargetY;
|
|
}
|
|
|
|
void AnimationData::setAxis(AnimationData::Axis axis)
|
|
{
|
|
m_axis = axis;
|
|
}
|
|
|
|
void AnimationData::setSourceAnchor(AnimationEffect::Anchor sourceAnchor)
|
|
{
|
|
m_sourceAnchor = sourceAnchor;
|
|
}
|
|
|
|
void AnimationData::setTargetAnchor(AnimationEffect::Anchor targetAnchor)
|
|
{
|
|
m_targetAnchor = targetAnchor;
|
|
}
|
|
|
|
AnimationEffect::Anchor AnimationData::sourceAnchor() const
|
|
{
|
|
return m_sourceAnchor;
|
|
}
|
|
|
|
AnimationEffect::Anchor AnimationData::targetAnchor() const
|
|
{
|
|
return m_targetAnchor;
|
|
}
|
|
|
|
} // namespace
|