diff --git a/scripting/scripting.cpp b/scripting/scripting.cpp index 13a77bc34..16a696c6c 100644 --- a/scripting/scripting.cpp +++ b/scripting/scripting.cpp @@ -1,751 +1,746 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Rohan Prabhu Copyright (C) 2011 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 "scripting.h" // own #include "meta.h" #include "scriptingutils.h" #include "workspace_wrapper.h" #include "../client.h" #include "../thumbnailitem.h" #include "../options.h" #include "../workspace.h" // KDE #include #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include QScriptValue kwinScriptPrint(QScriptContext *context, QScriptEngine *engine) { KWin::AbstractScript *script = qobject_cast(context->callee().data().toQObject()); if (!script) { return engine->undefinedValue(); } QString result; for (int i = 0; i < context->argumentCount(); ++i) { if (i > 0) { result.append(" "); } result.append(context->argument(i).toString()); } script->printMessage(result); return engine->undefinedValue(); } QScriptValue kwinScriptReadConfig(QScriptContext *context, QScriptEngine *engine) { KWin::AbstractScript *script = qobject_cast(context->callee().data().toQObject()); if (!script) { return engine->undefinedValue(); } if (context->argumentCount() < 1 || context->argumentCount() > 2) { kDebug(1212) << "Incorrect number of arguments"; return engine->undefinedValue(); } const QString key = context->argument(0).toString(); QVariant defaultValue; if (context->argumentCount() == 2) { defaultValue = context->argument(1).toVariant(); } return engine->newVariant(script->config().readEntry(key, defaultValue)); } QScriptValue kwinScriptGlobalShortcut(QScriptContext *context, QScriptEngine *engine) { return KWin::globalShortcut(context, engine); } QScriptValue kwinAssertTrue(QScriptContext *context, QScriptEngine *engine) { return KWin::scriptingAssert(context, engine, 1, 2, true); } QScriptValue kwinAssertFalse(QScriptContext *context, QScriptEngine *engine) { return KWin::scriptingAssert(context, engine, 1, 2, false); } QScriptValue kwinAssertEquals(QScriptContext *context, QScriptEngine *engine) { return KWin::scriptingAssert(context, engine, 2, 3); } QScriptValue kwinAssertNull(QScriptContext *context, QScriptEngine *engine) { if (!KWin::validateParameters(context, 1, 2)) { return engine->undefinedValue(); } if (!context->argument(0).isNull()) { if (context->argumentCount() == 2) { context->throwError(QScriptContext::UnknownError, context->argument(1).toString()); } else { context->throwError(QScriptContext::UnknownError, i18nc("Assertion failed in KWin script with given value", "Assertion failed: %1 is not null", context->argument(0).toString())); } return engine->undefinedValue(); } return true; } QScriptValue kwinAssertNotNull(QScriptContext *context, QScriptEngine *engine) { if (!KWin::validateParameters(context, 1, 2)) { return engine->undefinedValue(); } if (context->argument(0).isNull()) { if (context->argumentCount() == 2) { context->throwError(QScriptContext::UnknownError, context->argument(1).toString()); } else { context->throwError(QScriptContext::UnknownError, i18nc("Assertion failed in KWin script", "Assertion failed: argument is null")); } return engine->undefinedValue(); } return true; } QScriptValue kwinRegisterScreenEdge(QScriptContext *context, QScriptEngine *engine) { return KWin::registerScreenEdge(context, engine); } QScriptValue kwinRegisterUserActionsMenu(QScriptContext *context, QScriptEngine *engine) { return KWin::registerUserActionsMenu(context, engine); } QScriptValue kwinCallDBus(QScriptContext *context, QScriptEngine *engine) { KWin::AbstractScript *script = qobject_cast(context->callee().data().toQObject()); if (!script) { context->throwError(QScriptContext::UnknownError, "Internal Error: script not registered"); return engine->undefinedValue(); } if (context->argumentCount() < 4) { context->throwError(QScriptContext::SyntaxError, i18nc("Error in KWin Script", "Invalid number of arguments. At least service, path, interface and method need to be provided")); return engine->undefinedValue(); } if (!KWin::validateArgumentType(context)) { context->throwError(QScriptContext::SyntaxError, i18nc("Error in KWin Script", "Invalid type. Service, path, interface and method need to be string values")); return engine->undefinedValue(); } const QString service = context->argument(0).toString(); const QString path = context->argument(1).toString(); const QString interface = context->argument(2).toString(); const QString method = context->argument(3).toString(); int argumentsCount = context->argumentCount(); if (context->argument(argumentsCount-1).isFunction()) { --argumentsCount; } QDBusMessage msg = QDBusMessage::createMethodCall(service, path, interface, method); QVariantList arguments; for (int i=4; iargument(i).isArray()) { QStringList stringArray = engine->fromScriptValue(context->argument(i)); arguments << qVariantFromValue(stringArray); } else { arguments << context->argument(i).toVariant(); } } if (!arguments.isEmpty()) { msg.setArguments(arguments); } if (argumentsCount == context->argumentCount()) { // no callback, just fire and forget QDBusConnection::sessionBus().asyncCall(msg); } else { // with a callback QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(msg), script); watcher->setProperty("callback", script->registerCallback(context->argument(context->argumentCount()-1))); QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), script, SLOT(slotPendingDBusCall(QDBusPendingCallWatcher*))); } return engine->undefinedValue(); } KWin::AbstractScript::AbstractScript(int id, QString scriptName, QString pluginName, QObject *parent) : QObject(parent) , m_scriptId(id) , m_pluginName(pluginName) , m_running(false) , m_workspace(new WorkspaceWrapper(this)) { m_scriptFile.setFileName(scriptName); if (m_pluginName.isNull()) { m_pluginName = scriptName; } #ifdef KWIN_BUILD_SCREENEDGES connect(KWin::Workspace::self()->screenEdge(), SIGNAL(activated(ElectricBorder)), SLOT(borderActivated(ElectricBorder))); #endif } KWin::AbstractScript::~AbstractScript() { #ifdef KWIN_BUILD_SCREENEDGES for (QHash >::const_iterator it = m_screenEdgeCallbacks.constBegin(); it != m_screenEdgeCallbacks.constEnd(); ++it) { KWin::Workspace::self()->screenEdge()->unreserve(static_cast(it.key())); } #endif } KConfigGroup KWin::AbstractScript::config() const { return KGlobal::config()->group("Script-" + m_pluginName); } void KWin::AbstractScript::stop() { deleteLater(); } void KWin::AbstractScript::printMessage(const QString &message) { kDebug(1212) << scriptFile().fileName() << ":" << message; emit print(message); } void KWin::AbstractScript::registerShortcut(QAction *a, QScriptValue callback) { m_shortcutCallbacks.insert(a, callback); connect(a, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered())); } void KWin::AbstractScript::globalShortcutTriggered() { callGlobalShortcutCallback(this, sender()); } void KWin::AbstractScript::borderActivated(KWin::ElectricBorder edge) { screenEdgeActivated(this, edge); } void KWin::AbstractScript::installScriptFunctions(QScriptEngine* engine) { // add our print QScriptValue printFunc = engine->newFunction(kwinScriptPrint); printFunc.setData(engine->newQObject(this)); engine->globalObject().setProperty("print", printFunc); // add read config QScriptValue configFunc = engine->newFunction(kwinScriptReadConfig); configFunc.setData(engine->newQObject(this)); engine->globalObject().setProperty("readConfig", configFunc); QScriptValue dbusCallFunc = engine->newFunction(kwinCallDBus); dbusCallFunc.setData(engine->newQObject(this)); engine->globalObject().setProperty("callDBus", dbusCallFunc); // add global Shortcut registerGlobalShortcutFunction(this, engine, kwinScriptGlobalShortcut); // add screen edge registerScreenEdgeFunction(this, engine, kwinRegisterScreenEdge); // add user actions menu register function regesterUserActionsMenuFunction(this, engine, kwinRegisterUserActionsMenu); // add assertions QScriptValue assertTrueFunc = engine->newFunction(kwinAssertTrue); engine->globalObject().setProperty("assertTrue", assertTrueFunc); engine->globalObject().setProperty("assert", assertTrueFunc); QScriptValue assertFalseFunc = engine->newFunction(kwinAssertFalse); engine->globalObject().setProperty("assertFalse", assertFalseFunc); QScriptValue assertEqualsFunc = engine->newFunction(kwinAssertEquals); engine->globalObject().setProperty("assertEquals", assertEqualsFunc); QScriptValue assertNullFunc = engine->newFunction(kwinAssertNull); engine->globalObject().setProperty("assertNull", assertNullFunc); engine->globalObject().setProperty("assertEquals", assertEqualsFunc); QScriptValue assertNotNullFunc = engine->newFunction(kwinAssertNotNull); engine->globalObject().setProperty("assertNotNull", assertNotNullFunc); // global properties engine->globalObject().setProperty("KWin", engine->newQMetaObject(&WorkspaceWrapper::staticMetaObject)); QScriptValue workspace = engine->newQObject(AbstractScript::workspace(), QScriptEngine::QtOwnership, QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeDeleteLater); engine->globalObject().setProperty("workspace", workspace, QScriptValue::Undeletable); // install meta functions KWin::MetaScripting::registration(engine); } int KWin::AbstractScript::registerCallback(QScriptValue value) { int id = m_callbacks.size(); m_callbacks.insert(id, value); return id; } void KWin::AbstractScript::slotPendingDBusCall(QDBusPendingCallWatcher* watcher) { if (watcher->isError()) { kDebug(1212) << "Received D-Bus message is error"; watcher->deleteLater(); return; } const int id = watcher->property("callback").toInt(); QDBusMessage reply = watcher->reply(); QScriptValue callback (m_callbacks.value(id)); QScriptValueList arguments; foreach (const QVariant &argument, reply.arguments()) { arguments << callback.engine()->newVariant(argument); } callback.call(QScriptValue(), arguments); m_callbacks.remove(id); watcher->deleteLater(); } void KWin::AbstractScript::registerUseractionsMenuCallback(QScriptValue callback) { m_userActionsMenuCallbacks.append(callback); } QList< QAction * > KWin::AbstractScript::actionsForUserActionMenu(KWin::Client *c, QMenu *parent) { QList returnActions; for (QList::const_iterator it = m_userActionsMenuCallbacks.constBegin(); it != m_userActionsMenuCallbacks.constEnd(); ++it) { QScriptValue callback(*it); QScriptValueList arguments; arguments << callback.engine()->newQObject(c); QScriptValue actions = callback.call(QScriptValue(), arguments); if (!actions.isValid() || actions.isUndefined() || actions.isNull()) { // script does not want to handle this Client continue; } if (actions.isObject()) { QAction *a = scriptValueToAction(actions, parent); if (a) { returnActions << a; } } } return returnActions; } QAction *KWin::AbstractScript::scriptValueToAction(QScriptValue &value, QMenu *parent) { QScriptValue titleValue = value.property("text"); QScriptValue checkableValue = value.property("checkable"); QScriptValue checkedValue = value.property("checked"); QScriptValue itemsValue = value.property("items"); QScriptValue triggeredValue = value.property("triggered"); if (!titleValue.isValid()) { // title not specified - does not make any sense to include return NULL; } const QString title = titleValue.toString(); const bool checkable = checkableValue.isValid() && checkableValue.toBool(); const bool checked = checkable && checkedValue.isValid() && checkedValue.toBool(); // either a menu or a menu item if (itemsValue.isValid()) { if (!itemsValue.isArray()) { // not an array, so cannot be a menu return NULL; } QScriptValue lengthValue = itemsValue.property("length"); if (!lengthValue.isValid() || !lengthValue.isNumber() || lengthValue.toInteger() == 0) { // length property missing return NULL; } return createMenu(title, itemsValue, parent); } else if (triggeredValue.isValid()) { // normal item return createAction(title, checkable, checked, triggeredValue, parent); } return NULL; } QAction *KWin::AbstractScript::createAction(const QString &title, bool checkable, bool checked, QScriptValue &callback, QMenu *parent) { QAction *action = new QAction(title, parent); action->setCheckable(checkable); action->setChecked(checked); // TODO: rename m_shortcutCallbacks m_shortcutCallbacks.insert(action, callback); connect(action, SIGNAL(triggered(bool)), SLOT(globalShortcutTriggered())); connect(action, SIGNAL(destroyed(QObject*)), SLOT(actionDestroyed(QObject*))); return action; } QAction *KWin::AbstractScript::createMenu(const QString &title, QScriptValue &items, QMenu *parent) { QMenu *menu = new QMenu(title, parent); const int length = static_cast(items.property("length").toInteger()); for (int i=0; iaddAction(a); } } } return menu->menuAction(); } void KWin::AbstractScript::actionDestroyed(QObject *object) { // TODO: Qt 5 - change to lambda function m_shortcutCallbacks.remove(static_cast(object)); } KWin::Script::Script(int id, QString scriptName, QString pluginName, QObject* parent) : AbstractScript(id, scriptName, pluginName, parent) , m_engine(new QScriptEngine(this)) , m_starting(false) , m_agent(new ScriptUnloaderAgent(this)) { QDBusConnection::sessionBus().registerObject('/' + QString::number(scriptId()), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables); } KWin::Script::~Script() { QDBusConnection::sessionBus().unregisterObject('/' + QString::number(scriptId())); } void KWin::Script::run() { if (running() || m_starting) { return; } m_starting = true; QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, SIGNAL(finished()), SLOT(slotScriptLoadedFromFile())); watcher->setFuture(QtConcurrent::run(this, &KWin::Script::loadScriptFromFile)); } QByteArray KWin::Script::loadScriptFromFile() { if (!scriptFile().open(QIODevice::ReadOnly)) { return QByteArray(); } QByteArray result(scriptFile().readAll()); scriptFile().close(); return result; } void KWin::Script::slotScriptLoadedFromFile() { QFutureWatcher *watcher = dynamic_cast< QFutureWatcher< QByteArray>* >(sender()); if (!watcher) { // not invoked from a QFutureWatcher return; } if (watcher->result().isNull()) { // do not load empty script deleteLater(); watcher->deleteLater(); return; } QScriptValue optionsValue = m_engine->newQObject(options, QScriptEngine::QtOwnership, QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeDeleteLater); m_engine->globalObject().setProperty("options", optionsValue, QScriptValue::Undeletable); m_engine->globalObject().setProperty("QTimer", constructTimerClass(m_engine)); QObject::connect(m_engine, SIGNAL(signalHandlerException(QScriptValue)), this, SLOT(sigException(QScriptValue))); KWin::MetaScripting::supplyConfig(m_engine); installScriptFunctions(m_engine); QScriptValue ret = m_engine->evaluate(watcher->result()); if (ret.isError()) { sigException(ret); deleteLater(); } watcher->deleteLater(); setRunning(true); m_starting = false; } void KWin::Script::sigException(const QScriptValue& exception) { QScriptValue ret = exception; if (ret.isError()) { kDebug(1212) << "defaultscript encountered an error at [Line " << m_engine->uncaughtExceptionLineNumber() << "]"; kDebug(1212) << "Message: " << ret.toString(); kDebug(1212) << "-----------------"; QScriptValueIterator iter(ret); while (iter.hasNext()) { iter.next(); qDebug() << " " << iter.name() << ": " << iter.value().toString(); } } emit printError(exception.toString()); stop(); } KWin::ScriptUnloaderAgent::ScriptUnloaderAgent(KWin::Script *script) : QScriptEngineAgent(script->engine()) , m_script(script) { script->engine()->setAgent(this); } void KWin::ScriptUnloaderAgent::scriptUnload(qint64 id) { Q_UNUSED(id) m_script->stop(); } KWin::DeclarativeScript::DeclarativeScript(int id, QString scriptName, QString pluginName, QObject* parent) : AbstractScript(id, scriptName, pluginName, parent) , m_view(new QDeclarativeView()) { } KWin::DeclarativeScript::~DeclarativeScript() { } void KWin::DeclarativeScript::run() { if (running()) { return; } m_view->setAttribute(Qt::WA_TranslucentBackground); m_view->setWindowFlags(Qt::X11BypassWindowManagerHint); m_view->setResizeMode(QDeclarativeView::SizeViewToRootObject); QPalette pal = m_view->palette(); pal.setColor(m_view->backgroundRole(), Qt::transparent); m_view->setPalette(pal); - - foreach (const QString &importPath, KGlobal::dirs()->findDirs("module", "imports")) { - m_view->engine()->addImportPath(importPath); - } - // add read config KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(m_view->engine()); kdeclarative.initialize(); kdeclarative.setupBindings(); installScriptFunctions(kdeclarative.scriptEngine()); qmlRegisterType("org.kde.kwin", 0, 1, "ThumbnailItem"); qmlRegisterType(); m_view->rootContext()->setContextProperty("options", options); m_view->setSource(QUrl::fromLocalFile(scriptFile().fileName())); setRunning(true); } KWin::Scripting::Scripting(QObject *parent) : QObject(parent) , m_scriptsLock(new QMutex(QMutex::Recursive)) { QDBusConnection::sessionBus().registerObject("/Scripting", this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables); QDBusConnection::sessionBus().registerService("org.kde.kwin.Scripting"); connect(Workspace::self(), SIGNAL(configChanged()), SLOT(start())); connect(Workspace::self(), SIGNAL(workspaceInitialized()), SLOT(start())); } void KWin::Scripting::start() { #if 0 // TODO make this threaded again once KConfigGroup is sufficiently thread safe, bug #305361 and friends // perform querying for the services in a thread QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, SIGNAL(finished()), this, SLOT(slotScriptsQueried())); watcher->setFuture(QtConcurrent::run(this, &KWin::Scripting::queryScriptsToLoad, pluginStates, offers)); #else LoadScriptList scriptsToLoad = queryScriptsToLoad(); for (LoadScriptList::const_iterator it = scriptsToLoad.constBegin(); it != scriptsToLoad.constEnd(); ++it) { if (it->first) { loadScript(it->second.first, it->second.second); } else { loadDeclarativeScript(it->second.first, it->second.second); } } runScripts(); #endif } LoadScriptList KWin::Scripting::queryScriptsToLoad() { KSharedConfig::Ptr _config = KGlobal::config(); static bool s_started = false; if (s_started) { _config->reparseConfiguration(); } else { s_started = true; } QMap pluginStates = KConfigGroup(_config, "Plugins").entryMap(); KService::List offers = KServiceTypeTrader::self()->query("KWin/Script"); LoadScriptList scriptsToLoad; foreach (const KService::Ptr & service, offers) { KPluginInfo plugininfo(service); const QString value = pluginStates.value(plugininfo.pluginName() + QString::fromLatin1("Enabled"), QString()); plugininfo.setPluginEnabled(value.isNull() ? plugininfo.isPluginEnabledByDefault() : QVariant(value).toBool()); const bool javaScript = service->property("X-Plasma-API").toString() == "javascript"; const bool declarativeScript = service->property("X-Plasma-API").toString() == "declarativescript"; if (!javaScript && !declarativeScript) { continue; } if (!plugininfo.isPluginEnabled()) { if (isScriptLoaded(plugininfo.pluginName())) { // unload the script unloadScript(plugininfo.pluginName()); } continue; } const QString pluginName = service->property("X-KDE-PluginInfo-Name").toString(); const QString scriptName = service->property("X-Plasma-MainScript").toString(); const QString file = KStandardDirs::locate("data", QLatin1String(KWIN_NAME) + "/scripts/" + pluginName + "/contents/" + scriptName); if (file.isNull()) { kDebug(1212) << "Could not find script file for " << pluginName; continue; } scriptsToLoad << qMakePair(javaScript, qMakePair(file, pluginName)); } return scriptsToLoad; } void KWin::Scripting::slotScriptsQueried() { QFutureWatcher *watcher = dynamic_cast< QFutureWatcher* >(sender()); if (!watcher) { // slot invoked not from a FutureWatcher return; } LoadScriptList scriptsToLoad = watcher->result(); for (LoadScriptList::const_iterator it = scriptsToLoad.constBegin(); it != scriptsToLoad.constEnd(); ++it) { if (it->first) { loadScript(it->second.first, it->second.second); } else { loadDeclarativeScript(it->second.first, it->second.second); } } runScripts(); watcher->deleteLater(); } bool KWin::Scripting::isScriptLoaded(const QString &pluginName) const { QMutexLocker locker(m_scriptsLock.data()); foreach (AbstractScript *script, scripts) { if (script->pluginName() == pluginName) { return true; } } return false; } bool KWin::Scripting::unloadScript(const QString &pluginName) { QMutexLocker locker(m_scriptsLock.data()); foreach (AbstractScript *script, scripts) { if (script->pluginName() == pluginName) { script->deleteLater(); return true; } } return false; } void KWin::Scripting::runScripts() { QMutexLocker locker(m_scriptsLock.data()); for (int i = 0; i < scripts.size(); i++) { scripts.at(i)->run(); } } void KWin::Scripting::scriptDestroyed(QObject *object) { QMutexLocker locker(m_scriptsLock.data()); scripts.removeAll(static_cast(object)); } int KWin::Scripting::loadScript(const QString &filePath, const QString& pluginName) { QMutexLocker locker(m_scriptsLock.data()); if (isScriptLoaded(pluginName)) { return -1; } const int id = scripts.size(); KWin::Script *script = new KWin::Script(id, filePath, pluginName, this); connect(script, SIGNAL(destroyed(QObject*)), SLOT(scriptDestroyed(QObject*))); scripts.append(script); return id; } int KWin::Scripting::loadDeclarativeScript(const QString& filePath, const QString& pluginName) { QMutexLocker locker(m_scriptsLock.data()); if (isScriptLoaded(pluginName)) { return -1; } const int id = scripts.size(); KWin::DeclarativeScript *script = new KWin::DeclarativeScript(id, filePath, pluginName, this); connect(script, SIGNAL(destroyed(QObject*)), SLOT(scriptDestroyed(QObject*))); scripts.append(script); return id; } KWin::Scripting::~Scripting() { QDBusConnection::sessionBus().unregisterObject("/Scripting"); QDBusConnection::sessionBus().unregisterService("org.kde.kwin.Scripting"); } QList< QAction * > KWin::Scripting::actionsForUserActionMenu(KWin::Client *c, QMenu *parent) { QList actions; foreach (AbstractScript *script, scripts) { actions << script->actionsForUserActionMenu(c, parent); } return actions; } #include "scripting.moc" diff --git a/tabbox/declarative.cpp b/tabbox/declarative.cpp index f759512c7..7f169dcb0 100644 --- a/tabbox/declarative.cpp +++ b/tabbox/declarative.cpp @@ -1,410 +1,407 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2011 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 . *********************************************************************/ // own #include "declarative.h" #include "tabboxhandler.h" #include "clientmodel.h" // Qt #include #include #include #include #include #include #include // include KDE #include #include #include #include #include #include #include #include #include // KWin #include "thumbnailitem.h" #include #include "../effects.h" #include "../client.h" #include "../workspace.h" namespace KWin { namespace TabBox { ImageProvider::ImageProvider(QAbstractItemModel *model) : QDeclarativeImageProvider(QDeclarativeImageProvider::Pixmap) , m_model(model) { } QPixmap ImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) { bool ok = false; QStringList parts = id.split('/'); const int row = parts.first().toInt(&ok); if (!ok) { return QDeclarativeImageProvider::requestPixmap(id, size, requestedSize); } const QModelIndex index = m_model->index(row, 0); if (!index.isValid()) { return QDeclarativeImageProvider::requestPixmap(id, size, requestedSize); } TabBoxClient* client = static_cast< TabBoxClient* >(index.model()->data(index, ClientModel::ClientRole).value()); if (!client) { return QDeclarativeImageProvider::requestPixmap(id, size, requestedSize); } QSize s(32, 32); if (requestedSize.isValid()) { s = requestedSize; } *size = s; QPixmap icon = client->icon(s); if (s.width() > icon.width() || s.height() > icon.height()) { // icon is smaller than what we requested - QML would scale it which looks bad QPixmap temp(s); temp.fill(Qt::transparent); QPainter p(&temp); p.drawPixmap(s.width()/2 - icon.width()/2, s.height()/2 - icon.height()/2, icon); icon = temp; } if (parts.size() > 2) { KIconEffect *effect = KIconLoader::global()->iconEffect(); KIconLoader::States state = KIconLoader::DefaultState; if (parts.at(2) == QLatin1String("selected")) { state = KIconLoader::ActiveState; } else if (parts.at(2) == QLatin1String("disabled")) { state = KIconLoader::DisabledState; } icon = effect->apply(icon, KIconLoader::Desktop, state); } return icon; } // WARNING: this code exists to cover a bug in Qt which prevents plasma from detecting the state change // of the compositor through KWindowSystem. // once plasma uses (again) a KSelectionWatcher or Qt is fixed in this regard, the code can go. static QString plasmaThemeVariant() { #ifndef TABBOX_KCM if (!Workspace::self()->compositing() || !effects) { return Plasma::Theme::defaultTheme()->currentThemeHasImage("opaque/dialogs/background") ? QLatin1String("opaque/") : QLatin1String(""); } if (static_cast(effects)->provides(Effect::Blur)) { return Plasma::Theme::defaultTheme()->currentThemeHasImage("translucent/dialogs/background") ? QLatin1String("translucent/") : QLatin1String(""); } #endif return QLatin1String(""); } DeclarativeView::DeclarativeView(QAbstractItemModel *model, TabBoxConfig::TabBoxMode mode, QWidget *parent) : QDeclarativeView(parent) , m_model(model) , m_mode(mode) , m_currentScreenGeometry() , m_frame(new Plasma::FrameSvg(this)) , m_currentLayout() , m_cachedWidth(0) , m_cachedHeight(0) { setAttribute(Qt::WA_TranslucentBackground); setWindowFlags(Qt::X11BypassWindowManagerHint); if (tabBox->embedded()) { setResizeMode(QDeclarativeView::SizeRootObjectToView); } else { setResizeMode(QDeclarativeView::SizeViewToRootObject); } QPalette pal = palette(); pal.setColor(backgroundRole(), Qt::transparent); setPalette(pal); - foreach (const QString &importPath, KGlobal::dirs()->findDirs("module", "imports")) { - engine()->addImportPath(importPath); - } engine()->addImageProvider(QLatin1String("client"), new ImageProvider(model)); KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.initialize(); kdeclarative.setupBindings(); qmlRegisterType("org.kde.kwin", 0, 1, "ThumbnailItem"); rootContext()->setContextProperty("viewId", static_cast(winId())); rootContext()->setContextProperty("plasmaThemeVariant", plasmaThemeVariant()); if (m_mode == TabBoxConfig::ClientTabBox) { rootContext()->setContextProperty("clientModel", model); } else if (m_mode == TabBoxConfig::DesktopTabBox) { rootContext()->setContextProperty("clientModel", model); } setSource(QUrl(KStandardDirs::locate("data", QLatin1String(KWIN_NAME) + QLatin1String("/tabbox/tabbox.qml")))); // FrameSvg m_frame->setImagePath("dialogs/background"); m_frame->setCacheAllRenderedFrames(true); m_frame->setEnabledBorders(Plasma::FrameSvg::AllBorders); connect(tabBox, SIGNAL(configChanged()), SLOT(updateQmlSource())); if (m_mode == TabBoxConfig::ClientTabBox) { connect(tabBox, SIGNAL(embeddedChanged(bool)), SLOT(slotEmbeddedChanged(bool))); } } void DeclarativeView::showEvent(QShowEvent *event) { #ifndef TABBOX_KCM if (tabBox->embedded()) { Client *c = Workspace::self()->findClient(WindowMatchPredicate(tabBox->embedded())); if (c) { connect(c, SIGNAL(geometryChanged()), this, SLOT(slotUpdateGeometry())); } } #endif updateQmlSource(); m_currentScreenGeometry = QApplication::desktop()->screenGeometry(tabBox->activeScreen()); rootObject()->setProperty("screenWidth", m_currentScreenGeometry.width()); rootObject()->setProperty("screenHeight", m_currentScreenGeometry.height()); rootObject()->setProperty("allDesktops", tabBox->config().tabBoxMode() == TabBoxConfig::ClientTabBox && tabBox->config().clientDesktopMode() == TabBoxConfig::AllDesktopsClients); if (ClientModel *clientModel = qobject_cast(m_model)) { rootObject()->setProperty("longestCaption", clientModel->longestCaption()); } if (QObject *item = rootObject()->findChild("listView")) { item->setProperty("currentIndex", tabBox->first().row()); connect(item, SIGNAL(currentIndexChanged(int)), SLOT(currentIndexChanged(int))); } rootContext()->setContextProperty("plasmaThemeVariant", plasmaThemeVariant()); slotUpdateGeometry(); QResizeEvent re(size(), size()); // to set mask and blurring. resizeEvent(&re); QGraphicsView::showEvent(event); } void DeclarativeView::resizeEvent(QResizeEvent *event) { if (tabBox->embedded()) { Plasma::WindowEffects::enableBlurBehind(winId(), false); } else { const QString maskImagePath = rootObject()->property("maskImagePath").toString(); if (maskImagePath.isEmpty()) { clearMask(); Plasma::WindowEffects::enableBlurBehind(winId(), false); } else { const double maskWidth = rootObject()->property("maskWidth").toDouble(); const double maskHeight = rootObject()->property("maskHeight").toDouble(); const int maskTopMargin = rootObject()->property("maskTopMargin").toInt(); const int maskLeftMargin = rootObject()->property("maskLeftMargin").toInt(); m_frame->setImagePath(maskImagePath); m_frame->resizeFrame(QSizeF(maskWidth, maskHeight)); QRegion mask = m_frame->mask().translated(maskLeftMargin, maskTopMargin); #ifndef TABBOX_KCM // notice: this covers an issue with plasma detecting the compositing state. see plasmaThemeVariant() if (Workspace::self()->compositing() && effects) { // blur background?! Plasma::WindowEffects::enableBlurBehind(winId(), static_cast(effects)->provides(Effect::Blur), mask); clearMask(); } else #endif { // do not trim to mask with compositing enabled, otherwise shadows are cropped setMask(mask); } } } QDeclarativeView::resizeEvent(event); } void DeclarativeView::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); #ifndef TABBOX_KCM if (tabBox->embedded()) { Client *c = Workspace::self()->findClient(WindowMatchPredicate(tabBox->embedded())); if (c) { disconnect(c, SIGNAL(geometryChanged()), this, SLOT(slotUpdateGeometry())); } } #endif } bool DeclarativeView::x11Event(XEvent *e) { if (tabBox->embedded() && (e->type == ButtonPress || e->type == ButtonRelease || e->type == MotionNotify)) { XEvent ev; memcpy(&ev, e, sizeof(ev)); if (e->type == ButtonPress || e->type == ButtonRelease) { ev.xbutton.x += m_relativePos.x(); ev.xbutton.y += m_relativePos.y(); ev.xbutton.window = tabBox->embedded(); } else if (e->type == MotionNotify) { ev.xmotion.x += m_relativePos.x(); ev.xmotion.y += m_relativePos.y(); ev.xmotion.window = tabBox->embedded(); } XSendEvent( QX11Info::display(), tabBox->embedded(), False, NoEventMask, &ev ); } return QDeclarativeView::x11Event(e); } void DeclarativeView::slotUpdateGeometry() { const WId embeddedId = tabBox->embedded(); if (embeddedId != 0) { const KWindowInfo info = KWindowSystem::windowInfo(embeddedId, NET::WMGeometry); const Qt::Alignment alignment = tabBox->embeddedAlignment(); const QPoint offset = tabBox->embeddedOffset(); int x = info.geometry().left(); int y = info.geometry().top(); int width = tabBox->embeddedSize().width(); int height = tabBox->embeddedSize().height(); if (alignment.testFlag(Qt::AlignLeft) || alignment.testFlag(Qt::AlignHCenter)) { x += offset.x(); } if (alignment.testFlag(Qt::AlignRight)) { x = x + info.geometry().width() - offset.x() - width; } if (alignment.testFlag(Qt::AlignHCenter)) { width = info.geometry().width() - 2 * offset.x(); } if (alignment.testFlag(Qt::AlignTop) || alignment.testFlag(Qt::AlignVCenter)) { y += offset.y(); } if (alignment.testFlag(Qt::AlignBottom)) { y = y + info.geometry().height() - offset.y() - height; } if (alignment.testFlag(Qt::AlignVCenter)) { height = info.geometry().height() - 2 * offset.y(); } setGeometry(QRect(x, y, width, height)); m_relativePos = QPoint(info.geometry().x(), info.geometry().x()); } else { const int width = rootObject()->property("width").toInt(); const int height = rootObject()->property("height").toInt(); setGeometry(m_currentScreenGeometry.x() + static_cast(m_currentScreenGeometry.width()) * 0.5 - static_cast(width) * 0.5, m_currentScreenGeometry.y() + static_cast(m_currentScreenGeometry.height()) * 0.5 - static_cast(height) * 0.5, width, height); m_relativePos = pos(); } } void DeclarativeView::setCurrentIndex(const QModelIndex &index, bool disableAnimation) { if (tabBox->config().tabBoxMode() != m_mode) { return; } if (QObject *item = rootObject()->findChild("listView")) { QVariant durationRestore; if (disableAnimation) { durationRestore = item->property("highlightMoveDuration"); item->setProperty("highlightMoveDuration", QVariant(1)); } item->setProperty("currentIndex", index.row()); if (disableAnimation) { item->setProperty("highlightMoveDuration", durationRestore); } } } void DeclarativeView::currentIndexChanged(int row) { tabBox->setCurrentIndex(m_model->index(row, 0)); KWindowSystem::forceActiveWindow(m_model->data(m_model->index(row, 0), ClientModel::WIdRole).toLongLong()); } void DeclarativeView::updateQmlSource(bool force) { if (tabBox->config().tabBoxMode() != m_mode) { return; } if (!force && tabBox->config().layoutName() == m_currentLayout) { return; } if (m_mode == TabBoxConfig::DesktopTabBox) { m_currentLayout = tabBox->config().layoutName(); const QString file = KStandardDirs::locate("data", QLatin1String(KWIN_NAME) + QLatin1String("/tabbox/desktop.qml")); rootObject()->setProperty("source", QUrl(file)); return; } QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(tabBox->config().layoutName()); KService::List offers = KServiceTypeTrader::self()->query("KWin/WindowSwitcher", constraint); if (offers.isEmpty()) { // load default constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg("informative"); offers = KServiceTypeTrader::self()->query("KWin/WindowSwitcher", constraint); if (offers.isEmpty()) { kDebug(1212) << "could not find default window switcher layout"; return; } } m_currentLayout = tabBox->config().layoutName(); KService::Ptr service = offers.first(); const QString pluginName = service->property("X-KDE-PluginInfo-Name").toString(); if (service->property("X-Plasma-API").toString() != "declarativeappletscript") { kDebug(1212) << "Window Switcher Layout is no declarativeappletscript"; return; } const QString scriptName = service->property("X-Plasma-MainScript").toString(); const QString file = KStandardDirs::locate("data", QLatin1String(KWIN_NAME) + "/tabbox/" + pluginName + "/contents/" + scriptName); if (file.isNull()) { kDebug(1212) << "Could not find QML file for window switcher"; return; } rootObject()->setProperty("source", QUrl(file)); } void DeclarativeView::slotEmbeddedChanged(bool enabled) { if (enabled) { // cache the width setResizeMode(QDeclarativeView::SizeRootObjectToView); m_cachedWidth = rootObject()->property("width").toInt(); m_cachedHeight = rootObject()->property("height").toInt(); } else { setResizeMode(QDeclarativeView::SizeViewToRootObject); if (m_cachedWidth != 0 && m_cachedHeight != 0) { rootObject()->setProperty("width", m_cachedWidth); rootObject()->setProperty("height", m_cachedHeight); } updateQmlSource(true); } } void DeclarativeView::slotWindowChanged(WId wId, unsigned int properties) { if (wId != tabBox->embedded()) { return; } if (properties & NET::WMGeometry) { slotUpdateGeometry(); } } bool DeclarativeView::sendKeyEvent(QKeyEvent *e) { return event(e); } } // namespace TabBox } // namespace KWin