diff options
45 files changed, 1383 insertions, 66 deletions
diff --git a/tradeshow/iot-sensortag/mockdataproviderpool.cpp b/tradeshow/iot-sensortag/mockdataproviderpool.cpp index 5f37737..6592e1b 100644 --- a/tradeshow/iot-sensortag/mockdataproviderpool.cpp +++ b/tradeshow/iot-sensortag/mockdataproviderpool.cpp @@ -69,7 +69,7 @@ void MockDataProviderPool::startScanning() p->setTagType(SensorTagDataProvider::Humidity | SensorTagDataProvider::Light | SensorTagDataProvider::Accelometer); m_dataProviders.push_back(p); p = new MockDataProvider("MOCK_PROVIDER_3", this); - p->setTagType(SensorTagDataProvider::Magnetometer | SensorTagDataProvider::AirPressure); + p->setTagType(SensorTagDataProvider::Magnetometer | SensorTagDataProvider::AirPressure | SensorTagDataProvider::Altitude); m_dataProviders.push_back(p); for (int i=0; i < m_dataProviders.length(); i++) emit providerConnected(p->id()); diff --git a/wayland/democompositor/.gitignore b/wayland/democompositor/.gitignore new file mode 100644 index 0000000..acd6488 --- /dev/null +++ b/wayland/democompositor/.gitignore @@ -0,0 +1,11 @@ +.obj/ +.moc/ +.rcc/ +*.sw? +Makefile +target_wrapper.sh + +Makefile.democompositor +democompositor +tests/applist/tst_applistmodel +tests/apps/tst_apps diff --git a/wayland/democompositor/apps/README b/wayland/democompositor/apps/README new file mode 100644 index 0000000..2f6fd77 --- /dev/null +++ b/wayland/democompositor/apps/README @@ -0,0 +1,22 @@ +A directory holding a list of applications. There is one JSON file +per launchable application. The content is inspired by the "Desktop +Entry Specification" of freedesktop.org. + +Application files need to be installed into any of the paths that +will be listed for QStandardPaths::DataLocation/apps. In the +case of the democompositor this can be +/usr/share/democompositor/apps. + +The content of the files must follow: + +Name Description Required JSON type +Type Type of entry. Currently only Yes String + Application is supported. +Version The version of the specification + addressed. Currently version 1 Yes Number +Icon The icon to use for display Yes String +Name A user displayable name Yes String +Id Reverse-DNS as unique application Id Yes String +Exec Application to execute to launch Yes String +Path The directory to search for the app No String +X-* Extensions to the specification No Any diff --git a/wayland/democompositor/apps/appentry.cpp b/wayland/democompositor/apps/appentry.cpp new file mode 100644 index 0000000..ce94cad --- /dev/null +++ b/wayland/democompositor/apps/appentry.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "appentry.h" + +AppEntry AppEntry::empty() +{ + QString empty; + return AppEntry{empty, empty, empty, empty, empty, empty, QVariantMap()}; +} diff --git a/wayland/democompositor/apps/appentry.h b/wayland/democompositor/apps/appentry.h new file mode 100644 index 0000000..bddc552 --- /dev/null +++ b/wayland/democompositor/apps/appentry.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <QtCore/QObject> +#include <QtCore/QString> +#include <QtCore/QVariantMap> + +/** + * A const representatation of an application entry. Members include + * executable name, path, icon and other information in the future. + */ +class AppEntry { + Q_GADGET + Q_PROPERTY(QString iconName MEMBER iconName CONSTANT) + Q_PROPERTY(QString appName MEMBER appName CONSTANT) + Q_PROPERTY(QString appId MEMBER appId CONSTANT) + Q_PROPERTY(QString executableName MEMBER executableName CONSTANT) + Q_PROPERTY(QString executablePath MEMBER executablePath CONSTANT) + Q_PROPERTY(QString sourceFileName MEMBER sourceFileName CONSTANT) + Q_PROPERTY(QVariantMap extensions MEMBER extensions CONSTANT) +public: + + QString iconName; + QString appName; + QString appId; + QString executableName; + QString executablePath; + QString sourceFileName; + QVariantMap extensions; + + static AppEntry empty(); +}; + +Q_DECLARE_METATYPE(AppEntry) diff --git a/wayland/democompositor/apps/applistmodel.cpp b/wayland/democompositor/apps/applistmodel.cpp new file mode 100644 index 0000000..dfcc378 --- /dev/null +++ b/wayland/democompositor/apps/applistmodel.cpp @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "applistmodel.h" +#include "appparser.h" +#include "applog.h" + +#include <QtCore/QDirIterator> +#include <QtCore/QFileSystemWatcher> + +static QHash<int, QByteArray> modelRoles() +{ + QHash<int, QByteArray> roles; + roles[AppListModel::App] = "appEntry"; + roles[AppListModel::IconName] = "iconName"; + roles[AppListModel::ApplicationName] = "applicationName"; + roles[AppListModel::ApplicationId] = "applicationId"; + roles[AppListModel::ExeuctableName] = "executableName"; + roles[AppListModel::ExecutablePath] = "executablePath"; + roles[AppListModel::SourceFileName] = "sourceFileName"; + return roles; +} + +QHash<int, QByteArray> AppListModel::m_roles = modelRoles(); + +int AppListModel::rowCount(const QModelIndex& index) const +{ + if (index.isValid()) + return 0; + return m_rows.count(); +} + +QVariant AppListModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || index.parent().isValid()) + return QVariant(); + + auto entry = m_rows[index.row()]; + + switch (role) { + case App: + return QVariant::fromValue(entry); + case IconName: + return entry.iconName; + case ApplicationName: + return entry.appName; + case ApplicationId: + return entry.appId; + case ExeuctableName: + return entry.executableName; + case ExecutablePath: + return entry.executablePath; + case SourceFileName: + return entry.sourceFileName; + default: + qCWarning(apps) << "Unhandled role" << role; + return QVariant(); + } +} + +QHash<int, QByteArray> AppListModel::roleNames() const +{ + return m_roles; +} + +/*! + * Returns true if the directory can be watched + * + * Parse all JSON application files from the given directory + * and monitor it for changes. + */ +bool AppListModel::addAndWatchDir(const QString& dirName) +{ + auto watcher = new QFileSystemWatcher(this); + connect(watcher, &QFileSystemWatcher::directoryChanged, this, &AppListModel::addDir); + auto res = watcher->addPath(dirName); + addDir(dirName); + + qCDebug(apps) << "addAndWatchDir" << dirName << "result: " << res; + return res; +} + +void AppListModel::addFile(const QString& fileName) +{ + beginResetModel(); + doAddFile(fileName); + endResetModel(); +} + +void AppListModel::addDir(const QString& dirName) +{ + QDirIterator dirIt(dirName, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable); + + // We want to deal with the corner case of some files being + // removed. One option would have been to keep a marker bit + // inside the AppEntry but that might leak too much of the + // implementation to the outside. Let's keep a list of file + // names that came from the directory and remove items from + // the list as we re-parse them. Once we are done emit the + // signals. + QVector<QString> deletionCandidates = entriesWithPrefix(dirName); + + beginResetModel(); + while (dirIt.hasNext()) { + auto fileName = dirIt.next(); + if (!doAddFile(fileName)) + continue; + deletionCandidates.removeAll(fileName); + } + QVector<AppEntry> removedApps = removeEntries(deletionCandidates); + endResetModel(); + + // Announce the apps we removed after the model has been updated + for (const auto& app : removedApps) { + qCDebug(apps) << "Going to remove entry for " << app.sourceFileName; + emit appRemoved(app); + } +} + +bool AppListModel::doAddFile(const QString& fileName) +{ + bool ok; + auto newEntry = AppParser::parseFile(fileName, &ok); + if (!ok) + return false; + + for (int i = 0; i < m_rows.count(); ++i) { + if (m_rows[i].sourceFileName == fileName) { + m_rows[i] = newEntry; + return true; + } + } + + m_rows.push_back(newEntry); + return true; +} + +QVector<QString> AppListModel::entriesWithPrefix(const QString& prefix) const +{ + QVector<QString> entries; + + for (const AppEntry& entry : m_rows) + if (entry.sourceFileName.startsWith(prefix)) + entries.push_back(entry.sourceFileName); + return entries; +} + +QVector<AppEntry> AppListModel::removeEntries(const QVector<QString>& fileNames) +{ + QVector<AppEntry> removedEntries(fileNames.size()); + + // Rare but quadratic. The actual removal. + for (const auto &toRemoveFile: fileNames) { + for (int i = 0; i < m_rows.size(); ++i) { + if (m_rows[i].sourceFileName != toRemoveFile) + continue; + removedEntries.append(m_rows[i]); + m_rows.removeAt(i); + break; + } + } + + return removedEntries; +} + +QVariant AppListModel::findApplicationId(const QString& appId) const +{ + for (const auto& entry : m_rows) { + if (entry.appId == appId) + return QVariant::fromValue(entry); + } + + return QVariant(); +} diff --git a/wayland/democompositor/apps/applistmodel.h b/wayland/democompositor/apps/applistmodel.h new file mode 100644 index 0000000..f2e61cc --- /dev/null +++ b/wayland/democompositor/apps/applistmodel.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include "appentry.h" + +#include <QtCore/QAbstractListModel> +#include <QtCore/QVector> + +/** + * A model that holds all available applications and + * exports them to a QML scene + */ +class AppListModel : public QAbstractListModel { + Q_OBJECT +public: + + enum Roles { + App = Qt::UserRole, + IconName, + ApplicationName, + ApplicationId, + ExeuctableName, + ExecutablePath, + SourceFileName, + }; + + int rowCount(const QModelIndex& parent) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex& index, int role) const Q_DECL_OVERRIDE; + QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; + + Q_INVOKABLE QVariant findApplicationId(const QString& appId) const; + +Q_SIGNALS: + void appRemoved(const AppEntry& appEntry); + +public Q_SLOTS: + bool addAndWatchDir(const QString& dirName); + + void addFile(const QString& fileName); + void addDir(const QString& dirName); + +private: + QVector<QString> entriesWithPrefix(const QString& prefix) const; + QVector<AppEntry> removeEntries(const QVector<QString>& sourceFileNames); + bool doAddFile(const QString& fileName); + + QVector<AppEntry> m_rows; + static QHash<int, QByteArray> m_roles; +}; diff --git a/wayland/democompositor/apps/applog.h b/wayland/democompositor/apps/applog.h new file mode 100644 index 0000000..b116530 --- /dev/null +++ b/wayland/democompositor/apps/applog.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <QtCore/QLoggingCategory> + +Q_DECLARE_LOGGING_CATEGORY(apps) diff --git a/wayland/democompositor/apps/appparser.cpp b/wayland/democompositor/apps/appparser.cpp new file mode 100644 index 0000000..528cb8f --- /dev/null +++ b/wayland/democompositor/apps/appparser.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "appparser.h" +#include "applog.h" + +#include <QtCore/QFile> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> + +Q_LOGGING_CATEGORY(apps, "launcher.apps") + + +static QString doReadString(const QJsonValue& value, bool *ok) +{ + if (value.type() != QJsonValue::String) { + *ok = false; + return QString(); + } + return value.toString(); +} + +static int doReadInt(const QJsonValue& value, bool *ok) +{ + if (value.type() != QJsonValue::Double) { + *ok = false; + return 0; + } + return value.toInt(); +} + +static QString readString(const QJsonObject& object, const QString& key, bool *ok) +{ + return doReadString(object.value(key), ok); +} + +static QString readStringOptional(const QJsonObject& object, const QString& key, bool *ok) +{ + auto item = object.value(key); + if (item.type() == QJsonValue::Undefined) + return QString(); + return doReadString(item, ok); +} + +static int readInt(const QJsonObject& object, const QString& key, bool *ok) +{ + return doReadInt(object.value(key), ok); +} + +static QVariantMap readExtensions(const QJsonObject& object, const QString& prefixKey) +{ + QVariantMap map; + + for (auto it = object.constBegin(); it != object.constEnd(); ++it) { + if (!it.key().startsWith(prefixKey)) + continue; + map[it.key()] = it.value().toVariant(); + } + return map; +} + +AppEntry AppParser::parseData(const QByteArray& content, const QString& fileName, bool *ok) +{ + *ok = true; + QJsonParseError error; + + QJsonDocument doc = QJsonDocument::fromJson(content, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(apps) << "Failed to parse json: " << error.errorString(); + *ok = false; + return AppEntry::empty(); + } + + if (!doc.isObject()) { + qCWarning(apps) << "Parsed document is not an object"; + *ok = false; + return AppEntry::empty(); + } + + auto root = doc.object(); + + QString type = readString(root, QStringLiteral("Type"), ok); + if (type != QStringLiteral("Application")) { + qCWarning(apps) << "Unknown type" << type; + *ok = false; + } + + int version = readInt(root, QStringLiteral("Version"), ok); + if (version != 1) { + qCWarning(apps) << "Version number should be 1... Consider to fix that" << version; + } + + QString iconName = readString(root, QStringLiteral("Icon"), ok); + QString appName = readString(root, QStringLiteral("Name"), ok); + QString appId = readString(root, QStringLiteral("Id"), ok); + QString executableName = readString(root, QStringLiteral("Exec"), ok); + QString executablePath = readStringOptional(root, QStringLiteral("Path"), ok); + QVariantMap extensions = readExtensions(root, QStringLiteral("X-")); + if (!*ok) + return AppEntry::empty(); + + return AppEntry{iconName, appName, appId, executableName, executablePath, fileName, extensions}; +} + +AppEntry AppParser::parseFile(const QString& fileName, bool *ok) +{ + qCDebug(apps) << "Trying to parse" << fileName; + + QFile file(fileName); + if (!file.open(QFile::ReadOnly)) { + qCWarning(apps) << "Failed to open" << fileName; + *ok = false; + return AppEntry::empty(); + } + + auto entry = parseData(file.readAll(), fileName, ok); + file.close(); + if (!*ok) { + qCWarning(apps) << "Failed to parse" << fileName; + return AppEntry::empty(); + } + return entry; +} diff --git a/wayland/democompositor/apps/appparser.h b/wayland/democompositor/apps/appparser.h new file mode 100644 index 0000000..a6711b4 --- /dev/null +++ b/wayland/democompositor/apps/appparser.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include "appentry.h" + +/** + * Parse a single application entry to an AppEntry. + */ +class AppParser { +public: + static AppEntry parseData(const QByteArray& content, const QString& fileName, bool *ok); + static AppEntry parseFile(const QString& fileName, bool *ok); +}; diff --git a/wayland/democompositor/apps/apps.pri b/wayland/democompositor/apps/apps.pri new file mode 100644 index 0000000..9b19060 --- /dev/null +++ b/wayland/democompositor/apps/apps.pri @@ -0,0 +1,11 @@ + +HEADERS += \ + apps/appentry.h \ + apps/applog.h \ + apps/applistmodel.h \ + apps/appparser.h + +SOURCES += \ + apps/appentry.cpp \ + apps/applistmodel.cpp \ + apps/appparser.cpp diff --git a/wayland/democompositor/democompositor.pro b/wayland/democompositor/democompositor.pro index 99b6f93..22b7d98 100644 --- a/wayland/democompositor/democompositor.pro +++ b/wayland/democompositor/democompositor.pro @@ -17,5 +17,12 @@ RESOURCES += democompositor.qrc HEADERS += \ processlauncher.h -INSTALLS += target +# include modules +include(apps/apps.pri) + +INSTALLS += target apps target.path = /data/user/$$TARGET + +# TODO. How does /data/user/$$TARGET map to QStandardPaths +apps.path = /data/user/$$TARGET/apps/ +apps.files = resources/apps/*.app diff --git a/wayland/democompositor/democompositor.qrc b/wayland/democompositor/democompositor.qrc index 940f04e..6e8bd54 100644 --- a/wayland/democompositor/democompositor.qrc +++ b/wayland/democompositor/democompositor.qrc @@ -6,15 +6,15 @@ <file>qml/MyButton.qml</file> <file>qml/LaunchButton.qml</file> <file>qml/TimedButton.qml</file> - <file>images/quit.png</file> - <file>images/greendot.png</file> - <file>images/reddot.png</file> - <file>images/graydot.png</file> - <file>images/graydots.png</file> - <file>images/greendots.png</file> - <file>images/Icon_Clocks.png</file> - <file>images/Icon_Maps.png</file> - <file>images/Icon_RSS.png</file> - <file>images/Icon_StocQt.png</file> + <file alias="images/quit.png">resources/images/quit.png</file> + <file alias="images/greendot.png">resources/images/greendot.png</file> + <file alias="images/reddot.png">resources/images/reddot.png</file> + <file alias="images/graydot.png">resources/images/graydot.png</file> + <file alias="images/graydots.png">resources/images/graydots.png</file> + <file alias="images/greendots.png">resources/images/greendots.png</file> + <file alias="images/Icon_Clocks.png">resources/images/Icon_Clocks.png</file> + <file alias="images/Icon_Maps.png">resources/images/Icon_Maps.png</file> + <file alias="images/Icon_RSS.png">resources/images/Icon_RSS.png</file> + <file alias="images/Icon_StocQt.png">resources/images/Icon_StocQt.png</file> </qresource> </RCC> diff --git a/wayland/democompositor/democompositor_all.pro b/wayland/democompositor/democompositor_all.pro new file mode 100644 index 0000000..52ae399 --- /dev/null +++ b/wayland/democompositor/democompositor_all.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs +SUBDIRS = \ + democompositor.pro \ + tests/apps \ + tests/applist diff --git a/wayland/democompositor/main.cpp b/wayland/democompositor/main.cpp index a094a20..2b1279d 100644 --- a/wayland/democompositor/main.cpp +++ b/wayland/democompositor/main.cpp @@ -48,6 +48,8 @@ ** ****************************************************************************/ +#include <QtCore/QCommandLineOption> +#include <QtCore/QCommandLineParser> #include <QtCore/QUrl> #include <QtCore/QDebug> @@ -55,11 +57,14 @@ #include <QtGui/QFont> #include <QtQml/qqml.h> #include <QtQml/QQmlApplicationEngine> + +#include "apps/applistmodel.h" #include "processlauncher.h" static void registerTypes() { qmlRegisterType<WaylandProcessLauncher>("com.theqtcompany.wlprocesslauncher", 1, 0, "ProcessLauncher"); + qmlRegisterType<AppListModel>("com.theqtcompany.wlapplistmodel", 1, 0, "AppListModel"); } @@ -68,13 +73,38 @@ int main(int argc, char *argv[]) { qputenv("QT_QPA_EGLFS_HIDECURSOR", "1"); QGuiApplication app(argc, argv); + QGuiApplication::setApplicationName("democompositor"); + QGuiApplication::setApplicationVersion("1.0"); + + /* Parse options */ + QCommandLineParser parser; + parser.setApplicationDescription("Demo Compositor"); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption fontNameOpt(QStringList() << "f" << "font", + QCoreApplication::translate("main", "Default font to use"), + QCoreApplication::translate("main", "Name of the font"), + "Open Sans"); + parser.addOption(fontNameOpt); + QCommandLineOption fontNameSzeOpt(QStringList() << "s" << "size", + QCoreApplication::translate("main", "Default font size to use"), + QCoreApplication::translate("main", "Point size of the font"), + "12"); + parser.addOption(fontNameSzeOpt); + QCommandLineOption qmlUrlOpt(QStringList() << "o" << "open", + QCoreApplication::translate("main", "QML scene to open"), + QCoreApplication::translate("main", "URL"), + "qrc:///qml/main.qml"); + parser.addOption(qmlUrlOpt); + parser.process(app); - QFont f("Open Sans", 12.5); + QFont f(parser.value(fontNameOpt), parser.value(fontNameSzeOpt).toInt()); app.setFont(f); qputenv("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1"); registerTypes(); - QQmlApplicationEngine appEngine(QUrl("qrc:///qml/main.qml")); + QQmlApplicationEngine appEngine(QUrl(parser.value(qmlUrlOpt))); return app.exec(); } diff --git a/wayland/democompositor/processlauncher.cpp b/wayland/democompositor/processlauncher.cpp index a25fbd5..29e4d64 100644 --- a/wayland/democompositor/processlauncher.cpp +++ b/wayland/democompositor/processlauncher.cpp @@ -49,8 +49,22 @@ ****************************************************************************/ #include "processlauncher.h" +#include "apps/appentry.h" #include <QProcess> +#include <QTimer> + +Q_LOGGING_CATEGORY(procs, "launcher.procs") + +/* + * Two AppState's are equal if they are managing the same + * QProcess. It is assumed that no AppState survives beyond + * the QProcess. + */ +bool operator==(const AppState& lhs, const AppState& rhs) +{ + return lhs.process == rhs.process; +} WaylandProcessLauncher::WaylandProcessLauncher(QObject *parent) : QObject(parent) @@ -59,18 +73,114 @@ WaylandProcessLauncher::WaylandProcessLauncher(QObject *parent) WaylandProcessLauncher::~WaylandProcessLauncher() { + for (auto state : m_appStates) { + state.process->disconnect(state.finishedConn); + state.process->disconnect(state.errorOccurredConn); + state.process->disconnect(state.startedConn); + delete state.process; + } + m_appStates.clear(); +} + +QVariant WaylandProcessLauncher::appStateForPid(int pid) const +{ + for (auto state : m_appStates) { + if (state.process->pid() == pid) { + qCDebug(procs) << "Found state for" << pid << state.appEntry.executableName; + return QVariant::fromValue(state); + } + } + + qCDebug(procs) << "Couldn't find entry for" << pid; + return QVariant(); +} + +bool WaylandProcessLauncher::isRunning(const AppEntry& entry) const +{ + for (auto state : m_appStates) { + if (state.appEntry.sourceFileName == entry.sourceFileName) { + qCDebug(procs) << "AppEntry associated to a state" << entry.executableName; + return true; + } + } + + qCDebug(procs) << "AppEntry not associated to a state " << entry.executableName; + return false; +} + +void WaylandProcessLauncher::kill(const AppEntry& entry) +{ + for (auto state : m_appStates) { + if (state.appEntry.sourceFileName != entry.sourceFileName) + continue; + + qCDebug(procs) << "Killing process " << state.process->pid() << " for " << entry.sourceFileName; + state.process->kill(); + } } -void WaylandProcessLauncher::launch(const QString &program) +void WaylandProcessLauncher::stop(const AppEntry& entry, int ms) { + for (auto state : m_appStates) { + if (state.appEntry.sourceFileName != entry.sourceFileName) + continue; + + auto timer = new QTimer(state.process); + connect(timer, &QTimer::timeout, [entry, state, timer] { + qCDebug(procs) << "Sending SIGKILL " << state.process->pid() << " for " << entry.sourceFileName; + timer->deleteLater(); + state.process->kill(); + }); + timer->start(ms); + qCDebug(procs) << "Sending SIGTERM " << state.process->pid() << " for " << entry.sourceFileName; + state.process->terminate(); + } +} + +void WaylandProcessLauncher::launch(const AppEntry &entry) +{ + qCDebug(procs) << "Launching" << entry.executableName; + QProcess *process = new QProcess(this); - connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), - process, &QProcess::deleteLater); - connect(process, &QProcess::errorOccurred, &QProcess::deleteLater); + process->setProcessChannelMode(QProcess::ForwardedChannels); + + QMetaObject::Connection conn; + AppState state{process, entry, conn, conn, conn}; + m_appStates.push_back(state); + + /* handle potential errors and life cycle */ + state.finishedConn = connect(process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), + [state, this](int exitCode, QProcess::ExitStatus status) { + qCDebug(procs) << "AppEntry finished" << state.appEntry.executableName << exitCode << status; + emit appFinished(state, exitCode, status); + m_appStates.removeOne(state); + state.process->deleteLater(); + }); + state.errorOccurredConn = connect(process, &QProcess::errorOccurred, + [state, this](QProcess::ProcessError err) { + qCDebug(procs) << "AppEntry error occurred" << state.appEntry.executableName << err; + + /* Maybe finished was already emitted. Let's not emit an error after that */ + if (!m_appStates.removeOne(state)) + return; + + if (err == QProcess::FailedToStart || err == QProcess::UnknownError) + emit appNotStarted(state); + state.process->deleteLater(); + }); + state.startedConn = connect(process, &QProcess::started, + [state, this]() { + qCDebug(procs) << "AppEntry started" << state.appEntry.executableName; + emit appStarted(state); + }); + + if (!entry.executablePath.isNull()) { + auto env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("PATH"), entry.executablePath); + process->setProcessEnvironment(env); + } QStringList arguments; arguments << "-platform" << "wayland"; - process->start(program, arguments); - + process->start(entry.executableName, arguments); } - diff --git a/wayland/democompositor/processlauncher.h b/wayland/democompositor/processlauncher.h index 261da6d..ae847e2 100644 --- a/wayland/democompositor/processlauncher.h +++ b/wayland/democompositor/processlauncher.h @@ -51,7 +51,29 @@ #ifndef PROCESSLAUNCHER_H #define PROCESSLAUNCHER_H +#include "apps/appentry.h" + #include <QObject> +#include <QProcess> +#include <QLoggingCategory> + +Q_DECLARE_LOGGING_CATEGORY(procs) + +/** + * Transient class. Do not keep AppState beyond the + * finished signal. + */ +class AppState { + Q_GADGET + Q_PROPERTY(QProcess *process MEMBER process CONSTANT) + Q_PROPERTY(AppEntry appEntry MEMBER appEntry CONSTANT) +public: + QProcess *process; + AppEntry appEntry; + QMetaObject::Connection finishedConn; + QMetaObject::Connection errorOccurredConn; + QMetaObject::Connection startedConn; +}; class WaylandProcessLauncher : public QObject { @@ -60,7 +82,22 @@ class WaylandProcessLauncher : public QObject public: explicit WaylandProcessLauncher(QObject *parent = 0); ~WaylandProcessLauncher(); - Q_INVOKABLE void launch(const QString &program); + Q_INVOKABLE void launch(const AppEntry &entry); + + Q_INVOKABLE QVariant appStateForPid(int pid) const; + Q_INVOKABLE bool isRunning(const AppEntry& entry) const; + Q_INVOKABLE void kill(const AppEntry& entry); + Q_INVOKABLE void stop(const AppEntry& entry, int timeout_ms); + +Q_SIGNALS: + void appStarted(const AppState &appState); + void appFinished(const AppState &appState, int exitCode, QProcess::ExitStatus exitStatus); + void appNotStarted(const AppState& appState); + +private: + QVector<AppState> m_appStates; }; +Q_DECLARE_METATYPE(AppState) + #endif // PROCESSLAUNCHER_H diff --git a/wayland/democompositor/qml/Chrome.qml b/wayland/democompositor/qml/Chrome.qml index 33a5b99..9c21cd1 100644 --- a/wayland/democompositor/qml/Chrome.qml +++ b/wayland/democompositor/qml/Chrome.qml @@ -65,6 +65,8 @@ Rectangle { property int marginWidth : 5 property int titlebarHeight : 5 + property var appEntry + function requestSize(w, h) { surfaceItem.requestSize(Qt.size(w - 2 * marginWidth, h - titlebarHeight - marginWidth)) } diff --git a/wayland/democompositor/qml/LaunchButton.qml b/wayland/democompositor/qml/LaunchButton.qml index deb462c..80a057a 100644 --- a/wayland/democompositor/qml/LaunchButton.qml +++ b/wayland/democompositor/qml/LaunchButton.qml @@ -51,11 +51,14 @@ import QtQuick 2.6 MyButton { - property string executable + property var appEntry text.text: "Uninitialized" text.elide: Text.ElideRight text.maximumLineCount: 1 iconSize: 32 - onTriggered: launcher.launch(executable) + onTriggered: { + if (!launcher.isRunning(appEntry)) + launcher.launch(appEntry) + } } diff --git a/wayland/democompositor/qml/Screen.qml b/wayland/democompositor/qml/Screen.qml index 34fd090..65df18f 100644 --- a/wayland/democompositor/qml/Screen.qml +++ b/wayland/democompositor/qml/Screen.qml @@ -53,11 +53,13 @@ import QtQuick.Window 2.2 import QtWayland.Compositor 1.0 import com.theqtcompany.wlprocesslauncher 1.0 +import com.theqtcompany.wlapplistmodel 1.0 WaylandOutput { id: output property alias surfaceArea: background + property alias appLauncher: launcher property var windowList: [ ] property int hiddenWindowCount @@ -85,6 +87,17 @@ WaylandOutput { id: launcher } + AppListModel { + id: apps + onAppRemoved: { + console.log("Application was removed: " + appEntry.appName); + launcher.kill(appEntry); + } + } + + Component.onCompleted: { + apps.addAndWatchDir("/data/user/democompositor/apps/") + } Rectangle { id: curtain @@ -176,7 +189,7 @@ WaylandOutput { pressedColor: pressedCol text.maximumLineCount: 1 - text.text: modelData.shellSurface.title.length > 0 ? modelData.shellSurface.title : "Untitled" + text.text: winItem.appEntry == null ? "Untitled" : winItem.appEntry.appName text.elide: Text.ElideRight text.color: textCol onTriggered: { @@ -189,7 +202,9 @@ WaylandOutput { } onSlideTrigger: { //console.log("slide " + winItem + " : " + winItem.shellSurface.surface) - winItem.shellSurface.surface.client.close() + winItem.appEntry == null ? + winItem.shellSurface.surface.client.close() : + launcher.stop(winItem.appEntry, 5000); } } Rectangle { @@ -252,46 +267,20 @@ WaylandOutput { height: 5 width: 1 } - LaunchButton { - height: 60 - width: sidebar.width - 10 - buttonColor: backgroundCol - pressedColor: pressedCol - textColor: textCol - text.text: "Clocks" - executable: "./clocks" - icon.source: "qrc:/images/Icon_Clocks.png" - } - LaunchButton { - height: 60 - width: sidebar.width - 10 - buttonColor: backgroundCol - pressedColor: pressedCol - textColor: textCol - text.text: "RSS News" - executable: "./rssnews" - icon.source: "qrc:/images/Icon_RSS.png" - } - LaunchButton { - height: 60 - width: sidebar.width - 10 - buttonColor: backgroundCol - pressedColor: pressedCol - textColor: textCol - text.text: "StoQt" - executable: "./stocqt" - icon.source: "qrc:/images/Icon_StocQt.png" - } - LaunchButton { - height: 60 - width: sidebar.width - 10 - buttonColor: backgroundCol - pressedColor: pressedCol - textColor: textCol - text.text: "Maps" - executable: "./qml_location_mapviewer" - icon.source: "qrc:/images/Icon_Maps.png" + Repeater { + model: apps + + LaunchButton { + height: 60 + width: sidebar.width - 10 + buttonColor: backgroundCol + pressedColor: pressedCol + textColor: textCol + text.text: model.applicationName + appEntry: model.appEntry + icon.source: model.iconName + } } TimedButton { diff --git a/wayland/democompositor/qml/main.qml b/wayland/democompositor/qml/main.qml index 8c0c621..3947b52 100644 --- a/wayland/democompositor/qml/main.qml +++ b/wayland/democompositor/qml/main.qml @@ -57,6 +57,7 @@ WaylandCompositor { property var primarySurfacesArea: null Screen { + id: mainScreen compositor: comp } @@ -69,7 +70,18 @@ WaylandCompositor { WlShell { id: defaultShell onWlShellSurfaceCreated: { - chromeComponent.createObject(defaultOutput.surfaceArea, { "shellSurface": shellSurface } ); + const pid = shellSurface.surface.client.processId; + const appState = mainScreen.appLauncher.appStateForPid(pid); + var appEntry; + if (!appState) { + console.log("shellSurface of unknown application. Continuing. PID=" + pid); + } else { + console.log("shellSurface belonging to " + appState.appEntry.executableName); + appEntry = appState.appEntry; + } + chromeComponent.createObject(defaultOutput.surfaceArea, { + "shellSurface": shellSurface, + "appEntry": appEntry } ); defaultOutput.relayout(); } } diff --git a/wayland/democompositor/resources/apps/clock.app b/wayland/democompositor/resources/apps/clock.app new file mode 100644 index 0000000..6ddf0d2 --- /dev/null +++ b/wayland/democompositor/resources/apps/clock.app @@ -0,0 +1,12 @@ +{ + "Type": "Application", + "Version": 1, + "Icon": "qrc:/images/Icon_Clocks.png", + "Name": "Clocks", + "Id": "io.qt.clocks", + "Exec": "clocks", + "Path": "./", + "X-Fullscreen": true, + "X-Priority": 100, + "X-Screen": "left" +} diff --git a/wayland/democompositor/resources/apps/maps.app b/wayland/democompositor/resources/apps/maps.app new file mode 100644 index 0000000..0192e4b --- /dev/null +++ b/wayland/democompositor/resources/apps/maps.app @@ -0,0 +1,9 @@ +{ + "Type": "Application", + "Version": 1, + "Icon": "qrc:/images/Icon_Maps.png", + "Name": "Maps", + "Id": "io.qt.maps", + "Exec": "qml_location_mapviewer", + "Path": "./" +} diff --git a/wayland/democompositor/resources/apps/rss.app b/wayland/democompositor/resources/apps/rss.app new file mode 100644 index 0000000..d631cda --- /dev/null +++ b/wayland/democompositor/resources/apps/rss.app @@ -0,0 +1,9 @@ +{ + "Type": "Application", + "Version": 1, + "Icon": "qrc:/images/Icon_RSS.png", + "Name": "RSS News", + "Id": "io.qt.rssnews", + "Exec": "rssnews", + "Path": "./" +} diff --git a/wayland/democompositor/resources/apps/stocqt.app b/wayland/democompositor/resources/apps/stocqt.app new file mode 100644 index 0000000..692f9ce --- /dev/null +++ b/wayland/democompositor/resources/apps/stocqt.app @@ -0,0 +1,9 @@ +{ + "Type": "Application", + "Version": 1, + "Icon": "qrc:/images/Icon_StocQt.png", + "Name": "StoQt", + "Id": "io.qt.stoqt", + "Exec": "stocqt", + "Path": "./" +} diff --git a/wayland/democompositor/images/Icon_Clocks.png b/wayland/democompositor/resources/images/Icon_Clocks.png Binary files differindex 0ca1955..0ca1955 100644 --- a/wayland/democompositor/images/Icon_Clocks.png +++ b/wayland/democompositor/resources/images/Icon_Clocks.png diff --git a/wayland/democompositor/images/Icon_Maps.png b/wayland/democompositor/resources/images/Icon_Maps.png Binary files differindex fac0dc9..fac0dc9 100644 --- a/wayland/democompositor/images/Icon_Maps.png +++ b/wayland/democompositor/resources/images/Icon_Maps.png diff --git a/wayland/democompositor/images/Icon_RSS.png b/wayland/democompositor/resources/images/Icon_RSS.png Binary files differindex 54ada4f..54ada4f 100644 --- a/wayland/democompositor/images/Icon_RSS.png +++ b/wayland/democompositor/resources/images/Icon_RSS.png diff --git a/wayland/democompositor/images/Icon_StocQt.png b/wayland/democompositor/resources/images/Icon_StocQt.png Binary files differindex d12aec1..d12aec1 100644 --- a/wayland/democompositor/images/Icon_StocQt.png +++ b/wayland/democompositor/resources/images/Icon_StocQt.png diff --git a/wayland/democompositor/images/graydot.png b/wayland/democompositor/resources/images/graydot.png Binary files differindex 99606e3..99606e3 100644 --- a/wayland/democompositor/images/graydot.png +++ b/wayland/democompositor/resources/images/graydot.png diff --git a/wayland/democompositor/images/graydots.png b/wayland/democompositor/resources/images/graydots.png Binary files differindex 0f90f12..0f90f12 100644 --- a/wayland/democompositor/images/graydots.png +++ b/wayland/democompositor/resources/images/graydots.png diff --git a/wayland/democompositor/images/greendot.png b/wayland/democompositor/resources/images/greendot.png Binary files differindex 0afb08f..0afb08f 100644 --- a/wayland/democompositor/images/greendot.png +++ b/wayland/democompositor/resources/images/greendot.png diff --git a/wayland/democompositor/images/greendots.png b/wayland/democompositor/resources/images/greendots.png Binary files differindex ab1e220..ab1e220 100644 --- a/wayland/democompositor/images/greendots.png +++ b/wayland/democompositor/resources/images/greendots.png diff --git a/wayland/democompositor/images/icon1.png b/wayland/democompositor/resources/images/icon1.png Binary files differindex 08f1c0c..08f1c0c 100644 --- a/wayland/democompositor/images/icon1.png +++ b/wayland/democompositor/resources/images/icon1.png diff --git a/wayland/democompositor/images/icon2.png b/wayland/democompositor/resources/images/icon2.png Binary files differindex e02dda8..e02dda8 100644 --- a/wayland/democompositor/images/icon2.png +++ b/wayland/democompositor/resources/images/icon2.png diff --git a/wayland/democompositor/images/icon3.png b/wayland/democompositor/resources/images/icon3.png Binary files differindex 9872b89..9872b89 100644 --- a/wayland/democompositor/images/icon3.png +++ b/wayland/democompositor/resources/images/icon3.png diff --git a/wayland/democompositor/images/icon4.png b/wayland/democompositor/resources/images/icon4.png Binary files differindex 3f64825..3f64825 100644 --- a/wayland/democompositor/images/icon4.png +++ b/wayland/democompositor/resources/images/icon4.png diff --git a/wayland/democompositor/images/quit.png b/wayland/democompositor/resources/images/quit.png Binary files differindex 7366d77..7366d77 100644 --- a/wayland/democompositor/images/quit.png +++ b/wayland/democompositor/resources/images/quit.png diff --git a/wayland/democompositor/images/reddot.png b/wayland/democompositor/resources/images/reddot.png Binary files differindex 06451be..06451be 100644 --- a/wayland/democompositor/images/reddot.png +++ b/wayland/democompositor/resources/images/reddot.png diff --git a/wayland/democompositor/tests/applist/applist.pro b/wayland/democompositor/tests/applist/applist.pro new file mode 100644 index 0000000..e253b66 --- /dev/null +++ b/wayland/democompositor/tests/applist/applist.pro @@ -0,0 +1,17 @@ +TARGET = tst_applistmodel +QT = testlib +CONFIG += testcase + +INCLUDEPATH += ../../apps + +HEADERS += \ + ../../apps/appentry.h \ + ../../apps/applistmodel.h + +SOURCES += \ + ../../apps/appentry.cpp \ + ../../apps/appparser.cpp \ + ../../apps/applistmodel.cpp + +SOURCES += tst_applistmodel.cpp +RESOURCES += applist.qrc diff --git a/wayland/democompositor/tests/applist/applist.qrc b/wayland/democompositor/tests/applist/applist.qrc new file mode 100644 index 0000000..eb4d8da --- /dev/null +++ b/wayland/democompositor/tests/applist/applist.qrc @@ -0,0 +1,6 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/"> + <file alias="app1.json">../../resources/apps/clock.app</file> + <file alias="app2.json">../../resources/apps/maps.app</file> +</qresource> +</RCC> diff --git a/wayland/democompositor/tests/applist/tst_applistmodel.cpp b/wayland/democompositor/tests/applist/tst_applistmodel.cpp new file mode 100644 index 0000000..921defc --- /dev/null +++ b/wayland/democompositor/tests/applist/tst_applistmodel.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "applistmodel.h" +#include "appentry.h" + +#include <QtCore/QVariant> + +#include <QtTest> + +class tst_AppListModel : public QObject { + Q_OBJECT + +private Q_SLOTS: + void testNoDuplicates(); + void testRoles(); +}; + +void tst_AppListModel::testNoDuplicates() +{ + AppListModel model; + QCOMPARE(model.rowCount(QModelIndex()), 0); + model.addFile(":/app1.json"); + QCOMPARE(model.rowCount(QModelIndex()), 1); + model.addFile(":/app2.json"); + QCOMPARE(model.rowCount(QModelIndex()), 2); + model.addFile(":/app1.json"); + QCOMPARE(model.rowCount(QModelIndex()), 2); + model.addFile(":/app2.json"); +} + +void tst_AppListModel::testRoles() +{ + AppListModel model; + model.addFile(":/app1.json"); + + auto idx = model.index(1, 0); + QVERIFY(!idx.isValid()); + idx = model.index(0, 0); + QVERIFY(idx.isValid()); + + /* Check we get a full AppEntry back */ + auto var = idx.data(AppListModel::App); + QVERIFY(var.canConvert<AppEntry>()); + auto app = var.value<AppEntry>(); + QCOMPARE(app.sourceFileName, QStringLiteral(":/app1.json")); + + var = idx.data(AppListModel::IconName); + QCOMPARE(var.toString(), QStringLiteral("qrc:/images/Icon_Clocks.png")); + var = idx.data(AppListModel::ApplicationName); + QCOMPARE(var.toString(), QStringLiteral("Clocks")); + var = idx.data(AppListModel::ExeuctableName); + QCOMPARE(var.toString(), QStringLiteral("clocks")); + var = idx.data(AppListModel::ExecutablePath); + QCOMPARE(var.toString(), QStringLiteral("./")); + var = idx.data(AppListModel::SourceFileName); + QCOMPARE(var.toString(), QStringLiteral(":/app1.json")); +} + +QTEST_MAIN(tst_AppListModel) +#include "tst_applistmodel.moc" diff --git a/wayland/democompositor/tests/apps/apps.pro b/wayland/democompositor/tests/apps/apps.pro new file mode 100644 index 0000000..21a6cfe --- /dev/null +++ b/wayland/democompositor/tests/apps/apps.pro @@ -0,0 +1,15 @@ +TARGET = tst_apps +QT = testlib +CONFIG += testcase + +INCLUDEPATH += ../../apps + +HEADERS += \ + ../../apps/appentry.h + +SOURCES += \ + ../../apps/appentry.cpp \ + ../../apps/appparser.cpp + +SOURCES += tst_appparser.cpp +RESOURCES += apps.qrc diff --git a/wayland/democompositor/tests/apps/apps.qrc b/wayland/democompositor/tests/apps/apps.qrc new file mode 100644 index 0000000..a8a834b --- /dev/null +++ b/wayland/democompositor/tests/apps/apps.qrc @@ -0,0 +1,5 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/"> + <file alias="app.json">../../resources/apps/clock.app</file> +</qresource> +</RCC> diff --git a/wayland/democompositor/tests/apps/tst_appparser.cpp b/wayland/democompositor/tests/apps/tst_appparser.cpp new file mode 100644 index 0000000..38a459c --- /dev/null +++ b/wayland/democompositor/tests/apps/tst_appparser.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of Qt for Device Creation. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> + +#include "appparser.h" + +class tst_AppParser : public QObject { + Q_OBJECT + +private Q_SLOTS: + void testValidVersion1_data(); + void testValidVersion1(); + + void testInvalid_data(); + void testInvalid(); + + void testFileOpen(); +}; + + +void tst_AppParser::testValidVersion1_data() +{ + QTest::addColumn<QByteArray>("input"); + QTest::addColumn<QString>("icon"); + QTest::addColumn<QString>("name"); + QTest::addColumn<QString>("id"); + QTest::addColumn<QString>("exec"); + QTest::addColumn<QString>("path"); + + QTest::addRow("clock") + << QByteArray("{\"Type\":\"Application\", \"Version\":1, \"Icon\":\"icon\", \"Name\":\"Clocks\",\"Id\":\"io.qt.clocks\",\"Exec\":\"clocks\"}") + << QStringLiteral("icon") << QStringLiteral("Clocks") << QStringLiteral("io.qt.clocks") << QStringLiteral("clocks") << QString(); + QTest::addRow("path") + << QByteArray("{\"Type\":\"Application\", \"Version\":1, \"Icon\":\"icon\", \"Name\":\"Clocks\",\"Id\":\"io.qt.clocks\",\"Exec\":\"clocks\",\"Path\":\"P\"}") + << QStringLiteral("icon") << QStringLiteral("Clocks") << QStringLiteral("io.qt.clocks") << QStringLiteral("clocks") << QStringLiteral("P"); +} + +void tst_AppParser::testValidVersion1() +{ + QFETCH(QByteArray, input); + QFETCH(QString, icon); + QFETCH(QString, name); + QFETCH(QString, exec); + QFETCH(QString, path); + + bool ok = false; + auto entry = AppParser::parseData(input, QStringLiteral("dummy"), &ok); + QVERIFY(ok); +} + +void tst_AppParser::testInvalid_data() +{ + QTest::addColumn<QByteArray>("input"); + QTest::addRow("no json") << QByteArray("12345"); + QTest::addRow("array") << QByteArray("[]"); + QTest::addRow("no content") << QByteArray("{}"); + QTest::addRow("empty") << QByteArray(""); +} + +void tst_AppParser::testInvalid() +{ + QFETCH(QByteArray, input); + + bool ok = true; + AppParser::parseData(input, QStringLiteral("dummy"), &ok); + QVERIFY(!ok); +} + +void tst_AppParser::testFileOpen() +{ + bool ok = true; + + AppParser::parseFile(":/can_not_exist_here.json", &ok); + QVERIFY(!ok); + + ok = false; + auto entry = AppParser::parseFile(":/app.json", &ok); + QVERIFY(ok); + QCOMPARE(entry.iconName, QStringLiteral("qrc:/images/Icon_Clocks.png")); + QCOMPARE(entry.appName, QStringLiteral("Clocks")); + QCOMPARE(entry.executableName, QStringLiteral("clocks")); + QCOMPARE(entry.executablePath, QStringLiteral("./")); + + // Look at extensions + QVERIFY(entry.extensions["X-Fullscreen"].canConvert(QMetaType::Bool)); + QCOMPARE(entry.extensions["X-Fullscreen"].toBool(), true); + QVERIFY(entry.extensions["X-Priority"].canConvert(QMetaType::Double)); + QCOMPARE(entry.extensions["X-Priority"].toInt(), 100); + QVERIFY(entry.extensions["X-Screen"].canConvert(QMetaType::QString)); + QCOMPARE(entry.extensions["X-Screen"].toString(), QStringLiteral("left")); +} + +QTEST_MAIN(tst_AppParser) +#include "tst_appparser.moc" |