diff --git a/shell/scripting/scriptengine.cpp b/shell/scripting/scriptengine.cpp index 7736cd35f..b066c6a70 100644 --- a/shell/scripting/scriptengine.cpp +++ b/shell/scripting/scriptengine.cpp @@ -1,959 +1,964 @@ /* * Copyright 2009 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scriptengine.h" #include #include #include #include #include #include #include #include #include #include #include #include // KIO //#include // no camelcase include #include #include #include #include #include #include #include "appinterface.h" #include "containment.h" #include "configgroup.h" #include "i18n.h" #include "panel.h" #include "widget.h" #include "../shellcorona.h" #include "../standaloneappcorona.h" QScriptValue constructQRectFClass(QScriptEngine *engine); +namespace { + template + inline void awaitFuture(const QFuture &future) + { + while (!future.isFinished()) { + QCoreApplication::processEvents(); + } + } +} + namespace WorkspaceScripting { ScriptEngine::ScriptEngine(Plasma::Corona *corona, QObject *parent) : QScriptEngine(parent), m_corona(corona) { Q_ASSERT(m_corona); AppInterface *interface = new AppInterface(this); connect(interface, &AppInterface::print, this, &ScriptEngine::print); m_scriptSelf = newQObject(interface, QScriptEngine::QtOwnership, QScriptEngine::ExcludeSuperClassProperties | QScriptEngine::ExcludeSuperClassMethods); setupEngine(); connect(this, &ScriptEngine::signalHandlerException, this, &ScriptEngine::exception); bindI18N(this); } ScriptEngine::~ScriptEngine() { } QScriptValue ScriptEngine::desktopById(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("activityById requires an id")); } const uint id = context->argument(0).toInt32(); ScriptEngine *env = envFor(engine); foreach (Plasma::Containment *c, env->m_corona->containments()) { if (c->id() == id && !isPanel(c)) { return env->wrap(c); } } return engine->undefinedValue(); } +QStringList ScriptEngine::availableActivities(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine) + + ScriptEngine *env = envFor(engine); + + ShellCorona *sc = qobject_cast(env->m_corona); + StandaloneAppCorona *ac = qobject_cast(env->m_corona); + if (sc) { + return sc->availableActivities(); + } else if (ac) { + return ac->availableActivities(); + } + + return QStringList(); +} + QScriptValue ScriptEngine::desktopsForActivity(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("desktopsForActivity requires an id")); } QScriptValue containments = engine->newArray(); int count = 0; const QString id = context->argument(0).toString(); // confirm this activity actually exists - KActivities::Consumer consumer; bool found = false; - for (const QString &act: consumer.activities()) { + for (const QString &act: availableActivities(context, engine)) { if (act == id) { found = true; break; } } if (!found) { containments.setProperty(QStringLiteral("length"), 0); return containments; } ScriptEngine *env = envFor(engine); foreach (Plasma::Containment *c, env->m_corona->containments()) { if (c->activity() == id && !isPanel(c)) { containments.setProperty(count, env->wrap(c)); ++count; } } if (count == 0) { // we have no desktops for this activity, so lets make them now // this can happen when the activity already exists but has never been activated // with the current shell package and layout.js is run to set up the shell for the // first time const int numScreens = env->m_corona->numScreens(); for (int i = 0; i < numScreens; ++i) { ShellCorona *sc = qobject_cast(env->m_corona); StandaloneAppCorona *ac = qobject_cast(env->m_corona); if (sc) { Plasma::Containment *c = sc->createContainmentForActivity(id, i); containments.setProperty(count, env->wrap(c)); ++count; } else if (ac) { Plasma::Containment *c = ac->createContainmentForActivity(id, i); containments.setProperty(count, env->wrap(c)); ++count; } } } containments.setProperty(QStringLiteral("length"), count); return containments; } QScriptValue ScriptEngine::desktopForScreen(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("activityForScreen requires a screen id")); } const uint screen = context->argument(0).toInt32(); ScriptEngine *env = envFor(engine); return env->wrap(env->m_corona->containmentForScreen(screen)); } QScriptValue ScriptEngine::createActivity(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() < 0) { return context->throwError(i18n("createActivity required the activity name")); } const QString name = context->argument(0).toString(); QString plugin = context->argument(1).toString(); ScriptEngine *env = envFor(engine); KActivities::Controller controller; - //TODO: if there are activities without containment, recycle + // This is not the nicest way to do this, but createActivity + // is a synchronous API :/ QFuture futureId = controller.addActivity(name); - QEventLoop loop; + awaitFuture(futureId); - QFutureWatcher *watcher = new QFutureWatcher(); - connect(watcher, &QFutureWatcherBase::finished, &loop, &QEventLoop::quit); - - watcher->setFuture(futureId); - - loop.exec(); QString id = futureId.result(); qDebug() << "Setting default Containment plugin:" << plugin; if (plugin.isEmpty() || plugin == QLatin1String("undefined")) { KConfigGroup shellCfg = KConfigGroup(KSharedConfig::openConfig(env->m_corona->package().filePath("defaults")), "Desktop"); plugin = shellCfg.readEntry("Containment", "org.kde.desktopcontainment"); } ShellCorona *sc = qobject_cast(env->m_corona); StandaloneAppCorona *ac = qobject_cast(env->m_corona); if (sc) { sc->insertActivity(id, plugin); } else if (ac) { ac->insertActivity(id, plugin); } return QScriptValue(id); } QScriptValue ScriptEngine::setCurrentActivity(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() < 0) { return context->throwError(i18n("setCurrentActivity required the activity id")); } const QString id = context->argument(0).toString(); KActivities::Controller controller; QFuture task = controller.setCurrentActivity(id); - QEventLoop loop; - - QFutureWatcher watcher; - connect(&watcher, &QFutureWatcherBase::finished, &loop, &QEventLoop::quit); - - watcher.setFuture(task); - - loop.exec(); + awaitFuture(task); return QScriptValue(task.result()); } QScriptValue ScriptEngine::setActivityName(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() < 2) { return context->throwError(i18n("setActivityName required the activity id and name")); } const QString id = context->argument(0).toString(); const QString name = context->argument(1).toString(); KActivities::Controller controller; QFuture task = controller.setActivityName(id, name); - QEventLoop loop; - - QFutureWatcher watcher; - connect(&watcher, &QFutureWatcherBase::finished, &loop, &QEventLoop::quit); - - watcher.setFuture(task); - - loop.exec(); + awaitFuture(task); return QScriptValue(); } QScriptValue ScriptEngine::activityName(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() < 1) { return context->throwError(i18n("setActivityName required the activity id and name")); } const QString id = context->argument(0).toString(); KActivities::Info info(id); return QScriptValue(info.name()); } QScriptValue ScriptEngine::currentActivity(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) Q_UNUSED(context) KActivities::Consumer consumer; return consumer.currentActivity(); } QScriptValue ScriptEngine::activities(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) - KActivities::Consumer consumer; - - return qScriptValueFromSequence(engine, consumer.activities()); + return qScriptValueFromSequence(engine, availableActivities(context, engine)); } QScriptValue ScriptEngine::newPanel(QScriptContext *context, QScriptEngine *engine) { QString plugin(QStringLiteral("org.kde.panel")); if (context->argumentCount() > 0) { plugin = context->argument(0).toString(); } return createContainment(QStringLiteral("Panel"), plugin, context, engine); } QScriptValue ScriptEngine::createContainment(const QString &type, const QString &defaultPlugin, QScriptContext *context, QScriptEngine *engine) { QString plugin = context->argumentCount() > 0 ? context->argument(0).toString() : defaultPlugin; bool exists = false; const KPluginInfo::List list = Plasma::PluginLoader::listContainmentsOfType(type); foreach (const KPluginInfo &info, list) { if (info.pluginName() == plugin) { exists = true; break; } } if (!exists) { return context->throwError(i18n("Could not find a plugin for %1 named %2.", type, plugin)); } ScriptEngine *env = envFor(engine); Plasma::Containment *c = 0; if (type == QLatin1String("Panel")) { ShellCorona *sc = qobject_cast(env->m_corona); StandaloneAppCorona *ac = qobject_cast(env->m_corona); if (sc) { c = sc->addPanel(plugin); } else if (ac) { c = ac->addPanel(plugin); } } else { c = env->m_corona->createContainment(plugin); } if (c) { if (type == QLatin1String("Panel")) { // some defaults c->setFormFactor(Plasma::Types::Horizontal); c->setLocation(Plasma::Types::TopEdge); } c->updateConstraints(Plasma::Types::AllConstraints | Plasma::Types::StartupCompletedConstraint); c->flushPendingConstraintsEvents(); } return env->wrap(c); } QScriptValue ScriptEngine::wrap(Plasma::Applet *w) { Widget *wrapper = new Widget(w); QScriptValue v = newQObject(wrapper, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeSuperClassProperties | QScriptEngine::ExcludeSuperClassMethods); return v; } QScriptValue ScriptEngine::wrap(Plasma::Containment *c) { Containment *wrapper = isPanel(c) ? new Panel(c) : new Containment(c); return wrap(wrapper); } QScriptValue ScriptEngine::wrap(Containment *c) { QScriptValue v = newQObject(c, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeSuperClassProperties | QScriptEngine::ExcludeSuperClassMethods); v.setProperty(QStringLiteral("widgetById"), newFunction(Containment::widgetById)); v.setProperty(QStringLiteral("addWidget"), newFunction(Containment::addWidget)); v.setProperty(QStringLiteral("widgets"), newFunction(Containment::widgets)); return v; } int ScriptEngine::defaultPanelScreen() const { return 0; } ScriptEngine *ScriptEngine::envFor(QScriptEngine *engine) { QObject *object = engine->globalObject().toQObject(); Q_ASSERT(object); AppInterface *interface = qobject_cast(object); Q_ASSERT(interface); ScriptEngine *env = qobject_cast(interface->parent()); Q_ASSERT(env); return env; } QScriptValue ScriptEngine::panelById(QScriptContext *context, QScriptEngine *engine) { if (context->argumentCount() == 0) { return context->throwError(i18n("panelById requires an id")); } const uint id = context->argument(0).toInt32(); ScriptEngine *env = envFor(engine); foreach (Plasma::Containment *c, env->m_corona->containments()) { if (c->id() == id && isPanel(c)) { return env->wrap(c); } } return engine->undefinedValue(); } QScriptValue ScriptEngine::panels(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) QScriptValue panels = engine->newArray(); ScriptEngine *env = envFor(engine); int count = 0; foreach (Plasma::Containment *c, env->m_corona->containments()) { if (isPanel(c)) { panels.setProperty(count, env->wrap(c)); ++count; } } panels.setProperty(QStringLiteral("length"), count); return panels; } QScriptValue ScriptEngine::fileExists(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } const QString path = context->argument(0).toString(); if (path.isEmpty()) { return false; } QFile f(KShell::tildeExpand(path)); return f.exists(); } QScriptValue ScriptEngine::loadTemplate(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { // qDebug() << "no arguments"; return false; } const QString layout = context->argument(0).toString(); if (layout.isEmpty() || layout.contains(QStringLiteral("'"))) { // qDebug() << "layout is empty"; return false; } const QString constraint = QStringLiteral("[X-KDE-PluginInfo-Name] == '%2'").arg(layout); KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/LayoutTemplate"), constraint); if (offers.isEmpty()) { // qDebug() << "offers fail" << constraint; return false; } KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); KPluginInfo info(offers.first()); const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, package.defaultPackageRoot() + info.pluginName() + "/metadata.desktop"); if (path.isEmpty()) { // qDebug() << "script path is empty"; return false; } package.setPath(info.pluginName()); const QString scriptFile = package.filePath("mainscript"); if (scriptFile.isEmpty()) { // qDebug() << "scriptfile is empty"; return false; } QFile file(scriptFile); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << i18n("Unable to load script file: %1", path); return false; } QString script = file.readAll(); if (script.isEmpty()) { // qDebug() << "script is empty"; return false; } ScriptEngine *env = envFor(engine); env->globalObject().setProperty(QStringLiteral("templateName"), env->newVariant(info.name()), QScriptValue::ReadOnly | QScriptValue::Undeletable); env->globalObject().setProperty(QStringLiteral("templateComment"), env->newVariant(info.comment()), QScriptValue::ReadOnly | QScriptValue::Undeletable); QScriptValue rv = env->newObject(); QScriptContext *ctx = env->pushContext(); ctx->setThisObject(rv); env->evaluateScript(script, path); env->popContext(); return rv; } QScriptValue ScriptEngine::applicationExists(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } const QString application = context->argument(0).toString(); if (application.isEmpty()) { return false; } // first, check for it in $PATH if (!QStandardPaths::findExecutable(application).isEmpty()) { return true; } if (KService::serviceByStorageId(application)) { return true; } if (application.contains(QStringLiteral("'"))) { // apostrophes just screw up the trader lookups below, so check for it return false; } // next, consult ksycoca for an app by that name if (!KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("Name =~ '%1'").arg(application)).isEmpty()) { return true; } // next, consult ksycoca for an app by that generic name if (!KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("GenericName =~ '%1'").arg(application)).isEmpty()) { return true; } return false; } QString ScriptEngine::onlyExec(const QString &commandLine) { if (commandLine.isEmpty()) { return commandLine; } return KShell::splitArgs(commandLine, KShell::TildeExpand).first(); } QScriptValue ScriptEngine::defaultApplication(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } const QString application = context->argument(0).toString(); if (application.isEmpty()) { return false; } const bool storageId = context->argumentCount() < 2 ? false : context->argument(1).toBool(); // FIXME: there are some pretty horrible hacks below, in the sense that they assume a very // specific implementation system. there is much room for improvement here. see // kdebase-runtime/kcontrol/componentchooser/ for all the gory details ;) if (application.compare(QLatin1String("mailer"), Qt::CaseInsensitive) == 0) { // KEMailSettings settings; // in KToolInvocation, the default is kmail; but let's be friendlier :) // QString command = settings.getSetting(KEMailSettings::ClientProgram); QString command; if (command.isEmpty()) { if (KService::Ptr kontact = KService::serviceByStorageId(QStringLiteral("kontact"))) { return storageId ? kontact->storageId() : onlyExec(kontact->exec()); } else if (KService::Ptr kmail = KService::serviceByStorageId(QStringLiteral("kmail"))) { return storageId ? kmail->storageId() : onlyExec(kmail->exec()); } } if (!command.isEmpty()) { //if (settings.getSetting(KEMailSettings::ClientTerminal) == "true") { if (false) { KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); const QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); command = preferredTerminal + QLatin1String(" -e ") + command; } return command; } } else if (application.compare(QLatin1String("browser"), Qt::CaseInsensitive) == 0) { KConfigGroup config(KSharedConfig::openConfig(), "General"); QString browserApp = config.readPathEntry("BrowserApplication", QString()); if (browserApp.isEmpty()) { const KService::Ptr htmlApp = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html")); if (htmlApp) { browserApp = storageId ? htmlApp->storageId() : htmlApp->exec(); } } else if (browserApp.startsWith('!')) { browserApp = browserApp.mid(1); } return onlyExec(browserApp); } else if (application.compare(QLatin1String("terminal"), Qt::CaseInsensitive) == 0) { KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); return onlyExec(confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole"))); } else if (application.compare(QLatin1String("filemanager"), Qt::CaseInsensitive) == 0) { KService::Ptr service = KMimeTypeTrader::self()->preferredService(QStringLiteral("inode/directory")); if (service) { return storageId ? service->storageId() : onlyExec(service->exec()); } } else if (application.compare(QLatin1String("windowmanager"), Qt::CaseInsensitive) == 0) { KConfig cfg(QStringLiteral("ksmserverrc"), KConfig::NoGlobals); KConfigGroup confGroup(&cfg, "General"); return onlyExec(confGroup.readEntry("windowManager", QStringLiteral("kwin"))); } else if (KService::Ptr service = KMimeTypeTrader::self()->preferredService(application)) { return storageId ? service->storageId() : onlyExec(service->exec()); } else { // try the files in share/apps/kcm_componentchooser/ const QStringList services = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kcm_componentchooser/")); qDebug() << "ok, trying in" << services; foreach (const QString &service, services) { if (!service.endsWith(QLatin1String(".desktop"))) { continue; } KConfig config(service, KConfig::SimpleConfig); KConfigGroup cg = config.group(QByteArray()); const QString type = cg.readEntry("valueName", QString()); //qDebug() << " checking" << service << type << application; if (type.compare(application, Qt::CaseInsensitive) == 0) { KConfig store(cg.readPathEntry("storeInFile", QStringLiteral("null"))); KConfigGroup storeCg(&store, cg.readEntry("valueSection", QString())); const QString exec = storeCg.readPathEntry(cg.readEntry("valueName", "kcm_componenchooser_null"), cg.readEntry("defaultImplementation", QString())); if (!exec.isEmpty()) { return exec; } break; } } } return false; } QScriptValue ScriptEngine::applicationPath(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return false; } const QString application = context->argument(0).toString(); if (application.isEmpty()) { return false; } // first, check for it in $PATH const QString path = QStandardPaths::findExecutable(application); if (!path.isEmpty()) { return path; } if (KService::Ptr service = KService::serviceByStorageId(application)) { return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, service->entryPath()); } if (application.contains(QStringLiteral("'"))) { // apostrophes just screw up the trader lookups below, so check for it return QString(); } // next, consult ksycoca for an app by that name KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("Name =~ '%1'").arg(application)); if (offers.isEmpty()) { // next, consult ksycoca for an app by that generic name offers = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("GenericName =~ '%1'").arg(application)); } if (!offers.isEmpty()) { KService::Ptr offer = offers.first(); return QStandardPaths::locate(QStandardPaths::ApplicationsLocation, offer->entryPath()); } return QString(); } QScriptValue ScriptEngine::userDataPath(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) if (context->argumentCount() == 0) { return QDir::homePath(); } const QString type = context->argument(0).toString(); if (type.isEmpty()) { return QDir::homePath(); } QStandardPaths::StandardLocation location = QStandardPaths::GenericDataLocation; if (type.compare(QLatin1String("desktop"), Qt::CaseInsensitive) == 0) { location = QStandardPaths::DesktopLocation; } else if (type.compare(QLatin1String("documents"), Qt::CaseInsensitive) == 0) { location = QStandardPaths::DocumentsLocation; } else if (type.compare(QLatin1String("music"), Qt::CaseInsensitive) == 0) { location = QStandardPaths::MusicLocation; } else if (type.compare(QLatin1String("video"), Qt::CaseInsensitive) == 0) { location = QStandardPaths::MoviesLocation; } else if (type.compare(QLatin1String("downloads"), Qt::CaseInsensitive) == 0) { location = QStandardPaths::DownloadLocation; } else if (type.compare(QLatin1String("pictures"), Qt::CaseInsensitive) == 0) { location = QStandardPaths::PicturesLocation; } else if (type.compare(QLatin1String("config"), Qt::CaseInsensitive) == 0) { location = QStandardPaths::GenericConfigLocation; } if (context->argumentCount() > 1) { QString loc = QStandardPaths::writableLocation(location); loc.append(QDir::separator()); loc.append(context->argument(1).toString()); return loc; } const QStringList &locations = QStandardPaths::standardLocations(location); return locations.count() ? locations.first() : QString(); } QScriptValue ScriptEngine::knownWallpaperPlugins(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(engine) QString formFactor; if (context->argumentCount() > 0) { formFactor = context->argument(0).toString(); } QString constraint; if (!formFactor.isEmpty()) { constraint.append("[X-Plasma-FormFactors] ~~ '").append(formFactor).append("'"); } QList wallpapers = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/Wallpaper"), QString()); QScriptValue rv = engine->newArray(wallpapers.size()); for (auto wp : wallpapers) { rv.setProperty(wp.name(), engine->newArray(0)); } return rv; } QScriptValue ScriptEngine::configFile(QScriptContext *context, QScriptEngine *engine) { ConfigGroup *file = 0; if (context->argumentCount() > 0) { if (context->argument(0).isString()) { file = new ConfigGroup; const QString &fileName = context->argument(0).toString(); const ScriptEngine *env = envFor(engine); const Plasma::Corona* corona = env->corona(); if (fileName == corona->config()->name()) { file->setConfig(corona->config()); } else { file->setFile(fileName); } if (context->argumentCount() > 1) { file->setGroup(context->argument(1).toString()); } } else if (ConfigGroup *parent= qobject_cast(context->argument(0).toQObject())) { file = new ConfigGroup(parent); if (context->argumentCount() > 1) { file->setGroup(context->argument(1).toString()); } } } else { file = new ConfigGroup; } QScriptValue v = engine->newQObject(file, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeSuperClassProperties | QScriptEngine::ExcludeSuperClassMethods); return v; } void ScriptEngine::setupEngine() { QScriptValue v = globalObject(); QScriptValueIterator it(v); while (it.hasNext()) { it.next(); // we provide our own print implementation, but we want the rest if (it.name() != QLatin1String("print")) { m_scriptSelf.setProperty(it.name(), it.value()); } } m_scriptSelf.setProperty(QStringLiteral("QRectF"), constructQRectFClass(this)); m_scriptSelf.setProperty(QStringLiteral("createActivity"), newFunction(ScriptEngine::createActivity)); m_scriptSelf.setProperty(QStringLiteral("setCurrentActivity"), newFunction(ScriptEngine::setCurrentActivity)); m_scriptSelf.setProperty(QStringLiteral("currentActivity"), newFunction(ScriptEngine::currentActivity)); m_scriptSelf.setProperty(QStringLiteral("activities"), newFunction(ScriptEngine::activities)); m_scriptSelf.setProperty(QStringLiteral("setActivityName"), newFunction(ScriptEngine::setActivityName)); m_scriptSelf.setProperty(QStringLiteral("activityName"), newFunction(ScriptEngine::activityName)); m_scriptSelf.setProperty(QStringLiteral("setActivityName"), newFunction(ScriptEngine::setActivityName)); m_scriptSelf.setProperty(QStringLiteral("Panel"), newFunction(ScriptEngine::newPanel, newObject())); m_scriptSelf.setProperty(QStringLiteral("desktopsForActivity"), newFunction(ScriptEngine::desktopsForActivity)); m_scriptSelf.setProperty(QStringLiteral("desktops"), newFunction(ScriptEngine::desktops)); m_scriptSelf.setProperty(QStringLiteral("desktopById"), newFunction(ScriptEngine::desktopById)); m_scriptSelf.setProperty(QStringLiteral("desktopForScreen"), newFunction(ScriptEngine::desktopForScreen)); m_scriptSelf.setProperty(QStringLiteral("panelById"), newFunction(ScriptEngine::panelById)); m_scriptSelf.setProperty(QStringLiteral("panels"), newFunction(ScriptEngine::panels)); m_scriptSelf.setProperty(QStringLiteral("fileExists"), newFunction(ScriptEngine::fileExists)); m_scriptSelf.setProperty(QStringLiteral("loadTemplate"), newFunction(ScriptEngine::loadTemplate)); m_scriptSelf.setProperty(QStringLiteral("applicationExists"), newFunction(ScriptEngine::applicationExists)); m_scriptSelf.setProperty(QStringLiteral("defaultApplication"), newFunction(ScriptEngine::defaultApplication)); m_scriptSelf.setProperty(QStringLiteral("userDataPath"), newFunction(ScriptEngine::userDataPath)); m_scriptSelf.setProperty(QStringLiteral("applicationPath"), newFunction(ScriptEngine::applicationPath)); m_scriptSelf.setProperty(QStringLiteral("knownWallpaperPlugins"), newFunction(ScriptEngine::knownWallpaperPlugins)); m_scriptSelf.setProperty(QStringLiteral("ConfigFile"), newFunction(ScriptEngine::configFile)); m_scriptSelf.setProperty(QStringLiteral("gridUnit"), ScriptEngine::gridUnit()); setGlobalObject(m_scriptSelf); } bool ScriptEngine::isPanel(const Plasma::Containment *c) { if (!c) { return false; } return c->containmentType() == Plasma::Types::PanelContainment || c->containmentType() == Plasma::Types::CustomPanelContainment; } QScriptValue ScriptEngine::desktops(QScriptContext *context, QScriptEngine *engine) { Q_UNUSED(context) QScriptValue containments = engine->newArray(); ScriptEngine *env = envFor(engine); int count = 0; foreach (Plasma::Containment *c, env->corona()->containments()) { if (!isPanel(c)) { containments.setProperty(count, env->wrap(c)); ++count; } } containments.setProperty(QStringLiteral("length"), count); return containments; } QScriptValue ScriptEngine::gridUnit() { int gridUnit = QFontMetrics(QGuiApplication::font()).boundingRect(QStringLiteral("M")).height(); if (gridUnit % 2 != 0) { gridUnit++; } return gridUnit; } Plasma::Corona *ScriptEngine::corona() const { return m_corona; } bool ScriptEngine::evaluateScript(const QString &script, const QString &path) { //qDebug() << "evaluating" << m_editor->toPlainText(); evaluate(script, path); if (hasUncaughtException()) { //qDebug() << "catch the exception!"; QString error = i18n("Error: %1 at line %2\n\nBacktrace:\n%3", uncaughtException().toString(), QString::number(uncaughtExceptionLineNumber()), uncaughtExceptionBacktrace().join(QStringLiteral("\n "))); emit printError(error); return false; } return true; } void ScriptEngine::exception(const QScriptValue &value) { //qDebug() << "exception caught!" << value.toVariant(); emit printError(value.toVariant().toString()); } QStringList ScriptEngine::pendingUpdateScripts(Plasma::Corona *corona) { if (!corona->package().metadata().isValid()) { qWarning() << "Warning: corona package invalid"; return QStringList(); } const QString appName = corona->package().metadata().pluginName(); QStringList scripts; const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "plasma/shells/" + appName + QStringLiteral("/contents/updates"), QStandardPaths::LocateDirectory); Q_FOREACH(const QString& dir, dirs) { QDirIterator it(dir, QStringList() << QStringLiteral("*.js")); while (it.hasNext()) { scripts.append(it.next()); } } QStringList scriptPaths; if (scripts.isEmpty()) { //qDebug() << "no update scripts"; return scriptPaths; } KConfigGroup cg(KSharedConfig::openConfig(), "Updates"); QStringList performed = cg.readEntry("performed", QStringList()); const QString localXdgDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); foreach (const QString &script, scripts) { if (performed.contains(script)) { continue; } if (script.startsWith(localXdgDir)) { // qDebug() << "skipping user local script: " << script; continue; } scriptPaths.append(script); performed.append(script); } cg.writeEntry("performed", performed); KSharedConfig::openConfig()->sync(); return scriptPaths; } } diff --git a/shell/scripting/scriptengine.h b/shell/scripting/scriptengine.h index 663bfd759..6eae8a1b2 100644 --- a/shell/scripting/scriptengine.h +++ b/shell/scripting/scriptengine.h @@ -1,115 +1,115 @@ /* * Copyright 2009 Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SCRIPTENGINE #define SCRIPTENGINE #include #include #include #include #include "../shellcorona.h" namespace Plasma { class Applet; class Containment; } // namespace Plasma namespace WorkspaceScripting { class Containment; class ScriptEngine : public QScriptEngine { Q_OBJECT public: ScriptEngine(Plasma::Corona *corona, QObject *parent = 0); ~ScriptEngine() override; static QStringList pendingUpdateScripts(Plasma::Corona *corona); Plasma::Corona *corona() const; QScriptValue wrap(Plasma::Applet *w); virtual QScriptValue wrap(Plasma::Containment *c); QScriptValue wrap(Containment *c); virtual int defaultPanelScreen() const; static bool isPanel(const Plasma::Containment *c); static ScriptEngine *envFor(QScriptEngine *engine); public Q_SLOTS: bool evaluateScript(const QString &script, const QString &path = QString()); Q_SIGNALS: void print(const QString &string); void printError(const QString &string); private: void setupEngine(); static QString onlyExec(const QString &commandLine); - // containment accessors - static QStringList availableContainments(const QString &type); + static QStringList availableActivities(QScriptContext *context, QScriptEngine *engine); + static QScriptValue createActivity(QScriptContext *context, QScriptEngine *engine); static QScriptValue setCurrentActivity(QScriptContext *context, QScriptEngine *engine); static QScriptValue currentActivity(QScriptContext *controller, QScriptEngine *engine); static QScriptValue activities(QScriptContext *context, QScriptEngine *engine); static QScriptValue setActivityName(QScriptContext *context, QScriptEngine *engine); static QScriptValue activityName(QScriptContext *context, QScriptEngine *engine); static QScriptValue newPanel(QScriptContext *context, QScriptEngine *engine); static QScriptValue desktopsForActivity(QScriptContext *context, QScriptEngine *engine); static QScriptValue desktops(QScriptContext *context, QScriptEngine *engine); static QScriptValue desktopById(QScriptContext *context, QScriptEngine *engine); static QScriptValue desktopForScreen(QScriptContext *context, QScriptEngine *engine); static QScriptValue panelById(QScriptContext *context, QScriptEngine *engine); static QScriptValue panels(QScriptContext *context, QScriptEngine *engine); static QScriptValue fileExists(QScriptContext *context, QScriptEngine *engine); static QScriptValue loadTemplate(QScriptContext *context, QScriptEngine *engine); static QScriptValue applicationExists(QScriptContext *context, QScriptEngine *engine); static QScriptValue defaultApplication(QScriptContext *context, QScriptEngine *engine); static QScriptValue applicationPath(QScriptContext *context, QScriptEngine *engine); static QScriptValue userDataPath(QScriptContext *context, QScriptEngine *engine); static QScriptValue knownWallpaperPlugins(QScriptContext *context, QScriptEngine *engine); static QScriptValue configFile(QScriptContext *context, QScriptEngine *engine); static QScriptValue gridUnit(); // helpers static QScriptValue createContainment(const QString &type, const QString &defautPlugin, QScriptContext *context, QScriptEngine *engine); private Q_SLOTS: void exception(const QScriptValue &value); private: Plasma::Corona *m_corona; QScriptValue m_scriptSelf; }; static const int PLASMA_DESKTOP_SCRIPTING_VERSION = 20; } #endif diff --git a/shell/shellcorona.cpp b/shell/shellcorona.cpp index f43d41d2b..84098248c 100644 --- a/shell/shellcorona.cpp +++ b/shell/shellcorona.cpp @@ -1,1681 +1,1688 @@ /* * Copyright 2008 Aaron Seigo * Copyright 2013 Sebastian Kügler * Copyright 2013 Ivan Cukic * Copyright 2013 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "shellcorona.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config-ktexteditor.h" // HAVE_KTEXTEDITOR #include "alternativeshelper.h" #include "desktopview.h" #include "panelview.h" #include "scripting/scriptengine.h" #include "plasmaquick/configview.h" #include "shellmanager.h" #include "osd.h" #include "waylanddialogfilter.h" #include "plasmashelladaptor.h" #ifndef NDEBUG #define CHECK_SCREEN_INVARIANTS screenInvariants(); #else #define CHECK_SCREEN_INVARIANTS #endif #if HAVE_X11 #include #include #endif static const int s_configSyncDelay = 10000; // 10 seconds ShellCorona::ShellCorona(QObject *parent) : Plasma::Corona(parent), m_activityController(new KActivities::Controller(this)), m_addPanelAction(nullptr), m_addPanelsMenu(nullptr), m_interactiveConsole(nullptr), m_waylandPlasmaShell(nullptr) { setupWaylandIntegration(); qmlRegisterUncreatableType("org.kde.plasma.shell", 2, 0, "Desktop", QStringLiteral("It is not possible to create objects of type Desktop")); qmlRegisterUncreatableType("org.kde.plasma.shell", 2, 0, "Panel", QStringLiteral("It is not possible to create objects of type Panel")); m_lookAndFeelPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); if (!packageName.isEmpty()) { m_lookAndFeelPackage.setPath(packageName); } connect(this, &Plasma::Corona::containmentCreated, this, [this] (Plasma::Containment *c) { executeSetupPlasmoidScript(c, c); }); connect(this, &Plasma::Corona::availableScreenRectChanged, this, &Plasma::Corona::availableScreenRegionChanged); m_appConfigSyncTimer.setSingleShot(true); m_appConfigSyncTimer.setInterval(s_configSyncDelay); connect(&m_appConfigSyncTimer, &QTimer::timeout, this, &ShellCorona::syncAppConfig); m_waitingPanelsTimer.setSingleShot(true); m_waitingPanelsTimer.setInterval(250); connect(&m_waitingPanelsTimer, &QTimer::timeout, this, &ShellCorona::createWaitingPanels); m_reconsiderOutputsTimer.setSingleShot(true); m_reconsiderOutputsTimer.setInterval(1000); connect(&m_reconsiderOutputsTimer, &QTimer::timeout, this, &ShellCorona::reconsiderOutputs); m_desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package().filePath("defaults")), "Desktop"); new PlasmaShellAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/PlasmaShell"), this); connect(this, &Plasma::Corona::startupCompleted, this, []() { qDebug() << "Plasma Shell startup completed"; QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); }); // Look for theme config in plasmarc, if it isn't configured, take the theme from the // LookAndFeel package, if either is set, change the default theme connect(qApp, &QCoreApplication::aboutToQuit, this, [this]() { //saveLayout is a slot but arguments not compatible saveLayout(); }); connect(this, &ShellCorona::containmentAdded, this, &ShellCorona::handleContainmentAdded); QAction *dashboardAction = actions()->addAction(QStringLiteral("show dashboard")); QObject::connect(dashboardAction, &QAction::triggered, this, &ShellCorona::setDashboardShown); dashboardAction->setText(i18n("Show Desktop")); connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, [dashboardAction](bool showing) { dashboardAction->setText(showing ? i18n("Hide Desktop") : i18n("Show Desktop")); dashboardAction->setChecked(showing); }); dashboardAction->setAutoRepeat(true); dashboardAction->setCheckable(true); dashboardAction->setIcon(QIcon::fromTheme(QStringLiteral("dashboard-show"))); dashboardAction->setData(Plasma::Types::ControlAction); KGlobalAccel::self()->setGlobalShortcut(dashboardAction, Qt::CTRL + Qt::Key_F12); checkAddPanelAction(); connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(checkAddPanelAction(QStringList))); //Activity stuff QAction *activityAction = actions()->addAction(QStringLiteral("manage activities")); connect(activityAction, &QAction::triggered, this, &ShellCorona::toggleActivityManager); activityAction->setText(i18n("Activities...")); activityAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-activities"))); activityAction->setData(Plasma::Types::ConfigureAction); activityAction->setShortcut(QKeySequence(QStringLiteral("alt+d, alt+a"))); activityAction->setShortcutContext(Qt::ApplicationShortcut); KGlobalAccel::self()->setGlobalShortcut(activityAction, Qt::META + Qt::Key_Q); QAction *stopActivityAction = actions()->addAction(QStringLiteral("stop current activity")); QObject::connect(stopActivityAction, &QAction::triggered, this, &ShellCorona::stopCurrentActivity); stopActivityAction->setText(i18n("Stop Current Activity")); stopActivityAction->setData(Plasma::Types::ControlAction); stopActivityAction->setVisible(false); KGlobalAccel::self()->setGlobalShortcut(stopActivityAction, Qt::META + Qt::Key_S); connect(m_activityController, &KActivities::Controller::currentActivityChanged, this, &ShellCorona::currentActivityChanged); connect(m_activityController, &KActivities::Controller::activityAdded, this, &ShellCorona::activityAdded); connect(m_activityController, &KActivities::Controller::activityRemoved, this, &ShellCorona::activityRemoved); new Osd(this); qApp->installEventFilter(this); } ShellCorona::~ShellCorona() { qDeleteAll(m_views); m_views.clear(); while (!containments().isEmpty()) { //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona delete containments().first(); } qDeleteAll(m_panelViews); m_panelViews.clear(); } bool ShellCorona::eventFilter(QObject *watched, QEvent *event) { #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) if (event->type() == QEvent::PlatformSurface && watched->inherits("PlasmaQuick::Dialog")) { QPlatformSurfaceEvent *se = static_cast(event); if (se->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { if (QGuiApplication::platformName().startsWith( QLatin1String("wayland"), Qt::CaseInsensitive)) { WaylandDialogFilter::install(qobject_cast(watched), this); } } } #endif return QObject::eventFilter(watched, event); } KPackage::Package ShellCorona::lookAndFeelPackage() { return m_lookAndFeelPackage; } void ShellCorona::setShell(const QString &shell) { if (m_shell == shell) { return; } m_shell = shell; KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Shell")); package.setPath(shell); package.setAllowExternalPaths(true); setKPackage(package); m_desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Desktop"); const QString themeGroupKey = QStringLiteral("Theme"); const QString themeNameKey = QStringLiteral("name"); QString themeName; KConfigGroup plasmarc(KSharedConfig::openConfig(QStringLiteral("plasmarc")), themeGroupKey); themeName = plasmarc.readEntry(themeNameKey, themeName); if (themeName.isEmpty()) { KConfigGroup shellCfg = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Theme"); themeName = shellCfg.readEntry("name", "default"); KConfigGroup lnfCfg = KConfigGroup(KSharedConfig::openConfig( m_lookAndFeelPackage.filePath("defaults")), "plasmarc" ); lnfCfg = KConfigGroup(&lnfCfg, themeGroupKey); themeName = lnfCfg.readEntry(themeNameKey, themeName); } if (!themeName.isEmpty()) { Plasma::Theme *t = new Plasma::Theme(this); t->setThemeName(themeName); } //FIXME: this would change the runtime platform to a fixed one if available // but a different way to load platform specific components is needed beforehand // because if we import and use two different components plugin, the second time // the import is called it will fail /* KConfigGroup cg(KSharedConfig::openConfig(package.filePath("defaults")), "General"); KDeclarative::KDeclarative::setRuntimePlatform(cg.readEntry("DefaultRuntimePlatform", QStringList()));*/ unload(); /* * we want to make an initial load once we have the initial screen config and we have loaded the activities _IF_ KAMD is running * it is valid for KAMD to not be running. * * Potentially 2 async jobs * * here we connect for status changes from KAMD, and fetch the first config from kscreen. * load() will check that we have a kscreen config, and m_activityController->serviceStatus() is not loading (i.e not unknown) * * It might seem that we only need this connection if the activityConsumer is currently in state Unknown, however * there is an issue where m_activityController will start the kactivitymanagerd, as KAMD is starting the serviceStatus will be "not running" * Whilst we are loading the kscreen config, the event loop runs and we might find KAMD has started. * m_activityController will change from "not running" to unknown, and might still be unknown when the kscreen fetching is complete. * * if that happens we want to continue monitoring for state changes, and only finally load when it is up. * * See https://bugs.kde.org/show_bug.cgi?id=342431 be careful about changing * * The unique connection makes sure we don't reload plasma if KAMD ever crashes and reloads, the signal is disconnected in the body of load */ connect(m_activityController, &KActivities::Controller::serviceStatusChanged, this, &ShellCorona::load, Qt::UniqueConnection); load(); } QString ShellCorona::shell() const { return m_shell; } bool outputLess(QScreen* a, QScreen* b) { const QPoint aPos = a->geometry().topLeft(); const QPoint bPos = b->geometry().topLeft(); return (qGuiApp->primaryScreen() == a || (qGuiApp->primaryScreen() != b && (aPos.x() < bPos.x() || (aPos.x() == bPos.x() && aPos.y() < bPos.y())))); } static QList sortOutputs(const QList &outputs) { QList ret = outputs; std::sort(ret.begin(), ret.end(), outputLess); return ret; } void ShellCorona::load() { if (m_shell.isEmpty() || m_activityController->serviceStatus() == KActivities::Controller::Unknown) { return; } disconnect(m_activityController, &KActivities::Controller::serviceStatusChanged, this, &ShellCorona::load); loadLayout("plasma-" + m_shell + "-appletsrc"); checkActivities(); if (containments().isEmpty()) { loadDefaultLayout(); processUpdateScripts(); } else { processUpdateScripts(); foreach(Plasma::Containment *containment, containments()) { if (containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment) { //Don't give a view to containments that don't want one (negative lastscreen) //this is pretty mucha special case for the systray if (!m_waitingPanels.contains(containment) && containment->lastScreen() >= 0) { m_waitingPanels << containment; } //historically CustomContainments are treated as desktops } else if (containment->containmentType() == Plasma::Types::DesktopContainment || containment->containmentType() == Plasma::Types::CustomContainment) { //FIXME ideally fix this, or at least document the crap out of it int screen = containment->lastScreen(); if (screen < 0) { screen = 0; qWarning() << "last screen is < 0 so putting containment on screen " << screen; } insertContainment(containment->activity(), screen, containment); } } } for (QScreen* screen : sortOutputs(qGuiApp->screens())) { addOutput(screen); } connect(qGuiApp, &QGuiApplication::screenAdded, this, &ShellCorona::addOutput); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &ShellCorona::primaryOutputChanged); connect(qGuiApp, &QGuiApplication::screenRemoved, this, &ShellCorona::screenRemoved); if (!m_waitingPanels.isEmpty()) { m_waitingPanelsTimer.start(); } if (config()->isImmutable() || !KAuthorized::authorize(QStringLiteral("plasma/plasmashell/unlockedDesktop"))) { setImmutability(Plasma::Types::SystemImmutable); } else { KConfigGroup coronaConfig(config(), "General"); setImmutability((Plasma::Types::ImmutabilityType)coronaConfig.readEntry("immutability", (int)Plasma::Types::Mutable)); } } void ShellCorona::primaryOutputChanged() { if (m_views.isEmpty()) { return; } QScreen *oldPrimary = m_views[0]->screen(); QScreen *newPrimary = qGuiApp->primaryScreen(); if (!newPrimary || newPrimary == oldPrimary) { return; } bool screenAlreadyUsed = false; foreach(DesktopView *view, m_views){ if(view->screen() == newPrimary) screenAlreadyUsed = true; } if(!screenAlreadyUsed){ //This happens when a new primary output gets connected and primaryOutputChanged() is called before addOutput() //addOutput will take care of setting the primary screen return; } qDebug() << "primary changed!" << oldPrimary->name() << newPrimary->name(); foreach (DesktopView *view, m_views) { if (view->screen() == newPrimary) { Q_ASSERT(m_views[0]->screen() != view->screen()); Q_ASSERT(oldPrimary != newPrimary); Q_ASSERT(m_views[0]->screen() == oldPrimary); Q_ASSERT(m_views[0]->screen() != newPrimary); // Q_ASSERT(m_views[0]->geometry() == oldPrimary->geometry()); qDebug() << "adapting" << newPrimary->geometry() << oldPrimary->geometry(); view->setScreen(oldPrimary); break; } } m_views[0]->setScreen(newPrimary); Q_ASSERT(m_views[0]->screen()==newPrimary); foreach (PanelView *panel, m_panelViews) { if (panel->screen() == oldPrimary) { panel->setScreen(newPrimary); } else if (panel->screen() == newPrimary) { panel->setScreen(oldPrimary); } } CHECK_SCREEN_INVARIANTS } #ifndef NDEBUG void ShellCorona::screenInvariants() const { Q_ASSERT(m_views.count() <= QGuiApplication::screens().count()); QScreen *s = m_views.isEmpty() ? nullptr : m_views[0]->screen(); QScreen* ks = qGuiApp->primaryScreen(); Q_ASSERT(ks == s); QSet screens; int i = 0; foreach (const DesktopView *view, m_views) { QScreen *screen = view->screen(); Q_ASSERT(!screens.contains(screen)); Q_ASSERT(!m_redundantOutputs.contains(screen)); // commented out because a different part of the code-base is responsible for this // and sometimes is not yet called here. // Q_ASSERT(!view->fillScreen() || view->geometry() == screen->geometry()); Q_ASSERT(view->containment()); Q_ASSERT(view->containment()->screen() == i || view->containment()->screen() == -1); Q_ASSERT(view->containment()->lastScreen() == i || view->containment()->lastScreen() == -1); Q_ASSERT(view->isVisible()); foreach (const PanelView *panel, panelsForScreen(screen)) { Q_ASSERT(panel->containment()); Q_ASSERT(panel->containment()->screen() == i || panel->containment()->screen() == -1); Q_ASSERT(panel->isVisible()); } screens.insert(screen); ++i; } foreach (QScreen* out, m_redundantOutputs) { Q_ASSERT(isOutputRedundant(out)); } if (m_views.isEmpty()) { qWarning() << "no screens!!"; } } #endif void ShellCorona::showAlternativesForApplet(Plasma::Applet *applet) { const QString alternativesQML = package().filePath("appletalternativesui"); if (alternativesQML.isEmpty()) { return; } KDeclarative::QmlObject *qmlObj = new KDeclarative::QmlObject(this); qmlObj->setInitializationDelayed(true); qmlObj->setSource(QUrl::fromLocalFile(alternativesQML)); AlternativesHelper *helper = new AlternativesHelper(applet, qmlObj); qmlObj->rootContext()->setContextProperty(QStringLiteral("alternativesHelper"), helper); m_alternativesObjects << qmlObj; qmlObj->completeInitialization(); connect(qmlObj->rootObject(), SIGNAL(visibleChanged(bool)), this, SLOT(alternativesVisibilityChanged(bool))); connect(applet, &Plasma::Applet::destroyedChanged, this, [this, qmlObj] (bool destroyed) { if (!destroyed) { return; } QMutableListIterator it(m_alternativesObjects); while (it.hasNext()) { KDeclarative::QmlObject *obj = it.next(); if (obj == qmlObj) { it.remove(); obj->deleteLater(); } } }); } void ShellCorona::alternativesVisibilityChanged(bool visible) { if (visible) { return; } QObject *root = sender(); QMutableListIterator it(m_alternativesObjects); while (it.hasNext()) { KDeclarative::QmlObject *obj = it.next(); if (obj->rootObject() == root) { it.remove(); obj->deleteLater(); } } } void ShellCorona::unload() { if (m_shell.isEmpty()) { return; } qDeleteAll(containments()); } KSharedConfig::Ptr ShellCorona::applicationConfig() { return KSharedConfig::openConfig(); } void ShellCorona::requestApplicationConfigSync() { m_appConfigSyncTimer.start(); } void ShellCorona::loadDefaultLayout() { QString script = ShellManager::s_testModeLayout; if (script.isEmpty()) { script = m_lookAndFeelPackage.filePath("layouts", QString(shell() + "-layout.js").toLatin1()); } if (script.isEmpty()) { script = package().filePath("defaultlayout"); } QFile file(script); if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { QString code = file.readAll(); qDebug() << "evaluating startup script:" << script; WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); if (!scriptEngine.evaluateScript(code, script)) { qWarning() << "failed to initialize layout properly:" << script; } } Q_EMIT startupCompleted(); } void ShellCorona::processUpdateScripts() { WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); foreach (const QString &script, WorkspaceScripting::ScriptEngine::pendingUpdateScripts(this)) { QFile file(script); if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { QString code = file.readAll(); scriptEngine.evaluateScript(code); } else { qWarning() << "Unable to open the script file" << script << "for reading"; } } } int ShellCorona::numScreens() const { return qGuiApp->screens().count(); } QRect ShellCorona::screenGeometry(int id) const { if (id >= m_views.count() || id < 0) { qWarning() << "requesting unexisting screen" << id; QScreen *s = qGuiApp->primaryScreen(); return s ? s->geometry() : QRect(); } return m_views[id]->geometry(); } QRegion ShellCorona::availableScreenRegion(int id) const { if (id >= m_views.count() || id < 0) { //each screen should have a view qWarning() << "requesting unexisting screen" << id; QScreen *s = qGuiApp->primaryScreen(); return s ? s->availableGeometry() : QRegion(); } DesktopView *view = m_views[id]; QRegion r = view->geometry(); foreach (const PanelView *v, m_panelViews) { if (v->isVisible() && view->screen() == v->screen() && v->visibilityMode() != PanelView::AutoHide) { //if the panel is being moved around, we still want to calculate it from the edge r -= v->geometryByDistance(0); } } return r; } QRect ShellCorona::availableScreenRect(int id) const { if (id >= m_views.count() || id < 0) { //each screen should have a view qWarning() << "requesting unexisting screen" << id; QScreen *s = qGuiApp->primaryScreen(); return s ? s->availableGeometry() : QRect(); } DesktopView *view = m_views[id]; QRect r = view->geometry(); foreach (PanelView *v, m_panelViews) { if (v->isVisible() && v->screen() == view->screen() && v->visibilityMode() != PanelView::AutoHide) { switch (v->location()) { case Plasma::Types::LeftEdge: r.setLeft(r.left() + v->thickness()); break; case Plasma::Types::RightEdge: r.setRight(r.right() - v->thickness()); break; case Plasma::Types::TopEdge: r.setTop(r.top() + v->thickness()); break; case Plasma::Types::BottomEdge: r.setBottom(r.bottom() - v->thickness()); default: break; } } } return r; } +QStringList ShellCorona::availableActivities() const +{ + return m_activityContainmentPlugins.keys(); +} + QScreen *ShellCorona::screenForId(int screenId) const { DesktopView *v = m_views.value(screenId); return v ? v->screen() : nullptr; } void ShellCorona::remove(DesktopView *desktopView) { removeView(m_views.indexOf(desktopView)); } PanelView *ShellCorona::panelView(Plasma::Containment *containment) const { return m_panelViews.value(containment); } ///// SLOTS QList ShellCorona::panelsForScreen(QScreen *screen) const { QList ret; foreach (PanelView *v, m_panelViews) { if (v->screen() == screen) { ret += v; } } return ret; } DesktopView* ShellCorona::desktopForScreen(QScreen* screen) const { foreach (DesktopView *v, m_views) { if (v->screen() == screen) { return v; } } return Q_NULLPTR; } void ShellCorona::removeView(int idx) { if (idx < 0 || idx >= m_views.count() || m_views.isEmpty()) { return; } bool panelsAltered = false; const QScreen *lastScreen = m_views.last()->screen(); QMutableHashIterator it(m_panelViews); while (it.hasNext()) { it.next(); PanelView *panelView = it.value(); if (panelView->screen() == lastScreen) { m_waitingPanels << panelView->containment(); it.remove(); delete panelView; panelsAltered = true; } } for (int i = m_views.count() - 2; i >= idx; --i) { QScreen *screen = m_views[i + 1]->screen(); QScreen *oldScreen = m_views[i]->screen(); const bool wasVisible = m_views[idx]->isVisible(); m_views[i]->setScreen(screen); if (wasVisible) { m_views[idx]->show(); //when destroying the screen, QScreen might have hidden the window } const QList panels = panelsForScreen(oldScreen); panelsAltered = panelsAltered || !panels.isEmpty(); foreach (PanelView *p, panels) { p->setScreen(screen); } } delete m_views.takeLast(); if (panelsAltered) { emit availableScreenRectChanged(); } } void ShellCorona::screenRemoved(QScreen* screen) { if (DesktopView* v = desktopForScreen(screen)) remove(v); m_reconsiderOutputsTimer.start(); m_redundantOutputs.remove(screen); } bool ShellCorona::isOutputRedundant(QScreen* screen) const { Q_ASSERT(screen); const QRect geometry = screen->geometry(); //FIXME: QScreen doesn't have any idea of "this qscreen is clone of this other one //so this ultra inefficient heuristic has to stay until we have a slightly better api foreach (QScreen* s, qGuiApp->screens()) { if (screen == s) { continue; } const QRect sGeometry = s->geometry(); if (sGeometry.contains(geometry, false) && sGeometry.width() > geometry.width() && sGeometry.height() > geometry.height()) { return true; } } return false; } void ShellCorona::reconsiderOutputs() { foreach (QScreen* screen, qGuiApp->screens()) { if (m_redundantOutputs.contains(screen)) { if (!isOutputRedundant(screen)) { // qDebug() << "not redundant anymore" << out; addOutput(screen); } } else if (isOutputRedundant(screen)) { qDebug() << "new redundant screen" << screen; if (DesktopView* v = desktopForScreen(screen)) remove(v); m_redundantOutputs.insert(screen); } // else // qDebug() << "fine screen" << out; } updateStruts(); CHECK_SCREEN_INVARIANTS } void ShellCorona::addOutput(QScreen* screen) { Q_ASSERT(screen); connect(screen, &QScreen::geometryChanged, &m_reconsiderOutputsTimer, static_cast(&QTimer::start), Qt::UniqueConnection); if (isOutputRedundant(screen)) { m_redundantOutputs.insert(screen); return; } else { m_redundantOutputs.remove(screen); } int insertPosition = 0; foreach (DesktopView *view, m_views) { if (outputLess(screen, view->screen())) { break; } insertPosition++; } QScreen* newScreen = insertScreen(screen, insertPosition); DesktopView *view = new DesktopView(this, newScreen); connect(view, &QQuickWindow::sceneGraphError, this, &ShellCorona::showOpenGLNotCompatibleWarning); Plasma::Containment *containment = createContainmentForActivity(m_activityController->currentActivity(), m_views.count()); Q_ASSERT(containment); QAction *removeAction = containment->actions()->action(QStringLiteral("remove")); if (removeAction) { removeAction->deleteLater(); } m_views.append(view); view->setContainment(containment); view->show(); Q_ASSERT(newScreen == view->screen()); //need to specifically call the reactToScreenChange, since when the screen is shown it's not yet //in the list. We still don't want to have an invisible view added. containment->reactToScreenChange(); //were there any panels for this screen before it popped up? if (!m_waitingPanels.isEmpty()) { m_waitingPanelsTimer.start(); } emit availableScreenRectChanged(); CHECK_SCREEN_INVARIANTS } QScreen* ShellCorona::insertScreen(QScreen *screen, int idx) { if (idx == m_views.count()) { return screen; } DesktopView *v = m_views[idx]; QScreen *oldScreen = v->screen(); v->setScreen(screen); Q_ASSERT(v->screen() == screen); foreach (PanelView *panel, m_panelViews) { if (panel->screen() == oldScreen) { panel->setScreen(screen); } } return insertScreen(oldScreen, idx+1); } Plasma::Containment *ShellCorona::createContainmentForActivity(const QString& activity, int screenNum) { QHash act = m_desktopContainments.value(activity); QHash::const_iterator it = act.constFind(screenNum); if (it != act.constEnd()) { return *it; } QString plugin = m_activityContainmentPlugins.value(activity); if (plugin.isEmpty()) { plugin = m_desktopDefaultsConfig.readEntry("Containment", "org.kde.desktopcontainment"); } Plasma::Containment *containment = containmentForScreen(screenNum, plugin, QVariantList()); Q_ASSERT(containment); if (containment) { containment->setActivity(activity); insertContainment(activity, screenNum, containment); } return containment; } void ShellCorona::createWaitingPanels() { QList stillWaitingPanels; foreach (Plasma::Containment *cont, m_waitingPanels) { //ignore non existing (yet?) screens int requestedScreen = cont->lastScreen(); if (requestedScreen < 0) { ++requestedScreen; } if (requestedScreen > (m_views.count() - 1)) { stillWaitingPanels << cont; continue; } Q_ASSERT(qBound(0, requestedScreen, m_views.count() - 1) == requestedScreen); QScreen *screen = m_views[requestedScreen]->screen(); PanelView* panel = new PanelView(this, screen); connect(panel, &QQuickWindow::sceneGraphError, this, &ShellCorona::showOpenGLNotCompatibleWarning); connect(panel, &QWindow::visibleChanged, this, &Plasma::Corona::availableScreenRectChanged); connect(panel, &PanelView::locationChanged, this, &Plasma::Corona::availableScreenRectChanged); connect(panel, &PanelView::visibilityModeChanged, this, &Plasma::Corona::availableScreenRectChanged); connect(panel, &PanelView::thicknessChanged, this, &Plasma::Corona::availableScreenRectChanged); m_panelViews[cont] = panel; panel->setContainment(cont); panel->show(); cont->reactToScreenChange(); connect(cont, &QObject::destroyed, this, &ShellCorona::panelContainmentDestroyed); } m_waitingPanels = stillWaitingPanels; emit availableScreenRectChanged(); } void ShellCorona::panelContainmentDestroyed(QObject *cont) { auto view = m_panelViews.take(static_cast(cont)); view->deleteLater(); emit availableScreenRectChanged(); } void ShellCorona::handleContainmentAdded(Plasma::Containment *c) { connect(c, &Plasma::Containment::showAddWidgetsInterface, this, &ShellCorona::toggleWidgetExplorer); // Why queued? this is usually triggered after a context menu closes // due to its sync,modal nature it may eat some mouse event from the scene // waiting a bit to create a new window, the dialog seems to reliably // avoid the eating of one click in the panel after the context menu is gone connect(c, &Plasma::Containment::appletAlternativesRequested, this, &ShellCorona::showAlternativesForApplet, Qt::QueuedConnection); connect(c, &Plasma::Containment::appletCreated, this, [this, c] (Plasma::Applet *applet) { executeSetupPlasmoidScript(c, applet); }); } void ShellCorona::executeSetupPlasmoidScript(Plasma::Containment *containment, Plasma::Applet *applet) { if (!applet->pluginInfo().isValid() || !containment->pluginInfo().isValid()) { return; } const QString scriptFile = m_lookAndFeelPackage.filePath("plasmoidsetupscripts", applet->pluginInfo().pluginName() + ".js"); if (scriptFile.isEmpty()) { return; } WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); QFile file(scriptFile); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << i18n("Unable to load script file: %1", scriptFile); return; } QString script = file.readAll(); if (script.isEmpty()) { // qDebug() << "script is empty"; return; } scriptEngine.globalObject().setProperty(QStringLiteral("applet"), scriptEngine.wrap(applet)); scriptEngine.globalObject().setProperty(QStringLiteral("containment"), scriptEngine.wrap(containment)); scriptEngine.evaluateScript(script, scriptFile); } void ShellCorona::toggleWidgetExplorer() { const QPoint cursorPos = QCursor::pos(); foreach (DesktopView *view, m_views) { if (view->screen()->geometry().contains(cursorPos)) { //The view QML has to provide something to display the widget explorer view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleWidgetExplorer", Q_ARG(QVariant, QVariant::fromValue(sender()))); return; } } } void ShellCorona::toggleActivityManager() { const QPoint cursorPos = QCursor::pos(); foreach (DesktopView *view, m_views) { if (view->screen()->geometry().contains(cursorPos)) { //The view QML has to provide something to display the activity explorer view->rootObject()->metaObject()->invokeMethod(view->rootObject(), "toggleActivityManager", Qt::QueuedConnection); return; } } } void ShellCorona::syncAppConfig() { applicationConfig()->sync(); } void ShellCorona::setDashboardShown(bool show) { KWindowSystem::setShowingDesktop(show); } void ShellCorona::toggleDashboard() { setDashboardShown(!KWindowSystem::showingDesktop()); } void ShellCorona::loadInteractiveConsole() { if (KSharedConfig::openConfig()->isImmutable() || !KAuthorized::authorize(QStringLiteral("plasma-desktop/scripting_console"))) { delete m_interactiveConsole; m_interactiveConsole = 0; return; } if (!m_interactiveConsole) { const QString consoleQML = package().filePath("interactiveconsole"); if (consoleQML.isEmpty()) { return; } m_interactiveConsole = new KDeclarative::QmlObject(this); m_interactiveConsole->setInitializationDelayed(true); m_interactiveConsole->setSource(QUrl::fromLocalFile(consoleQML)); QObject *engine = new WorkspaceScripting::ScriptEngine(this, m_interactiveConsole); m_interactiveConsole->rootContext()->setContextProperty(QStringLiteral("scriptEngine"), engine); m_interactiveConsole->completeInitialization(); if (m_interactiveConsole->rootObject()) { connect(m_interactiveConsole->rootObject(), SIGNAL(visibleChanged(bool)), this, SLOT(interactiveConsoleVisibilityChanged(bool))); } } } void ShellCorona::showInteractiveConsole() { loadInteractiveConsole(); if (m_interactiveConsole && m_interactiveConsole->rootObject()) { m_interactiveConsole->rootObject()->setProperty("mode", "desktop"); m_interactiveConsole->rootObject()->setProperty("visible", true); } } void ShellCorona::loadScriptInInteractiveConsole(const QString &script) { showInteractiveConsole(); if (m_interactiveConsole) { m_interactiveConsole->rootObject()->setProperty("script", script); } } void ShellCorona::showInteractiveKWinConsole() { loadInteractiveConsole(); if (m_interactiveConsole && m_interactiveConsole->rootObject()) { m_interactiveConsole->rootObject()->setProperty("mode", "windowmanager"); m_interactiveConsole->rootObject()->setProperty("visible", true); } } void ShellCorona::loadKWinScriptInInteractiveConsole(const QString &script) { showInteractiveKWinConsole(); if (m_interactiveConsole) { m_interactiveConsole->rootObject()->setProperty("script", script); } } void ShellCorona::evaluateScript(const QString &script) { if (immutability() != Plasma::Types::Mutable) { if (calledFromDBus()) { sendErrorReply(QDBusError::Failed, QStringLiteral("Widgets are locked")); } return; } WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); scriptEngine.evaluateScript(script); if (scriptEngine.hasUncaughtException() && calledFromDBus()) { sendErrorReply(QDBusError::Failed, scriptEngine.uncaughtException().toString()); } } void ShellCorona::interactiveConsoleVisibilityChanged(bool visible) { if (!visible) { m_interactiveConsole->deleteLater(); m_interactiveConsole = nullptr; } } void ShellCorona::checkActivities() { KActivities::Controller::ServiceStatus status = m_activityController->serviceStatus(); //qDebug() << "$%$%$#%$%$%Status:" << status; if (status != KActivities::Controller::Running) { //panic and give up - better than causing a mess qDebug() << "ShellCorona::checkActivities is called whilst activity daemon is still connecting"; return; } QStringList existingActivities = m_activityController->activities(); foreach (const QString &id, existingActivities) { activityAdded(id); } // Checking whether the result we got is valid. Just in case. Q_ASSERT_X(!existingActivities.isEmpty(), "isEmpty", "There are no activities, and the service is running"); Q_ASSERT_X(existingActivities[0] != QStringLiteral("00000000-0000-0000-0000-000000000000"), "null uuid", "There is a nulluuid activity present"); // Killing the unassigned containments foreach (Plasma::Containment *cont, containments()) { if ((cont->containmentType() == Plasma::Types::DesktopContainment || cont->containmentType() == Plasma::Types::CustomContainment) && !existingActivities.contains(cont->activity())) { cont->destroy(); } } } void ShellCorona::currentActivityChanged(const QString &newActivity) { // qDebug() << "Activity changed:" << newActivity; for (int i = 0; i < m_views.count(); ++i) { Plasma::Containment *c = createContainmentForActivity(newActivity, i); QAction *removeAction = c->actions()->action(QStringLiteral("remove")); if (removeAction) { removeAction->deleteLater(); } m_views[i]->setContainment(c); } } void ShellCorona::activityAdded(const QString &id) { //TODO more sanity checks if (m_activityContainmentPlugins.contains(id)) { qWarning() << "Activity added twice" << id; return; } const QString plugin = m_desktopDefaultsConfig.readEntry("Containment", "org.kde.desktopcontainment"); m_activityContainmentPlugins.insert(id, plugin); } void ShellCorona::activityRemoved(const QString &id) { m_activityContainmentPlugins.remove(id); } void ShellCorona::insertActivity(const QString &id, const QString &plugin) { + activityAdded(id); + m_activityContainmentPlugins.insert(id, plugin); for (int i = 0; i < m_views.count(); ++i) { Plasma::Containment *c = createContainmentForActivity(id, i); if (c) { c->config().writeEntry("lastScreen", i); } } } Plasma::Containment *ShellCorona::setContainmentTypeForScreen(int screen, const QString &plugin) { Plasma::Containment *oldContainment = containmentForScreen(screen); //no valid containment in given screen, giving up if (!oldContainment) { return 0; } if (plugin.isEmpty()) { return oldContainment; } DesktopView *view = 0; foreach (DesktopView *v, m_views) { if (v->containment() == oldContainment) { view = v; break; } } //no view? give up if (!view) { return oldContainment; } //create a new containment Plasma::Containment *newContainment = createContainmentDelayed(plugin); //if creation failed or invalid plugin, give up if (!newContainment) { return oldContainment; } else if (!newContainment->pluginInfo().isValid()) { newContainment->deleteLater(); return oldContainment; } newContainment->setWallpaper(oldContainment->wallpaper()); //At this point we have a valid new containment from plugin and a view //copy all configuration groups (excluded applets) KConfigGroup oldCg = oldContainment->config(); //newCg *HAS* to be from a KSharedConfig, because some KConfigSkeleton will need to be synced //this makes the configscheme work KConfigGroup newCg(KSharedConfig::openConfig(oldCg.config()->name()), "Containments"); newCg = KConfigGroup(&newCg, QString::number(newContainment->id())); //this makes containment->config() work, is a separate thing from its configscheme KConfigGroup newCg2 = newContainment->config(); foreach (const QString &group, oldCg.groupList()) { if (group != QLatin1String("Applets")) { KConfigGroup subGroup(&oldCg, group); KConfigGroup newSubGroup(&newCg, group); subGroup.copyTo(&newSubGroup); KConfigGroup newSubGroup2(&newCg2, group); subGroup.copyTo(&newSubGroup2); } } newContainment->init(); newCg.writeEntry("activityId", oldContainment->activity()); newContainment->restore(newCg); newContainment->updateConstraints(Plasma::Types::StartupCompletedConstraint); newContainment->save(newCg); requestConfigSync(); newContainment->flushPendingConstraintsEvents(); emit containmentAdded(newContainment); //Move the applets foreach (Plasma::Applet *applet, oldContainment->applets()) { newContainment->addApplet(applet); } //remove the "remove" action QAction *removeAction = newContainment->actions()->action(QStringLiteral("remove")); if (removeAction) { removeAction->deleteLater(); } view->setContainment(newContainment); newContainment->setActivity(oldContainment->activity()); m_desktopContainments.remove(oldContainment->activity()); insertContainment(oldContainment->activity(), screen, newContainment); //removing the focus from the item that is going to be destroyed //fixes a crash //delayout the destruction of the old containment fixes another crash view->rootObject()->setFocus(true, Qt::MouseFocusReason); QTimer::singleShot(2500, oldContainment, &Plasma::Applet::destroy); emit availableScreenRectChanged(); return newContainment; } void ShellCorona::checkAddPanelAction(const QStringList &sycocaChanges) { if (!sycocaChanges.isEmpty() && !sycocaChanges.contains(QStringLiteral("services"))) { return; } delete m_addPanelAction; m_addPanelAction = 0; delete m_addPanelsMenu; m_addPanelsMenu = 0; KPluginInfo::List panelContainmentPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); auto filter = [](const KPluginMetaData &md) -> bool { return md.value(QStringLiteral("X-Plasma-Shell")) == qApp->applicationName() && md.value(QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); }; QList templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); if (panelContainmentPlugins.count() + templates.count() == 1) { m_addPanelAction = new QAction(i18n("Add Panel"), this); m_addPanelAction->setData(Plasma::Types::AddAction); connect(m_addPanelAction, SIGNAL(triggered(bool)), this, SLOT(addPanel())); } else if (!panelContainmentPlugins.isEmpty()) { m_addPanelsMenu = new QMenu; m_addPanelAction = m_addPanelsMenu->menuAction(); m_addPanelAction->setText(i18n("Add Panel")); m_addPanelAction->setData(Plasma::Types::AddAction); connect(m_addPanelsMenu, &QMenu::aboutToShow, this, &ShellCorona::populateAddPanelsMenu); connect(m_addPanelsMenu, SIGNAL(triggered(QAction*)), this, SLOT(addPanel(QAction*))); } if (m_addPanelAction) { m_addPanelAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); actions()->addAction(QStringLiteral("add panel"), m_addPanelAction); } } void ShellCorona::populateAddPanelsMenu() { m_addPanelsMenu->clear(); const KPluginInfo emptyInfo; KPluginInfo::List panelContainmentPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); QMap > sorted; foreach (const KPluginInfo &plugin, panelContainmentPlugins) { sorted.insert(plugin.name(), qMakePair(plugin, KPluginMetaData())); } auto filter = [](const KPluginMetaData &md) -> bool { return md.value(QStringLiteral("X-Plasma-Shell")) == qApp->applicationName() && md.value(QStringLiteral("X-Plasma-ContainmentCategories")).contains(QStringLiteral("panel")); }; QList templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), filter); for (auto tpl : templates) { sorted.insert(tpl.name(), qMakePair(emptyInfo, tpl)); } QMapIterator > it(sorted); KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate")); while (it.hasNext()) { it.next(); QPair pair = it.value(); if (pair.first.isValid()) { KPluginInfo plugin = pair.first; QAction *action = m_addPanelsMenu->addAction(i18n("Empty %1", plugin.name())); if (!plugin.icon().isEmpty()) { action->setIcon(QIcon::fromTheme(plugin.icon())); } action->setData(plugin.pluginName()); } else { //FIXME: proper names KPluginInfo info(pair.second); const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, package.defaultPackageRoot() + info.pluginName() + "/metadata.desktop"); if (!path.isEmpty()) { package.setPath(info.pluginName()); const QString scriptFile = package.filePath("mainscript"); if (!scriptFile.isEmpty()) { QAction *action = m_addPanelsMenu->addAction(info.name()); action->setData(QStringLiteral("plasma-desktop-template:%1").arg(scriptFile)); } } } } } void ShellCorona::addPanel() { KPluginInfo::List panelPlugins = Plasma::PluginLoader::listContainmentsOfType(QStringLiteral("Panel")); if (!panelPlugins.isEmpty()) { addPanel(panelPlugins.first().pluginName()); } } void ShellCorona::addPanel(QAction *action) { const QString plugin = action->data().toString(); if (plugin.startsWith(QLatin1String("plasma-desktop-template:"))) { WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); const QString scriptFile = plugin.right(plugin.length() - qstrlen("plasma-desktop-template:")); QFile file(scriptFile); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << i18n("Unable to load script file: %1", scriptFile); return; } QString script = file.readAll(); if (script.isEmpty()) { // qDebug() << "script is empty"; return; } scriptEngine.evaluateScript(script, scriptFile); } else if (!plugin.isEmpty()) { addPanel(plugin); } } Plasma::Containment *ShellCorona::addPanel(const QString &plugin) { Plasma::Containment *panel = createContainment(plugin); if (!panel) { return 0; } QList availableLocations; availableLocations << Plasma::Types::LeftEdge << Plasma::Types::TopEdge << Plasma::Types::RightEdge << Plasma::Types::BottomEdge; foreach (const Plasma::Containment *cont, m_panelViews.keys()) { availableLocations.removeAll(cont->location()); } Plasma::Types::Location loc; if (availableLocations.isEmpty()) { loc = Plasma::Types::TopEdge; } else { loc = availableLocations.first(); } panel->setLocation(loc); switch (loc) { case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: panel->setFormFactor(Plasma::Types::Vertical); break; default: panel->setFormFactor(Plasma::Types::Horizontal); break; } Q_ASSERT(panel); m_waitingPanels << panel; createWaitingPanels(); const QPoint cursorPos(QCursor::pos()); foreach (QScreen *screen, QGuiApplication::screens()) { //m_panelViews.contains(panel) == false iff addPanel is executed in a startup script if (screen->geometry().contains(cursorPos) && m_panelViews.contains(panel)) { m_panelViews[panel]->setScreen(screen); break; } } return panel; } int ShellCorona::screenForContainment(const Plasma::Containment *containment) const { //case in which this containment is child of an applet, hello systray :) if (Plasma::Applet *parentApplet = qobject_cast(containment->parent())) { if (Plasma::Containment* cont = parentApplet->containment()) { return screenForContainment(cont); } else { return -1; } } //if the desktop views already exist, base the decision upon them for (int i = 0; i < m_views.count(); i++) { if (m_views[i]->containment() == containment && containment->activity() == m_activityController->currentActivity()) { return i; } } //if the panel views already exist, base upon them PanelView *view = m_panelViews.value(containment); if (view) { QScreen *screen = view->screen(); for (int i = 0; i < m_views.count(); i++) { if (m_views[i]->screen() == screen) { return i; } } } //Failed? fallback on lastScreen() //lastScreen() is the correct screen for panels //It is also correct for desktops *that have the correct activity()* //a containment with lastScreen() == 0 but another activity, //won't be associated to a screen // qDebug() << "ShellCorona screenForContainment: " << containment << " Last screen is " << containment->lastScreen(); for (int i = 0, count = qGuiApp->screens().count(); ilastScreen() == i && (containment->activity() == m_activityController->currentActivity() || containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment)) { return i; } } return -1; } void ShellCorona::nextActivity() { const QStringList list = m_activityController->activities(KActivities::Info::Running); if (list.isEmpty()) { return; } const int start = list.indexOf(m_activityController->currentActivity()); const int i = (start + 1) % list.size(); m_activityController->setCurrentActivity(list.at(i)); } void ShellCorona::previousActivity() { const QStringList list = m_activityController->activities(KActivities::Info::Running); if (list.isEmpty()) { return; } const int start = list.indexOf(m_activityController->currentActivity()); int i = start - 1; if(i < 0) { i = list.size() - 1; } m_activityController->setCurrentActivity(list.at(i)); } void ShellCorona::stopCurrentActivity() { const QStringList list = m_activityController->activities(KActivities::Info::Running); if (list.isEmpty()) { return; } m_activityController->stopActivity(m_activityController->currentActivity()); } void ShellCorona::insertContainment(const QString &activity, int screenNum, Plasma::Containment *containment) { Plasma::Containment *cont = m_desktopContainments.value(activity).value(screenNum); if (containment == cont) { return; } Q_ASSERT(!m_desktopContainments.value(activity).values().contains(containment)); if (cont) { disconnect(cont, SIGNAL(destroyed(QObject*)), this, SLOT(desktopContainmentDestroyed(QObject*))); cont->destroy(); } m_desktopContainments[activity][screenNum] = containment; //when a containment gets deleted update our map of containments connect(containment, SIGNAL(destroyed(QObject*)), this, SLOT(desktopContainmentDestroyed(QObject*))); } void ShellCorona::desktopContainmentDestroyed(QObject *obj) { // when QObject::destroyed arrives, ~Plasma::Containment has run, // members of Containment are not accessible anymore, // so keep ugly bookeeping by hand auto containment = static_cast(obj); for (auto a : m_desktopContainments) { QMutableHashIterator it(a); while (it.hasNext()) { it.next(); if (it.value() == containment) { it.remove(); return; } } } } void ShellCorona::showOpenGLNotCompatibleWarning() { static bool s_multipleInvokations = false; if (s_multipleInvokations) { return; } s_multipleInvokations = true; QCoreApplication::setAttribute(Qt::AA_ForceRasterWidgets); QMessageBox::critical(nullptr, i18n("Plasma Failed To Start"), i18n("Plasma is unable to start as it could not correctly use OpenGL 2.\n Please check that your graphic drivers are set up correctly.")); qCritical("Open GL context could not be created"); // this doesn't work and I have no idea why. QCoreApplication::exit(1); } void ShellCorona::setupWaylandIntegration() { if (!QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) { return; } using namespace KWayland::Client; ConnectionThread *connection = ConnectionThread::fromApplication(this); if (!connection) { return; } Registry *registry = new Registry(this); registry->create(connection); connect(registry, &Registry::plasmaShellAnnounced, this, [this, registry] (quint32 name, quint32 version) { m_waylandPlasmaShell = registry->createPlasmaShell(name, version, this); } ); registry->setup(); } KWayland::Client::PlasmaShell *ShellCorona::waylandPlasmaShellInterface() const { return m_waylandPlasmaShell; } void ShellCorona::updateStruts() { foreach(PanelView* view, m_panelViews) { view->updateStruts(); } } // Desktop corona handler #include "moc_shellcorona.cpp" diff --git a/shell/shellcorona.h b/shell/shellcorona.h index 15a106835..33ffefe66 100644 --- a/shell/shellcorona.h +++ b/shell/shellcorona.h @@ -1,233 +1,235 @@ /* * Copyright 2008 Aaron Seigo * Copyright 2013 Sebastian Kügler * Copyright 2013 Ivan Cukic * Copyright 2013 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, 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 Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SHELLCORONA_H #define SHELLCORONA_H #include "plasma/corona.h" #include #include #include #include #include class DesktopView; class PanelView; class QMenu; class QScreen; namespace KActivities { class Controller; } // namespace KActivities namespace KDeclarative { class QmlObject; } // namespace KDeclarative namespace KScreen { class Output; } // namespace KScreen namespace Plasma { class Applet; } // namespace Plasma namespace KWayland { namespace Client { class PlasmaShell; } } class ShellCorona : public Plasma::Corona, QDBusContext { Q_OBJECT Q_PROPERTY(QString shell READ shell WRITE setShell) Q_PROPERTY(int numScreens READ numScreens) Q_CLASSINFO("D-Bus Interface", "org.kde.PlasmaShell") public: explicit ShellCorona(QObject *parent = 0); ~ShellCorona() override; KPackage::Package lookAndFeelPackage(); /** * Where to save global configuration that doesn't have anything to do with the scene (e.g. views) */ KSharedConfig::Ptr applicationConfig(); int numScreens() const override; Q_INVOKABLE QRect screenGeometry(int id) const override; Q_INVOKABLE QRegion availableScreenRegion(int id) const override; Q_INVOKABLE QRect availableScreenRect(int id) const override; + Q_INVOKABLE QStringList availableActivities() const; + PanelView *panelView(Plasma::Containment *containment) const; //Those two are a bit of an hack but are just for desktop scripting void insertActivity(const QString &id, const QString &plugin); Plasma::Containment *setContainmentTypeForScreen(int screen, const QString &plugin); QScreen *screenForId(int screenId) const; void remove(DesktopView *desktopView); /** * @returns a new containment associated with the specified @p activity and @p screen. */ Plasma::Containment *createContainmentForActivity(const QString &activity, int screenNum); KWayland::Client::PlasmaShell *waylandPlasmaShellInterface() const; protected: bool eventFilter(QObject *watched, QEvent *event) override; public Q_SLOTS: /** * Request saving applicationConfig on disk, it's event compressed, not immediate */ void requestApplicationConfigSync(); /** * Sets the shell that the corona should display */ void setShell(const QString &shell); /** * Gets the currently shown shell */ QString shell() const; ///DBUS methods void toggleDashboard(); void setDashboardShown(bool show); void loadInteractiveConsole(); void showInteractiveConsole(); void loadScriptInInteractiveConsole(const QString &script); void showInteractiveKWinConsole(); void loadKWinScriptInInteractiveConsole(const QString &script); void toggleActivityManager(); void evaluateScript(const QString &string); Plasma::Containment *addPanel(const QString &plugin); void nextActivity(); void previousActivity(); void stopCurrentActivity(); protected Q_SLOTS: /** * Loads the layout and performs the needed checks */ void load(); /** * Unloads everything */ void unload(); /** * Loads the default (system wide) layout for this user **/ void loadDefaultLayout() override; /** * Execute any update script */ void processUpdateScripts(); int screenForContainment(const Plasma::Containment *containment) const override; void showAlternativesForApplet(Plasma::Applet *applet); private Q_SLOTS: void createWaitingPanels(); void handleContainmentAdded(Plasma::Containment *c); void toggleWidgetExplorer(); void syncAppConfig(); void checkActivities(); void currentActivityChanged(const QString &newActivity); void activityAdded(const QString &id); void activityRemoved(const QString &id); void checkAddPanelAction(const QStringList &sycocaChanges = QStringList()); void addPanel(); void addPanel(QAction *action); void populateAddPanelsMenu(); void addOutput(QScreen* screen); void primaryOutputChanged(); void panelContainmentDestroyed(QObject* cont); void desktopContainmentDestroyed(QObject*); void showOpenGLNotCompatibleWarning(); void alternativesVisibilityChanged(bool visible); void interactiveConsoleVisibilityChanged(bool visible); void screenRemoved(QScreen* screen); private: void updateStruts(); QScreen *insertScreen(QScreen *screen, int idx); void removeView(int idx); bool isOutputRedundant(QScreen* screen) const; void reconsiderOutputs(); QList panelsForScreen(QScreen *screen) const; DesktopView* desktopForScreen(QScreen *screen) const; void setupWaylandIntegration(); void executeSetupPlasmoidScript(Plasma::Containment *containment, Plasma::Applet *applet); #ifndef NDEBUG void screenInvariants() const; #endif void insertContainment(const QString &activity, int screenNum, Plasma::Containment *containment); QString m_shell; QList m_views; KActivities::Controller *m_activityController; QHash m_panelViews; KConfigGroup m_desktopDefaultsConfig; QList m_waitingPanels; QHash m_activityContainmentPlugins; QHash > m_desktopContainments; QAction *m_addPanelAction; QMenu *m_addPanelsMenu; KPackage::Package m_lookAndFeelPackage; QSet m_redundantOutputs; QList m_alternativesObjects; KDeclarative::QmlObject *m_interactiveConsole; QTimer m_waitingPanelsTimer; QTimer m_appConfigSyncTimer; QTimer m_reconsiderOutputsTimer; KWayland::Client::PlasmaShell *m_waylandPlasmaShell; }; #endif // SHELLCORONA_H diff --git a/shell/standaloneappcorona.cpp b/shell/standaloneappcorona.cpp index 7746eaab8..2d6e1d78e 100644 --- a/shell/standaloneappcorona.cpp +++ b/shell/standaloneappcorona.cpp @@ -1,239 +1,244 @@ /* * Copyright 2014 Bhushan Shah * Copyright 2014 Marco Martin * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "standaloneappcorona.h" #include "desktopview.h" #include #include #include #include #include #include #include #include "scripting/scriptengine.h" StandaloneAppCorona::StandaloneAppCorona(const QString &coronaPlugin, QObject *parent) : Plasma::Corona(parent), m_coronaPlugin(coronaPlugin), m_activityConsumer(new KActivities::Consumer(this)), m_view(0) { qmlRegisterUncreatableType("org.kde.plasma.shell", 2, 0, "Desktop", QStringLiteral("It is not possible to create objects of type Desktop")); qmlRegisterUncreatableType("org.kde.plasma.shell", 2, 0, "Panel", QStringLiteral("It is not possible to create objects of type Panel")); KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Shell")); package.setPath(m_coronaPlugin); package.setAllowExternalPaths(true); setKPackage(package); Plasma::Theme theme; theme.setUseGlobalSettings(false); KConfigGroup lnfCfg = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Theme"); theme.setThemeName(lnfCfg.readEntry("name", "default")); m_desktopDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(package.filePath("defaults")), "Desktop"); m_view = new DesktopView(this); connect(m_activityConsumer, &KActivities::Consumer::currentActivityChanged, this, &StandaloneAppCorona::currentActivityChanged); connect(m_activityConsumer, &KActivities::Consumer::activityAdded, this, &StandaloneAppCorona::activityAdded); connect(m_activityConsumer, &KActivities::Consumer::activityRemoved, this, &StandaloneAppCorona::activityRemoved); connect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &StandaloneAppCorona::load); } StandaloneAppCorona::~StandaloneAppCorona() { delete m_view; } QRect StandaloneAppCorona::screenGeometry(int id) const { Q_UNUSED(id); if(m_view) { return m_view->geometry(); } else { return QRect(); } } void StandaloneAppCorona::load() { loadLayout("plasma-" + m_coronaPlugin + "-appletsrc"); bool found = false; for (auto c : containments()) { if (c->containmentType() == Plasma::Types::DesktopContainment || c->containmentType() == Plasma::Types::CustomContainment) { found = true; break; } } if (!found) { qDebug() << "Loading default layout"; loadDefaultLayout(); saveLayout("plasma-" + m_coronaPlugin + "-appletsrc"); } for (auto c : containments()) { qDebug() << "containment found"; if (c->containmentType() == Plasma::Types::DesktopContainment || c->containmentType() == Plasma::Types::CustomContainment) { QAction *removeAction = c->actions()->action(QStringLiteral("remove")); if(removeAction) { removeAction->deleteLater(); } m_view->setContainment(c); m_view->show(); connect(m_view, &QWindow::visibleChanged, [=](bool visible){ if (!visible) { deleteLater(); } }); break; } } } void StandaloneAppCorona::loadDefaultLayout() { const QString script = package().filePath("defaultlayout"); QFile file(script); if (file.open(QIODevice::ReadOnly | QIODevice::Text) ) { QString code = file.readAll(); qDebug() << "evaluating startup script:" << script; WorkspaceScripting::ScriptEngine scriptEngine(this); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::printError, this, [](const QString &msg) { qWarning() << msg; }); connect(&scriptEngine, &WorkspaceScripting::ScriptEngine::print, this, [](const QString &msg) { qDebug() << msg; }); scriptEngine.evaluateScript(code); } } Plasma::Containment *StandaloneAppCorona::createContainmentForActivity(const QString& activity, int screenNum) { for (Plasma::Containment *cont : containments()) { if (cont->activity() == activity && (cont->containmentType() == Plasma::Types::DesktopContainment || cont->containmentType() == Plasma::Types::CustomContainment)) { return cont; } } Plasma::Containment *containment = containmentForScreen(screenNum, m_desktopDefaultsConfig.readEntry("Containment", "org.kde.desktopcontainment"), QVariantList()); Q_ASSERT(containment); if (containment) { containment->setActivity(activity); } return containment; } void StandaloneAppCorona::activityAdded(const QString &id) { //TODO more sanity checks if (m_activityContainmentPlugins.contains(id)) { qWarning() << "Activity added twice" << id; return; } m_activityContainmentPlugins.insert(id, QString()); } void StandaloneAppCorona::activityRemoved(const QString &id) { m_activityContainmentPlugins.remove(id); } void StandaloneAppCorona::currentActivityChanged(const QString &newActivity) { //qDebug() << "Activity changed:" << newActivity; if (containments().isEmpty()) { return; } Plasma::Containment *c = createContainmentForActivity(newActivity, 0); connect(c, &Plasma::Containment::showAddWidgetsInterface, this, &StandaloneAppCorona::toggleWidgetExplorer); QAction *removeAction = c->actions()->action(QStringLiteral("remove")); if (removeAction) { removeAction->deleteLater(); } m_view->setContainment(c); } void StandaloneAppCorona::toggleWidgetExplorer() { //The view QML has to provide something to display the widget explorer m_view->rootObject()->metaObject()->invokeMethod(m_view->rootObject(), "toggleWidgetExplorer", Q_ARG(QVariant, QVariant::fromValue(sender()))); return; } +QStringList StandaloneAppCorona::availableActivities() const +{ + return m_activityContainmentPlugins.keys(); +} + void StandaloneAppCorona::insertActivity(const QString &id, const QString &plugin) { m_activityContainmentPlugins.insert(id, plugin); Plasma::Containment *c = createContainmentForActivity(id, 0); if (c) { c->config().writeEntry("lastScreen", 0); } } Plasma::Containment *StandaloneAppCorona::addPanel(const QString &plugin) { //this creates a panel that wwill be used for nothing //it's needed by the scriptengine to create //a corona useful also when launched in fullshell Plasma::Containment *panel = createContainment(plugin); if (!panel) { return 0; } return panel; } int StandaloneAppCorona::screenForContainment(const Plasma::Containment *containment) const { //this simple corona doesn't have multiscreen support if (containment->containmentType() != Plasma::Types::PanelContainment && containment->containmentType() != Plasma::Types::CustomPanelContainment) { if (containment->activity() != m_activityConsumer->currentActivity()) { return -1; } } return 0; } diff --git a/shell/standaloneappcorona.h b/shell/standaloneappcorona.h index cc83cf9ef..65644a92c 100644 --- a/shell/standaloneappcorona.h +++ b/shell/standaloneappcorona.h @@ -1,68 +1,70 @@ /* * Copyright 2014 Bhushan Shah * Copyright 2014 Marco Martin * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 SIMPLESHELLCORONA_H #define SIMPLESHELLCORONA_H #include #include "desktopview.h" namespace KActivities { class Consumer; } class StandaloneAppCorona : public Plasma::Corona { Q_OBJECT public: explicit StandaloneAppCorona(const QString &coronaPlugin, QObject * parent = 0); ~StandaloneAppCorona() override; QRect screenGeometry(int id) const override; void loadDefaultLayout() override; Plasma::Containment *createContainmentForActivity(const QString& activity, int screenNum); void insertActivity(const QString &id, const QString &plugin); Plasma::Containment *addPanel(const QString &plugin); + Q_INVOKABLE QStringList availableActivities() const; + public Q_SLOTS: void load(); void currentActivityChanged(const QString &newActivity); void activityAdded(const QString &id); void activityRemoved(const QString &id); void toggleWidgetExplorer(); protected Q_SLOTS: int screenForContainment(const Plasma::Containment *containment) const override; private: QString m_coronaPlugin; KActivities::Consumer *m_activityConsumer; KConfigGroup m_desktopDefaultsConfig; DesktopView *m_view; QHash m_activityContainmentPlugins; }; #endif