diff --git a/scripting/scripting.cpp b/scripting/scripting.cpp index 8ecddb385..5ccde33f7 100644 --- a/scripting/scripting.cpp +++ b/scripting/scripting.cpp @@ -1,898 +1,899 @@ /******************************************************************** 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 "dbuscall.h" #include "meta.h" #include "scriptingutils.h" #include "workspace_wrapper.h" #include "screenedgeitem.h" #include "scripting_model.h" #include "scripting_logging.h" #include "../x11client.h" #include "../thumbnailitem.h" #include "../options.h" #include "../workspace.h" // KDE #include #include // Qt #include #include #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; QTextStream stream(&result); for (int i = 0; i < context->argumentCount(); ++i) { if (i > 0) { stream << " "; } QScriptValue argument = context->argument(i); if (KWin::X11Client *client = qscriptvalue_cast(argument)) { client->print(stream); } else { stream << argument.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) { qCDebug(KWIN_SCRIPTING) << "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 kwinUnregisterScreenEdge(QScriptContext *context, QScriptEngine *engine) { return KWin::unregisterScreenEdge(context, engine); } QScriptValue kwinRegisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) { return KWin::registerTouchScreenEdge(context, engine); } QScriptValue kwinUnregisterTouchScreenEdge(QScriptContext *context, QScriptEngine *engine) { return KWin::unregisterTouchScreenEdge(context, engine); } QScriptValue kwinRegisterUserActionsMenu(QScriptContext *context, QScriptEngine *engine) { 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, QStringLiteral("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 << QVariant::fromValue(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_fileName(scriptName) , m_pluginName(pluginName) , m_running(false) { if (m_pluginName.isNull()) { m_pluginName = scriptName; } } KWin::AbstractScript::~AbstractScript() { } KConfigGroup KWin::AbstractScript::config() const { return kwinApp()->config()->group(QLatin1String("Script-") + m_pluginName); } void KWin::AbstractScript::stop() { deleteLater(); } void KWin::AbstractScript::printMessage(const QString &message) { qCDebug(KWIN_SCRIPTING) << 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()); } bool KWin::AbstractScript::borderActivated(KWin::ElectricBorder edge) { screenEdgeActivated(this, edge); return true; } void KWin::Script::installScriptFunctions(QScriptEngine* engine) { // add our print QScriptValue printFunc = engine->newFunction(kwinScriptPrint); printFunc.setData(engine->newQObject(this)); engine->globalObject().setProperty(QStringLiteral("print"), printFunc); // add read config QScriptValue configFunc = engine->newFunction(kwinScriptReadConfig); configFunc.setData(engine->newQObject(this)); engine->globalObject().setProperty(QStringLiteral("readConfig"), configFunc); QScriptValue dbusCallFunc = engine->newFunction(kwinCallDBus); dbusCallFunc.setData(engine->newQObject(this)); engine->globalObject().setProperty(QStringLiteral("callDBus"), dbusCallFunc); // add global Shortcut registerGlobalShortcutFunction(this, engine, kwinScriptGlobalShortcut); // add screen edge registerScreenEdgeFunction(this, engine, kwinRegisterScreenEdge); unregisterScreenEdgeFunction(this, engine, kwinUnregisterScreenEdge); registerTouchScreenEdgeFunction(this, engine, kwinRegisterTouchScreenEdge); unregisterTouchScreenEdgeFunction(this, engine, kwinUnregisterTouchScreenEdge); // add user actions menu register function registerUserActionsMenuFunction(this, engine, kwinRegisterUserActionsMenu); // add assertions QScriptValue assertTrueFunc = engine->newFunction(kwinAssertTrue); engine->globalObject().setProperty(QStringLiteral("assertTrue"), assertTrueFunc); engine->globalObject().setProperty(QStringLiteral("assert"), assertTrueFunc); QScriptValue assertFalseFunc = engine->newFunction(kwinAssertFalse); engine->globalObject().setProperty(QStringLiteral("assertFalse"), assertFalseFunc); QScriptValue assertEqualsFunc = engine->newFunction(kwinAssertEquals); engine->globalObject().setProperty(QStringLiteral("assertEquals"), assertEqualsFunc); QScriptValue assertNullFunc = engine->newFunction(kwinAssertNull); engine->globalObject().setProperty(QStringLiteral("assertNull"), assertNullFunc); engine->globalObject().setProperty(QStringLiteral("assertEquals"), assertEqualsFunc); QScriptValue assertNotNullFunc = engine->newFunction(kwinAssertNotNull); engine->globalObject().setProperty(QStringLiteral("assertNotNull"), assertNotNullFunc); // global properties engine->globalObject().setProperty(QStringLiteral("KWin"), engine->newQMetaObject(&QtScriptWorkspaceWrapper::staticMetaObject)); QScriptValue workspace = engine->newQObject(Scripting::self()->workspaceWrapper(), QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater); engine->globalObject().setProperty(QStringLiteral("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()) { qCDebug(KWIN_SCRIPTING) << "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::AbstractClient *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(QStringLiteral("text")); QScriptValue checkableValue = value.property(QStringLiteral("checkable")); QScriptValue checkedValue = value.property(QStringLiteral("checked")); QScriptValue itemsValue = value.property(QStringLiteral("items")); QScriptValue triggeredValue = value.property(QStringLiteral("triggered")); if (!titleValue.isValid()) { // title not specified - does not make any sense to include return nullptr; } 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 nullptr; } QScriptValue lengthValue = itemsValue.property(QStringLiteral("length")); if (!lengthValue.isValid() || !lengthValue.isNumber() || lengthValue.toInteger() == 0) { // length property missing return nullptr; } return createMenu(title, itemsValue, parent); } else if (triggeredValue.isValid()) { // normal item return createAction(title, checkable, checked, triggeredValue, parent); } return nullptr; } 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(QStringLiteral("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(QLatin1Char('/') + QString::number(scriptId()), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables); } KWin::Script::~Script() { QDBusConnection::sessionBus().unregisterObject(QLatin1Char('/') + QString::number(scriptId())); } void KWin::Script::run() { if (running() || m_starting) { return; } if (calledFromDBus()) { m_invocationContext = message(); setDelayedReply(true); } m_starting = true; QFutureWatcher *watcher = new QFutureWatcher(this); connect(watcher, SIGNAL(finished()), SLOT(slotScriptLoadedFromFile())); watcher->setFuture(QtConcurrent::run(this, &KWin::Script::loadScriptFromFile, fileName())); } QByteArray KWin::Script::loadScriptFromFile(const QString &fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { return QByteArray(); } QByteArray result(file.readAll()); 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(); if (m_invocationContext.type() == QDBusMessage::MethodCallMessage) { auto reply = m_invocationContext.createErrorReply("org.kde.kwin.Scripting.FileError", QString("Could not open %1").arg(fileName())); QDBusConnection::sessionBus().send(reply); m_invocationContext = QDBusMessage(); } return; } QScriptValue optionsValue = m_engine->newQObject(options, QScriptEngine::QtOwnership, QScriptEngine::ExcludeSuperClassContents | QScriptEngine::ExcludeDeleteLater); m_engine->globalObject().setProperty(QStringLiteral("options"), optionsValue, QScriptValue::Undeletable); m_engine->globalObject().setProperty(QStringLiteral("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(QString::fromUtf8(watcher->result())); if (ret.isError()) { sigException(ret); deleteLater(); } if (m_invocationContext.type() == QDBusMessage::MethodCallMessage) { auto reply = m_invocationContext.createReply(); QDBusConnection::sessionBus().send(reply); m_invocationContext = QDBusMessage(); } watcher->deleteLater(); setRunning(true); m_starting = false; } void KWin::Script::sigException(const QScriptValue& exception) { QScriptValue ret = exception; if (ret.isError()) { qCDebug(KWIN_SCRIPTING) << "defaultscript encountered an error at [Line " << m_engine->uncaughtExceptionLineNumber() << "]"; qCDebug(KWIN_SCRIPTING) << "Message: " << ret.toString(); qCDebug(KWIN_SCRIPTING) << "-----------------"; QScriptValueIterator iter(ret); while (iter.hasNext()) { iter.next(); qCDebug(KWIN_SCRIPTING) << " " << iter.name() << ": " << iter.value().toString(); } } emit printError(exception.toString()); stop(); } bool KWin::Script::registerTouchScreenCallback(int edge, QScriptValue callback) { if (m_touchScreenEdgeCallbacks.constFind(edge) != m_touchScreenEdgeCallbacks.constEnd()) { return false; } QAction *action = new QAction(this); connect(action, &QAction::triggered, this, [callback] { QScriptValue invoke(callback); invoke.call(); } ); ScreenEdges::self()->reserveTouch(KWin::ElectricBorder(edge), action); m_touchScreenEdgeCallbacks.insert(edge, action); return true; } bool KWin::Script::unregisterTouchScreenCallback(int edge) { auto it = m_touchScreenEdgeCallbacks.find(edge); if (it == m_touchScreenEdgeCallbacks.end()) { return false; } delete it.value(); m_touchScreenEdgeCallbacks.erase(it); return true; } 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_context(new QQmlContext(Scripting::self()->declarativeScriptSharedContext(), this)) , m_component(new QQmlComponent(Scripting::self()->qmlEngine(), this)) { m_context->setContextProperty(QStringLiteral("KWin"), new JSEngineGlobalMethodsWrapper(this)); } KWin::DeclarativeScript::~DeclarativeScript() { } void KWin::DeclarativeScript::run() { if (running()) { return; } m_component->loadUrl(QUrl::fromLocalFile(fileName())); if (m_component->isLoading()) { connect(m_component, &QQmlComponent::statusChanged, this, &DeclarativeScript::createComponent); } else { createComponent(); } } void KWin::DeclarativeScript::createComponent() { if (m_component->isError()) { qCDebug(KWIN_SCRIPTING) << "Component failed to load: " << m_component->errors(); } else { if (QObject *object = m_component->create(m_context)) { object->setParent(this); } } setRunning(true); } KWin::JSEngineGlobalMethodsWrapper::JSEngineGlobalMethodsWrapper(KWin::DeclarativeScript *parent) : QObject(parent) , m_script(parent) { } KWin::JSEngineGlobalMethodsWrapper::~JSEngineGlobalMethodsWrapper() { } QVariant KWin::JSEngineGlobalMethodsWrapper::readConfig(const QString &key, QVariant defaultValue) { return m_script->config().readEntry(key, defaultValue); } void KWin::JSEngineGlobalMethodsWrapper::registerWindow(QQuickWindow *window) { connect(window, &QWindow::visibilityChanged, this, [window](QWindow::Visibility visibility) { if (visibility == QWindow::Hidden) { window->destroy(); } }, Qt::QueuedConnection); } bool KWin::JSEngineGlobalMethodsWrapper::registerShortcut(const QString &name, const QString &text, const QKeySequence& keys, QJSValue function) { if (!function.isCallable()) { qCDebug(KWIN_SCRIPTING) << "Fourth and final argument must be a javascript function"; return false; } QAction *a = new QAction(this); a->setObjectName(name); a->setText(text); const QKeySequence shortcut = QKeySequence(keys); KGlobalAccel::self()->setShortcut(a, QList{shortcut}); KWin::input()->registerShortcut(shortcut, a); connect(a, &QAction::triggered, this, [=]() mutable { QJSValueList arguments; arguments << Scripting::self()->qmlEngine()->toScriptValue(a); function.call(arguments); }); return true; } KWin::Scripting *KWin::Scripting::s_self = nullptr; KWin::Scripting *KWin::Scripting::create(QObject *parent) { Q_ASSERT(!s_self); s_self = new Scripting(parent); return s_self; } KWin::Scripting::Scripting(QObject *parent) : QObject(parent) , m_scriptsLock(new QMutex(QMutex::Recursive)) , m_qmlEngine(new QQmlEngine(this)) , m_declarativeScriptSharedContext(new QQmlContext(m_qmlEngine, this)) , m_workspaceWrapper(new QtScriptWorkspaceWrapper(this)) { init(); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Scripting"), this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportScriptableInvokables); connect(Workspace::self(), SIGNAL(configChanged()), SLOT(start())); connect(Workspace::self(), SIGNAL(workspaceInitialized()), SLOT(start())); } void KWin::Scripting::init() { qmlRegisterType("org.kde.kwin", 2, 0, "DesktopThumbnailItem"); qmlRegisterType("org.kde.kwin", 2, 0, "ThumbnailItem"); qmlRegisterType("org.kde.kwin", 2, 0, "DBusCall"); qmlRegisterType("org.kde.kwin", 2, 0, "ScreenEdgeItem"); qmlRegisterType(); qmlRegisterType("org.kde.kwin", 2, 0, "ClientModel"); qmlRegisterType("org.kde.kwin", 2, 0, "ClientModelByScreen"); qmlRegisterType("org.kde.kwin", 2, 0, "ClientModelByScreenAndDesktop"); + qmlRegisterType("org.kde.kwin", 2, 1, "ClientModelByScreenAndActivity"); qmlRegisterType("org.kde.kwin", 2, 0, "ClientFilterModel"); qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("workspace"), m_workspaceWrapper); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("options"), options); m_declarativeScriptSharedContext->setContextProperty(QStringLiteral("workspace"), new DeclarativeScriptWorkspaceWrapper(this)); // QQmlListProperty interfaces only work via properties, rebind them as functions here QQmlExpression expr(m_declarativeScriptSharedContext, nullptr, "workspace.clientList = function() { return workspace.clients }"); expr.evaluate(); } 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 = kwinApp()->config(); static bool s_started = false; if (s_started) { _config->reparseConfiguration(); } else { s_started = true; } QMap pluginStates = KConfigGroup(_config, "Plugins").entryMap(); const QString scriptFolder = QStringLiteral(KWIN_NAME "/scripts/"); const auto offers = KPackage::PackageLoader::self()->listPackages(QStringLiteral("KWin/Script"), scriptFolder); LoadScriptList scriptsToLoad; for (const KPluginMetaData &service: offers) { const QString value = pluginStates.value(service.pluginId() + QLatin1String("Enabled"), QString()); const bool enabled = value.isNull() ? service.isEnabledByDefault() : QVariant(value).toBool(); const bool javaScript = service.value(QStringLiteral("X-Plasma-API")) == QLatin1String("javascript"); const bool declarativeScript = service.value(QStringLiteral("X-Plasma-API")) == QLatin1String("declarativescript"); if (!javaScript && !declarativeScript) { continue; } if (!enabled) { if (isScriptLoaded(service.pluginId())) { // unload the script unloadScript(service.pluginId()); } continue; } const QString pluginName = service.pluginId(); const QString scriptName = service.value(QStringLiteral("X-Plasma-MainScript")); const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, scriptFolder + pluginName + QLatin1String("/contents/") + scriptName); if (file.isNull()) { qCDebug(KWIN_SCRIPTING) << "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 { return findScript(pluginName) != nullptr; } KWin::AbstractScript *KWin::Scripting::findScript(const QString &pluginName) const { QMutexLocker locker(m_scriptsLock.data()); foreach (AbstractScript *script, scripts) { if (script->pluginName() == pluginName) { return script; } } return nullptr; } 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(QStringLiteral("/Scripting")); s_self = nullptr; } QList< QAction * > KWin::Scripting::actionsForUserActionMenu(KWin::AbstractClient *c, QMenu *parent) { QList actions; foreach (AbstractScript *script, scripts) { actions << script->actionsForUserActionMenu(c, parent); } return actions; } diff --git a/scripting/scripting_model.cpp b/scripting/scripting_model.cpp index 235dc88f4..199c2ebd0 100644 --- a/scripting/scripting_model.cpp +++ b/scripting/scripting_model.cpp @@ -1,927 +1,928 @@ /******************************************************************** 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 . *********************************************************************/ #include "scripting_model.h" #include #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "x11client.h" #include "screens.h" #include "workspace.h" #include "wayland_server.h" namespace KWin { namespace ScriptingClientModel { static quint32 nextId() { static quint32 counter = 0; return ++counter; } ClientLevel::ClientLevel(ClientModel *model, AbstractLevel *parent) : AbstractLevel(model, parent) { #if KWIN_BUILD_ACTIVITIES if (Activities *activities = Activities::self()) { connect(activities, &Activities::currentChanged, this, &ClientLevel::reInit); } #endif connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &ClientLevel::reInit); connect(Workspace::self(), &Workspace::clientAdded, this, &ClientLevel::clientAdded); connect(Workspace::self(), &Workspace::clientRemoved, this, &ClientLevel::clientRemoved); connect(model, SIGNAL(exclusionsChanged()), SLOT(reInit())); if (waylandServer()) { connect(waylandServer(), &WaylandServer::shellClientAdded, this, &ClientLevel::clientAdded); } } ClientLevel::~ClientLevel() { } void ClientLevel::clientAdded(AbstractClient *client) { setupClientConnections(client); checkClient(client); } void ClientLevel::clientRemoved(AbstractClient *client) { removeClient(client); } void ClientLevel::setupClientConnections(AbstractClient *client) { auto check = [this, client] { checkClient(client); }; connect(client, &AbstractClient::desktopChanged, this, check); connect(client, &AbstractClient::screenChanged, this, check); connect(client, &AbstractClient::activitiesChanged, this, check); connect(client, &AbstractClient::windowHidden, this, check); connect(client, &AbstractClient::windowShown, this, check); } void ClientLevel::checkClient(AbstractClient *client) { const bool shouldInclude = !exclude(client) && shouldAdd(client); const bool contains = containsClient(client); if (shouldInclude && !contains) { addClient(client); } else if (!shouldInclude && contains) { removeClient(client); } } bool ClientLevel::exclude(AbstractClient *client) const { ClientModel::Exclusions exclusions = model()->exclusions(); if (exclusions == ClientModel::NoExclusion) { return false; } if (exclusions & ClientModel::DesktopWindowsExclusion) { if (client->isDesktop()) { return true; } } if (exclusions & ClientModel::DockWindowsExclusion) { if (client->isDock()) { return true; } } if (exclusions & ClientModel::UtilityWindowsExclusion) { if (client->isUtility()) { return true; } } if (exclusions & ClientModel::SpecialWindowsExclusion) { if (client->isSpecialWindow()) { return true; } } if (exclusions & ClientModel::SkipTaskbarExclusion) { if (client->skipTaskbar()) { return true; } } if (exclusions & ClientModel::SkipPagerExclusion) { if (client->skipPager()) { return true; } } if (exclusions & ClientModel::SwitchSwitcherExclusion) { if (client->skipSwitcher()) { return true; } } if (exclusions & ClientModel::OtherDesktopsExclusion) { if (!client->isOnCurrentDesktop()) { return true; } } if (exclusions & ClientModel::OtherActivitiesExclusion) { if (!client->isOnCurrentActivity()) { return true; } } if (exclusions & ClientModel::MinimizedExclusion) { if (client->isMinimized()) { return true; } } if (exclusions & ClientModel::NotAcceptingFocusExclusion) { if (!client->wantsInput()) { return true; } } return false; } bool ClientLevel::shouldAdd(AbstractClient *client) const { if (restrictions() == ClientModel::NoRestriction) { return true; } if (restrictions() & ClientModel::ActivityRestriction) { if (!client->isOnActivity(activity())) { return false; } } if (restrictions() & ClientModel::VirtualDesktopRestriction) { if (!client->isOnDesktop(virtualDesktop())) { return false; } } if (restrictions() & ClientModel::ScreenRestriction) { if (client->screen() != int(screen())) { return false; } } return true; } void ClientLevel::addClient(AbstractClient *client) { if (containsClient(client)) { return; } emit beginInsert(m_clients.count(), m_clients.count(), id()); m_clients.insert(nextId(), client); emit endInsert(); } void ClientLevel::removeClient(AbstractClient *client) { int index = 0; auto it = m_clients.begin(); for (; it != m_clients.end(); ++it, ++index) { if (it.value() == client) { break; } } if (it == m_clients.end()) { return; } emit beginRemove(index, index, id()); m_clients.erase(it); emit endRemove(); } void ClientLevel::init() { const QList &clients = Workspace::self()->clientList(); for (auto it = clients.begin(); it != clients.end(); ++it) { X11Client *client = *it; setupClientConnections(client); if (!exclude(client) && shouldAdd(client)) { m_clients.insert(nextId(), client); } } } void ClientLevel::reInit() { const QList &clients = Workspace::self()->clientList(); for (auto it = clients.begin(); it != clients.end(); ++it) { checkClient((*it)); } if (waylandServer()) { const auto &clients = waylandServer()->clients(); for (auto *c : clients) { checkClient(c); } } } quint32 ClientLevel::idForRow(int row) const { if (row >= m_clients.size()) { return 0; } auto it = m_clients.constBegin(); for (int i=0; i(this); } return nullptr; } AbstractLevel *AbstractLevel::create(const QList< ClientModel::LevelRestriction > &restrictions, ClientModel::LevelRestrictions parentRestrictions, ClientModel *model, AbstractLevel *parent) { if (restrictions.isEmpty() || restrictions.first() == ClientModel::NoRestriction) { ClientLevel *leaf = new ClientLevel(model, parent); leaf->setRestrictions(parentRestrictions); if (!parent) { leaf->setParent(model); } return leaf; } // create a level QList childRestrictions(restrictions); ClientModel::LevelRestriction restriction = childRestrictions.takeFirst(); ClientModel::LevelRestrictions childrenRestrictions = restriction | parentRestrictions; ForkLevel *currentLevel = new ForkLevel(childRestrictions, model, parent); currentLevel->setRestrictions(childrenRestrictions); currentLevel->setRestriction(restriction); if (!parent) { currentLevel->setParent(model); } switch (restriction) { case ClientModel::ActivityRestriction: { #ifdef KWIN_BUILD_ACTIVITIES if (Activities::self()) { const QStringList &activities = Activities::self()->all(); for (QStringList::const_iterator it = activities.begin(); it != activities.end(); ++it) { AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel); if (!childLevel) { continue; } childLevel->setActivity(*it); currentLevel->addChild(childLevel); } } break; #else return nullptr; #endif } case ClientModel::ScreenRestriction: for (int i=0; icount(); ++i) { AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel); if (!childLevel) { continue; } childLevel->setScreen(i); currentLevel->addChild(childLevel); } break; case ClientModel::VirtualDesktopRestriction: for (uint i=1; i<=VirtualDesktopManager::self()->count(); ++i) { AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel); if (!childLevel) { continue; } childLevel->setVirtualDesktop(i); currentLevel->addChild(childLevel); } break; default: // invalid return nullptr; } return currentLevel; } AbstractLevel::AbstractLevel(ClientModel *model, AbstractLevel *parent) : QObject(parent) , m_model(model) , m_parent(parent) , m_screen(0) , m_virtualDesktop(0) , m_activity() , m_restriction(ClientModel::ClientModel::NoRestriction) , m_restrictions(ClientModel::NoRestriction) , m_id(nextId()) { } AbstractLevel::~AbstractLevel() { } void AbstractLevel::setRestriction(ClientModel::LevelRestriction restriction) { m_restriction = restriction; } void AbstractLevel::setActivity(const QString &activity) { m_activity = activity; } void AbstractLevel::setScreen(uint screen) { m_screen = screen; } void AbstractLevel::setVirtualDesktop(uint virtualDesktop) { m_virtualDesktop = virtualDesktop; } void AbstractLevel::setRestrictions(ClientModel::LevelRestrictions restrictions) { m_restrictions = restrictions; } ForkLevel::ForkLevel(const QList &childRestrictions, ClientModel *model, AbstractLevel *parent) : AbstractLevel(model, parent) , m_childRestrictions(childRestrictions) { connect(VirtualDesktopManager::self(), SIGNAL(countChanged(uint,uint)), SLOT(desktopCountChanged(uint,uint))); connect(screens(), SIGNAL(countChanged(int,int)), SLOT(screenCountChanged(int,int))); #ifdef KWIN_BUILD_ACTIVITIES if (Activities *activities = Activities::self()) { connect(activities, SIGNAL(added(QString)), SLOT(activityAdded(QString))); connect(activities, SIGNAL(removed(QString)), SLOT(activityRemoved(QString))); } #endif } ForkLevel::~ForkLevel() { } void ForkLevel::desktopCountChanged(uint previousCount, uint newCount) { if (restriction() != ClientModel::ClientModel::VirtualDesktopRestriction) { return; } if (previousCount != uint(count())) { return; } if (previousCount > newCount) { // desktops got removed emit beginRemove(newCount, previousCount-1, id()); while (uint(m_children.count()) > newCount) { delete m_children.takeLast(); } emit endRemove(); } else { // desktops got added emit beginInsert(previousCount, newCount-1, id()); for (uint i=previousCount+1; i<=newCount; ++i) { AbstractLevel *childLevel = AbstractLevel::create(m_childRestrictions, restrictions(), model(), this); if (!childLevel) { continue; } childLevel->setVirtualDesktop(i); childLevel->init(); addChild(childLevel); } emit endInsert(); } } void ForkLevel::screenCountChanged(int previousCount, int newCount) { if (restriction() != ClientModel::ClientModel::ClientModel::ScreenRestriction) { return; } if (newCount == previousCount || previousCount != count()) { return; } if (previousCount > newCount) { // screens got removed emit beginRemove(newCount, previousCount-1, id()); while (m_children.count() > newCount) { delete m_children.takeLast(); } emit endRemove(); } else { // screens got added emit beginInsert(previousCount, newCount-1, id()); for (int i=previousCount; isetScreen(i); childLevel->init(); addChild(childLevel); } emit endInsert(); } } void ForkLevel::activityAdded(const QString &activityId) { #ifdef KWIN_BUILD_ACTIVITIES if (restriction() != ClientModel::ClientModel::ActivityRestriction) { return; } // verify that our children do not contain this activity foreach (AbstractLevel *child, m_children) { if (child->activity() == activityId) { return; } } emit beginInsert(m_children.count(), m_children.count(), id()); AbstractLevel *childLevel = AbstractLevel::create(m_childRestrictions, restrictions(), model(), this); if (!childLevel) { emit endInsert(); return; } childLevel->setActivity(activityId); childLevel->init(); addChild(childLevel); emit endInsert(); #else Q_UNUSED(activityId) #endif } void ForkLevel::activityRemoved(const QString &activityId) { #ifdef KWIN_BUILD_ACTIVITIES if (restriction() != ClientModel::ClientModel::ActivityRestriction) { return; } for (int i=0; iactivity() == activityId) { emit beginRemove(i, i, id()); delete m_children.takeAt(i); emit endRemove(); break; } } #else Q_UNUSED(activityId) #endif } int ForkLevel::count() const { return m_children.count(); } void ForkLevel::addChild(AbstractLevel *child) { m_children.append(child); connect(child, SIGNAL(beginInsert(int,int,quint32)), SIGNAL(beginInsert(int,int,quint32))); connect(child, SIGNAL(beginRemove(int,int,quint32)), SIGNAL(beginRemove(int,int,quint32))); connect(child, SIGNAL(endInsert()), SIGNAL(endInsert())); connect(child, SIGNAL(endRemove()), SIGNAL(endRemove())); } void ForkLevel::setActivity(const QString &activity) { AbstractLevel::setActivity(activity); for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->setActivity(activity); } } void ForkLevel::setScreen(uint screen) { AbstractLevel::setScreen(screen); for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->setScreen(screen); } } void ForkLevel::setVirtualDesktop(uint virtualDesktop) { AbstractLevel::setVirtualDesktop(virtualDesktop); for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->setVirtualDesktop(virtualDesktop); } } void ForkLevel::init() { for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->init(); } } quint32 ForkLevel::idForRow(int row) const { if (row >= m_children.length()) { return 0; } return m_children.at(row)->id(); } const AbstractLevel *ForkLevel::levelForId(quint32 id) const { if (id == AbstractLevel::id()) { return this; } for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { if (const AbstractLevel *child = (*it)->levelForId(id)) { return child; } } // not found return nullptr; } AbstractLevel *ForkLevel::parentForId(quint32 child) const { if (child == id()) { return parentLevel(); } for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { if (AbstractLevel *parent = (*it)->parentForId(child)) { return parent; } } // not found return nullptr; } int ForkLevel::rowForId(quint32 child) const { if (id() == child) { return 0; } for (int i=0; iid() == child) { return i; } } // do recursion for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { int row = (*it)->rowForId(child); if (row != -1) { return row; } } // not found return -1; } AbstractClient *ForkLevel::clientForId(quint32 child) const { for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { if (AbstractClient *client = (*it)->clientForId(child)) { return client; } } // not found return nullptr; } ClientModel::ClientModel(QObject *parent) : QAbstractItemModel(parent) , m_root(nullptr) , m_exclusions(NoExclusion) { } ClientModel::~ClientModel() { } void ClientModel::setLevels(QList< ClientModel::LevelRestriction > restrictions) { beginResetModel(); if (m_root) { delete m_root; } m_root = AbstractLevel::create(restrictions, NoRestriction, this); connect(m_root, SIGNAL(beginInsert(int,int,quint32)), SLOT(levelBeginInsert(int,int,quint32))); connect(m_root, SIGNAL(beginRemove(int,int,quint32)), SLOT(levelBeginRemove(int,int,quint32))); connect(m_root, SIGNAL(endInsert()), SLOT(levelEndInsert())); connect(m_root, SIGNAL(endRemove()), SLOT(levelEndRemove())); m_root->init(); endResetModel(); } void ClientModel::setExclusions(ClientModel::Exclusions exclusions) { if (exclusions == m_exclusions) { return; } m_exclusions = exclusions; emit exclusionsChanged(); } QVariant ClientModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.column() != 0) { return QVariant(); } if (const AbstractLevel *level = getLevel(index)) { LevelRestriction restriction = level->restriction(); if (restriction == ActivityRestriction && (role == Qt::DisplayRole || role == ActivityRole)) { return level->activity(); } else if (restriction == VirtualDesktopRestriction && (role == Qt::DisplayRole || role == DesktopRole)) { return level->virtualDesktop(); } else if (restriction ==ScreenRestriction && (role == Qt::DisplayRole || role == ScreenRole)) { return level->screen(); } else { return QVariant(); } } if (role == Qt::DisplayRole || role == ClientRole) { if (AbstractClient *client = m_root->clientForId(index.internalId())) { return QVariant::fromValue(client); } } return QVariant(); } int ClientModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } int ClientModel::rowCount(const QModelIndex &parent) const { if (!m_root) { return 0; } if (!parent.isValid()) { return m_root->count(); } if (const AbstractLevel *level = getLevel(parent)) { if (level->id() != parent.internalId()) { // not a real level - no children return 0; } return level->count(); } return 0; } QHash ClientModel::roleNames() const { return { { Qt::DisplayRole, QByteArrayLiteral("display") }, { ClientRole, QByteArrayLiteral("client") }, { ScreenRole, QByteArrayLiteral("screen") }, { DesktopRole, QByteArrayLiteral("desktop") }, { ActivityRole, QByteArrayLiteral("activity") }, }; } QModelIndex ClientModel::parent(const QModelIndex &child) const { if (!child.isValid() || child.column() != 0) { return QModelIndex(); } return parentForId(child.internalId()); } QModelIndex ClientModel::parentForId(quint32 childId) const { if (childId == m_root->id()) { // asking for parent of our toplevel return QModelIndex(); } if (AbstractLevel *parentLevel = m_root->parentForId(childId)) { if (parentLevel == m_root) { return QModelIndex(); } const int row = m_root->rowForId(parentLevel->id()); if (row == -1) { // error return QModelIndex(); } return createIndex(row, 0, parentLevel->id()); } return QModelIndex(); } QModelIndex ClientModel::index(int row, int column, const QModelIndex &parent) const { if (column != 0 || row < 0 || !m_root) { return QModelIndex(); } if (!parent.isValid()) { if (row >= rowCount()) { return QModelIndex(); } return createIndex(row, 0, m_root->idForRow(row)); } const AbstractLevel *parentLevel = getLevel(parent); if (!parentLevel) { return QModelIndex(); } if (row >= parentLevel->count()) { return QModelIndex(); } const quint32 id = parentLevel->idForRow(row); if (id == 0) { return QModelIndex(); } return createIndex(row, column, id); } const AbstractLevel *ClientModel::getLevel(const QModelIndex &index) const { if (!index.isValid()) { return m_root; } return m_root->levelForId(index.internalId()); } void ClientModel::levelBeginInsert(int rowStart, int rowEnd, quint32 id) { const int row = m_root->rowForId(id); QModelIndex parent; if (row != -1) { parent = createIndex(row, 0, id); } beginInsertRows(parent, rowStart, rowEnd); } void ClientModel::levelBeginRemove(int rowStart, int rowEnd, quint32 id) { const int row = m_root->rowForId(id); QModelIndex parent; if (row != -1) { parent = createIndex(row, 0, id); } beginRemoveRows(parent, rowStart, rowEnd); } void ClientModel::levelEndInsert() { endInsertRows(); } void ClientModel::levelEndRemove() { endRemoveRows(); } #define CLIENT_MODEL_WRAPPER(name, levels) \ name::name(QObject *parent) \ : ClientModel(parent) \ { \ setLevels(levels); \ } \ name::~name() {} CLIENT_MODEL_WRAPPER(SimpleClientModel, QList()) CLIENT_MODEL_WRAPPER(ClientModelByScreen, QList() << ScreenRestriction) CLIENT_MODEL_WRAPPER(ClientModelByScreenAndDesktop, QList() << ScreenRestriction << VirtualDesktopRestriction) +CLIENT_MODEL_WRAPPER(ClientModelByScreenAndActivity, QList() << ScreenRestriction << ActivityRestriction) #undef CLIENT_MODEL_WRAPPER ClientFilterModel::ClientFilterModel(QObject *parent) : QSortFilterProxyModel(parent) , m_clientModel(nullptr) { } ClientFilterModel::~ClientFilterModel() { } void ClientFilterModel::setClientModel(ClientModel *clientModel) { if (clientModel == m_clientModel) { return; } m_clientModel = clientModel; setSourceModel(m_clientModel); emit clientModelChanged(); } void ClientFilterModel::setFilter(const QString &filter) { if (filter == m_filter) { return; } m_filter = filter; emit filterChanged(); invalidateFilter(); } bool ClientFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (!m_clientModel) { return false; } if (m_filter.isEmpty()) { return true; } QModelIndex index = m_clientModel->index(sourceRow, 0, sourceParent); if (!index.isValid()) { return false; } QVariant data = index.data(); if (!data.isValid()) { // an invalid QVariant is valid data return true; } // TODO: introduce a type as a data role and properly check, this seems dangerous if (data.type() == QVariant::Int || data.type() == QVariant::UInt || data.type() == QVariant::String) { // we do not filter out screen, desktop and activity return true; } X11Client *client = qvariant_cast(data); if (!client) { return false; } if (client->caption().contains(m_filter, Qt::CaseInsensitive)) { return true; } const QString windowRole(QString::fromUtf8(client->windowRole())); if (windowRole.contains(m_filter, Qt::CaseInsensitive)) { return true; } const QString resourceName(QString::fromUtf8(client->resourceName())); if (resourceName.contains(m_filter, Qt::CaseInsensitive)) { return true; } const QString resourceClass(QString::fromUtf8(client->resourceClass())); if (resourceClass.contains(m_filter, Qt::CaseInsensitive)) { return true; } return false; } } // namespace Scripting } // namespace KWin diff --git a/scripting/scripting_model.h b/scripting/scripting_model.h index b98d6fbd9..c6e07aef3 100644 --- a/scripting/scripting_model.h +++ b/scripting/scripting_model.h @@ -1,379 +1,387 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef KWIN_SCRIPTING_MODEL_H #define KWIN_SCRIPTING_MODEL_H #include #include #include namespace KWin { class AbstractClient; class Client; namespace ScriptingClientModel { class AbstractLevel; class ClientModel : public QAbstractItemModel { Q_OBJECT Q_ENUMS(Exclude) Q_ENUMS(LevelRestriction) Q_PROPERTY(Exclusions exclusions READ exclusions WRITE setExclusions NOTIFY exclusionsChanged) public: enum Exclusion { NoExclusion = 0, // window types DesktopWindowsExclusion = 1 << 0, DockWindowsExclusion = 1 << 1, UtilityWindowsExclusion = 1 << 2, SpecialWindowsExclusion = 1 << 3, // windows with flags SkipTaskbarExclusion = 1 << 4, SkipPagerExclusion = 1 << 5, SwitchSwitcherExclusion = 1 << 6, // based on state OtherDesktopsExclusion = 1 << 7, OtherActivitiesExclusion = 1 << 8, MinimizedExclusion = 1 << 9, NonSelectedWindowTabExclusion = 1 << 10, NotAcceptingFocusExclusion = 1 << 11 }; Q_DECLARE_FLAGS(Exclusions, Exclusion) Q_FLAGS(Exclusions) enum LevelRestriction { NoRestriction = 0, VirtualDesktopRestriction = 1 << 0, ScreenRestriction = 1 << 1, ActivityRestriction = 1 << 2 }; Q_DECLARE_FLAGS(LevelRestrictions, LevelRestriction) Q_FLAGS(LevelRestrictions) explicit ClientModel(QObject *parent); ~ClientModel() override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; void setExclusions(ClientModel::Exclusions exclusions); Exclusions exclusions() const; Q_SIGNALS: void exclusionsChanged(); private Q_SLOTS: void levelBeginInsert(int rowStart, int rowEnd, quint32 parentId); void levelEndInsert(); void levelBeginRemove(int rowStart, int rowEnd, quint32 parentId); void levelEndRemove(); protected: enum ClientModelRoles { ClientRole = Qt::UserRole, ScreenRole, DesktopRole, ActivityRole }; void setLevels(QList restrictions); private: QModelIndex parentForId(quint32 childId) const; const AbstractLevel *getLevel(const QModelIndex &index) const; AbstractLevel *m_root; Exclusions m_exclusions; }; /** * @brief The data structure of the Model. * * The model is implemented as a Tree consisting of AbstractLevels as the levels of the tree. * A non leaf level is represented by the inheriting class ForkLevel, the last level above a * leaf is represented by the inheriting class ClientLevel, which contains the Clients - each * Client is one leaf. * * In case the tree would only consist of Clients - leafs - it has always one ClientLevel as the root * of the tree. * * The number of levels in the tree is controlled by the LevelRestrictions. For each existing * LevelRestriction a new Level is created, if there are no more restrictions a ClientLevel is created. * * To build up the tree the static factory method @ref create has to be used. It will recursively * build up the tree. After the tree has been build up use @ref init to initialize the tree which * will add the Clients to the ClientLevel. * * Each element of the tree has a unique id which can be used by the QAbstractItemModel as the * internal id for its QModelIndex. Note: the ids have no ordering, if trying to get a specific element * the tree performs a depth-first search. */ class AbstractLevel : public QObject { Q_OBJECT public: ~AbstractLevel() override; virtual int count() const = 0; virtual void init() = 0; virtual quint32 idForRow(int row) const = 0; uint screen() const; uint virtualDesktop() const; const QString &activity() const; ClientModel::LevelRestrictions restrictions() const; void setRestrictions(ClientModel::LevelRestrictions restrictions); ClientModel::LevelRestriction restriction() const; void setRestriction(ClientModel::LevelRestriction restriction); quint32 id() const; AbstractLevel *parentLevel() const; virtual const AbstractLevel *levelForId(quint32 id) const = 0; virtual AbstractLevel *parentForId(quint32 child) const = 0; virtual int rowForId(quint32 child) const = 0; virtual AbstractClient *clientForId(quint32 child) const = 0; virtual void setScreen(uint screen); virtual void setVirtualDesktop(uint virtualDesktop); virtual void setActivity(const QString &activity); static AbstractLevel *create(const QList &restrictions, ClientModel::LevelRestrictions parentRestrictions, ClientModel *model, AbstractLevel *parent = nullptr); Q_SIGNALS: void beginInsert(int rowStart, int rowEnd, quint32 parentId); void endInsert(); void beginRemove(int rowStart, int rowEnd, quint32 parentId); void endRemove(); protected: AbstractLevel(ClientModel *model, AbstractLevel *parent); ClientModel *model() const; private: ClientModel *m_model; AbstractLevel *m_parent; uint m_screen; uint m_virtualDesktop; QString m_activity; ClientModel::LevelRestriction m_restriction; ClientModel::LevelRestrictions m_restrictions; quint32 m_id; }; class ForkLevel : public AbstractLevel { Q_OBJECT public: ForkLevel(const QList &childRestrictions, ClientModel *model, AbstractLevel *parent); ~ForkLevel() override; int count() const override; void init() override; quint32 idForRow(int row) const override; void addChild(AbstractLevel *child); void setScreen(uint screen) override; void setVirtualDesktop(uint virtualDesktop) override; void setActivity(const QString &activity) override; const AbstractLevel *levelForId(quint32 id) const override; AbstractLevel *parentForId(quint32 child) const override; int rowForId(quint32 child) const override; AbstractClient *clientForId(quint32 child) const override; private Q_SLOTS: void desktopCountChanged(uint previousCount, uint newCount); void screenCountChanged(int previousCount, int newCount); void activityAdded(const QString &id); void activityRemoved(const QString &id); private: QList m_children; QList m_childRestrictions; }; /** * @brief The actual leafs of the model's tree containing the Client's in this branch of the tree. * * This class groups all the Clients of one branch of the tree and takes care of updating the tree * when a Client changes its state in a way that it should be excluded/included or gets added or * removed. * * The Clients in this group are not sorted in any particular way. It's a simple list which only * gets added to. If some sorting should be applied, use a QSortFilterProxyModel. */ class ClientLevel : public AbstractLevel { Q_OBJECT public: explicit ClientLevel(ClientModel *model, AbstractLevel *parent); ~ClientLevel() override; void init() override; int count() const override; quint32 idForRow(int row) const override; bool containsId(quint32 id) const; int rowForId(quint32 row) const override; AbstractClient *clientForId(quint32 child) const override; const AbstractLevel *levelForId(quint32 id) const override; AbstractLevel *parentForId(quint32 child) const override; public Q_SLOTS: void clientAdded(KWin::AbstractClient *client); void clientRemoved(KWin::AbstractClient *client); private Q_SLOTS: // uses sender() void reInit(); private: void checkClient(KWin::AbstractClient *client); void setupClientConnections(AbstractClient *client); void addClient(AbstractClient *client); void removeClient(AbstractClient *client); bool shouldAdd(AbstractClient *client) const; bool exclude(AbstractClient *client) const; bool containsClient(AbstractClient *client) const; QMap m_clients; }; class SimpleClientModel : public ClientModel { Q_OBJECT public: SimpleClientModel(QObject *parent = nullptr); ~SimpleClientModel() override; }; class ClientModelByScreen : public ClientModel { Q_OBJECT public: ClientModelByScreen(QObject *parent = nullptr); ~ClientModelByScreen() override; }; class ClientModelByScreenAndDesktop : public ClientModel { Q_OBJECT public: ClientModelByScreenAndDesktop(QObject *parent = nullptr); ~ClientModelByScreenAndDesktop() override; }; +class ClientModelByScreenAndActivity : public ClientModel +{ + Q_OBJECT +public: + ClientModelByScreenAndActivity(QObject *parent = nullptr); + ~ClientModelByScreenAndActivity() override; +}; + /** * @brief Custom QSortFilterProxyModel to filter on Client caption, role and class. */ class ClientFilterModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(KWin::ScriptingClientModel::ClientModel *clientModel READ clientModel WRITE setClientModel NOTIFY clientModelChanged) Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged) public: ClientFilterModel(QObject *parent = nullptr); ~ClientFilterModel() override; ClientModel *clientModel() const; const QString &filter() const; public Q_SLOTS: void setClientModel(ClientModel *clientModel); void setFilter(const QString &filter); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; Q_SIGNALS: void clientModelChanged(); void filterChanged(); private: ClientModel *m_clientModel; QString m_filter; }; inline int ClientLevel::count() const { return m_clients.count(); } inline const QString &AbstractLevel::activity() const { return m_activity; } inline AbstractLevel *AbstractLevel::parentLevel() const { return m_parent; } inline ClientModel *AbstractLevel::model() const { return m_model; } inline uint AbstractLevel::screen() const { return m_screen; } inline uint AbstractLevel::virtualDesktop() const { return m_virtualDesktop; } inline ClientModel::LevelRestriction AbstractLevel::restriction() const { return m_restriction; } inline ClientModel::LevelRestrictions AbstractLevel::restrictions() const { return m_restrictions; } inline quint32 AbstractLevel::id() const { return m_id; } inline ClientModel::Exclusions ClientModel::exclusions() const { return m_exclusions; } inline ClientModel *ClientFilterModel::clientModel() const { return m_clientModel; } inline const QString &ClientFilterModel::filter() const { return m_filter; } } // namespace Scripting } // namespace KWin Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::ScriptingClientModel::ClientModel::Exclusions) Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::ScriptingClientModel::ClientModel::LevelRestrictions) #endif // KWIN_SCRIPTING_MODEL_H