homescreens/halcyon: Port to new model

This commit is contained in:
Devin Lin 2022-06-18 15:42:29 -04:00
parent b41e1f2b8a
commit ef45546127
20 changed files with 854 additions and 74 deletions

View file

@ -1,14 +1,9 @@
/*
* SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
// Self
#include "applicationlistmodel.h"
// Qt
#include <QByteArray>
#include <QDebug>
#include <QModelIndex>
@ -16,7 +11,6 @@
#include <QQuickItem>
#include <QQuickWindow>
// KDE
#include <KApplicationTrader>
#include <KConfigGroup>
#include <KIO/ApplicationLauncherJob>
@ -25,11 +19,6 @@
#include <KSharedConfig>
#include <KSycoca>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
ApplicationListModel::ApplicationListModel(QObject *parent)
: QAbstractListModel(parent)
{
@ -72,12 +61,6 @@ QHash<int, QByteArray> ApplicationListModel::roleNames() const
{ApplicationLocationRole, QByteArrayLiteral("applicationLocation")}};
}
ApplicationListModel *ApplicationListModel::instance()
{
static ApplicationListModel *model = new ApplicationListModel;
return model;
}
void ApplicationListModel::sycocaDbChanged()
{
m_applicationList.clear();
@ -232,19 +215,16 @@ void ApplicationListModel::setMinimizedDelegate(int row, QQuickItem *delegate)
}
QWindow *delegateWindow = delegate->window();
if (!delegateWindow) {
return;
}
using namespace KWayland::Client;
KWayland::Client::PlasmaWindow *window = m_applicationList[row].window;
if (!window) {
return;
}
Surface *surface = Surface::fromWindow(delegateWindow);
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
if (!surface) {
return;
}
@ -261,19 +241,16 @@ void ApplicationListModel::unsetMinimizedDelegate(int row, QQuickItem *delegate)
}
QWindow *delegateWindow = delegate->window();
if (!delegateWindow) {
return;
}
using namespace KWayland::Client;
KWayland::Client::PlasmaWindow *window = m_applicationList[row].window;
if (!window) {
return;
}
Surface *surface = Surface::fromWindow(delegateWindow);
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
if (!surface) {
return;
}

View file

@ -1,29 +1,19 @@
/*
* SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
* SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// Qt
#include <QAbstractListModel>
#include <QList>
#include <QObject>
#include <QQuickItem>
#include <QSet>
class QString;
namespace KWayland
{
namespace Client
{
class PlasmaWindowManagement;
class PlasmaWindow;
}
}
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
/**
* @short The base application list, used directly by the app drawer.
@ -64,8 +54,6 @@ public:
ApplicationListModel(QObject *parent = nullptr);
~ApplicationListModel() override;
static ApplicationListModel *instance();
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;

View file

@ -3,6 +3,11 @@
set(homescreen_SRCS
homescreen.cpp
application.cpp
applicationfolder.cpp
applicationlistmodel.cpp
pinnedmodel.cpp
windowlistener.cpp
)
add_library(plasma_containment_phone_homescreen_halcyon MODULE ${homescreen_SRCS})

View file

@ -0,0 +1,153 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "application.h"
#include "windowlistener.h"
#include <QQuickWindow>
#include <KIO/ApplicationLauncherJob>
#include <KNotificationJobUiDelegate>
#include <KService>
Application::Application(QObject *parent, KService::Ptr service)
: QObject{parent}
, m_running{false}
, m_name{service->name()}
, m_icon{service->icon()}
, m_storageId{service->storageId()}
{
auto windows = WindowListener::instance()->windowsFromStorageId(m_storageId);
if (windows.empty()) {
m_window = nullptr;
} else {
m_window = windows[0];
}
connect(WindowListener::instance(), &WindowListener::windowChanged, this, [this](QString storageId) {
if (storageId == m_storageId) {
auto windows = WindowListener::instance()->windowsFromStorageId(m_storageId);
if (windows.empty()) {
setWindow(nullptr);
} else {
setWindow(windows[0]);
}
}
});
}
Application *Application::fromJson(QJsonObject &obj, QObject *parent)
{
QString storageId = obj[QStringLiteral("storageId")].toString();
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
return new Application(parent, service);
}
return nullptr;
}
QJsonObject Application::toJson()
{
QJsonObject obj;
obj[QStringLiteral("type")] = "application";
obj[QStringLiteral("storageId")] = m_storageId;
return obj;
}
bool Application::running() const
{
return m_window != nullptr;
}
QString Application::name() const
{
return m_name;
}
QString Application::icon() const
{
return m_icon;
}
QString Application::storageId() const
{
return m_storageId;
}
KWayland::Client::PlasmaWindow *Application::window() const
{
return m_window;
}
void Application::setName(QString &name)
{
m_name = name;
Q_EMIT nameChanged();
}
void Application::setIcon(QString &icon)
{
m_icon = icon;
Q_EMIT iconChanged();
}
void Application::setStorageId(QString &storageId)
{
m_storageId = storageId;
Q_EMIT storageIdChanged();
}
void Application::setWindow(KWayland::Client::PlasmaWindow *window)
{
m_window = window;
Q_EMIT windowChanged();
}
void Application::runApplication()
{
if (m_window) {
m_window->requestActivate();
return;
}
KService::Ptr service = KService::serviceByStorageId(m_storageId);
KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service);
job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled));
job->start();
}
void Application::setMinimizedDelegate(QQuickItem *delegate)
{
QWindow *delegateWindow = delegate->window();
if (!delegateWindow) {
return;
}
if (!m_window) {
return;
}
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
if (!surface) {
return;
}
QRect rect = delegate->mapRectToScene(QRectF(0, 0, delegate->width(), delegate->height())).toRect();
m_window->setMinimizedGeometry(surface, rect);
}
void Application::unsetMinimizedDelegate(QQuickItem *delegate)
{
QWindow *delegateWindow = delegate->window();
if (!delegateWindow) {
return;
}
if (!m_window) {
return;
}
KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(delegateWindow);
if (!surface) {
return;
}
m_window->unsetMinimizedGeometry(surface);
}

View file

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QJsonObject>
#include <QObject>
#include <QQuickItem>
#include <QString>
#include <KService>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
class Application : public QObject
{
Q_OBJECT
Q_PROPERTY(bool running READ running NOTIFY windowChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
Q_PROPERTY(QString storageId READ storageId NOTIFY storageIdChanged)
public:
Application(QObject *parent = nullptr, KService::Ptr service = QExplicitlySharedDataPointer<KService>{nullptr});
static Application *fromJson(QJsonObject &obj, QObject *parent); // may return nullptr
QJsonObject toJson();
bool running() const;
QString name() const;
QString icon() const;
QString storageId() const;
KWayland::Client::PlasmaWindow *window() const;
void setName(QString &name);
void setIcon(QString &icon);
void setStorageId(QString &storageId);
void setWindow(KWayland::Client::PlasmaWindow *window);
Q_INVOKABLE void runApplication();
Q_INVOKABLE void setMinimizedDelegate(QQuickItem *delegate);
Q_INVOKABLE void unsetMinimizedDelegate(QQuickItem *delegate);
Q_SIGNALS:
void nameChanged();
void iconChanged();
void storageIdChanged();
void windowChanged();
private:
bool m_running;
QString m_name;
QString m_icon;
QString m_storageId;
KWayland::Client::PlasmaWindow *m_window = nullptr;
};

View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "applicationfolder.h"
#include <QJsonArray>
ApplicationFolder::ApplicationFolder(QObject *parent)
{
}
ApplicationFolder *ApplicationFolder::fromJson(QJsonObject &obj, QObject *parent)
{
QString name = obj[QStringLiteral("name")].toString();
QList<Application *> apps;
for (auto storageId : obj[QStringLiteral("apps")].toArray()) {
if (KService::Ptr service = KService::serviceByStorageId(storageId.toString())) {
apps.append(new Application(parent, service));
}
}
ApplicationFolder *folder = new ApplicationFolder(parent);
folder->setName(name);
folder->setApplications(apps);
return folder;
}
QJsonObject ApplicationFolder::toJson()
{
QJsonObject obj;
obj[QStringLiteral("type")] = "folder";
obj[QStringLiteral("name")] = m_name;
QJsonArray arr;
for (auto *application : m_applications) {
arr.append(QJsonValue::fromVariant(application->storageId()));
}
obj[QStringLiteral("apps")] = arr;
return obj;
}
QString ApplicationFolder::name() const
{
return m_name;
}
void ApplicationFolder::setName(QString &name)
{
m_name = name;
Q_EMIT nameChanged();
}
QList<Application *> ApplicationFolder::applications()
{
return m_applications;
}
void ApplicationFolder::setApplications(QList<Application *> applications)
{
m_applications = applications;
Q_EMIT applicationsChanged();
}

View file

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "application.h"
#include <QObject>
#include <QString>
#include <KService>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
class ApplicationFolder : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QList<Application *> applications READ applications NOTIFY applicationsChanged)
public:
ApplicationFolder(QObject *parent = nullptr);
static ApplicationFolder *fromJson(QJsonObject &obj, QObject *parent);
QJsonObject toJson();
QString name() const;
void setName(QString &name);
QList<Application *> applications();
void setApplications(QList<Application *> applications);
Q_SIGNALS:
void nameChanged();
void applicationsChanged();
private:
QString m_name;
QList<Application *> m_applications;
};

View file

@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "applicationlistmodel.h"
#include <QByteArray>
#include <QDebug>
#include <QModelIndex>
#include <QProcess>
#include <QQuickItem>
#include <QQuickWindow>
#include <KApplicationTrader>
#include <KConfigGroup>
#include <KIO/ApplicationLauncherJob>
#include <KNotificationJobUiDelegate>
#include <KService>
#include <KSharedConfig>
#include <KSycoca>
ApplicationListModel::ApplicationListModel(QObject *parent)
: QAbstractListModel(parent)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
connect(KSycoca::self(), qOverload<const QStringList &>(&KSycoca::databaseChanged), this, &ApplicationListModel::sycocaDbChanged);
#else
connect(KSycoca::self(), &KSycoca::databaseChanged, this, &ApplicationListModel::sycocaDbChanged);
#endif
}
ApplicationListModel::~ApplicationListModel() = default;
QHash<int, QByteArray> ApplicationListModel::roleNames() const
{
return {{ApplicationRole, QByteArrayLiteral("application")}};
}
void ApplicationListModel::sycocaDbChanged()
{
m_applicationList.clear();
loadApplications();
}
void ApplicationListModel::loadApplications()
{
auto cfg = KSharedConfig::openConfig(QStringLiteral("applications-blacklistrc"));
auto blgroup = KConfigGroup(cfg, QStringLiteral("Applications"));
const QStringList blacklist = blgroup.readEntry("blacklist", QStringList());
beginResetModel();
m_applicationList.clear();
QList<Application *> unorderedList;
auto filter = [blacklist](const KService::Ptr &service) -> bool {
if (service->noDisplay()) {
return false;
}
if (!service->showOnCurrentPlatform()) {
return false;
}
if (blacklist.contains(service->desktopEntryName())) {
return false;
}
return true;
};
const KService::List apps = KApplicationTrader::query(filter);
for (const KService::Ptr &service : apps) {
Application *application = new Application{this, service};
unorderedList.append(application);
}
std::sort(unorderedList.begin(), unorderedList.end(), [](const Application *a1, const Application *a2) {
return a1->name().compare(a2->name(), Qt::CaseInsensitive) < 0;
});
m_applicationList << unorderedList;
endResetModel();
}
QVariant ApplicationListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
return QVariant::fromValue(m_applicationList.at(index.row()));
}
int ApplicationListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_applicationList.count();
}

View file

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2014 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "application.h"
#include <QAbstractListModel>
#include <QList>
#include <QObject>
#include <QQuickItem>
#include <QSet>
/**
* @short The base application list, used directly by the app drawer.
*/
class ApplicationListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles { ApplicationRole = Qt::UserRole + 1 };
ApplicationListModel(QObject *parent = nullptr);
~ApplicationListModel() override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void loadApplications();
public Q_SLOTS:
void sycocaDbChanged();
Q_SIGNALS:
void launchError(const QString &msg);
protected:
QList<Application *> m_applicationList;
};

View file

@ -1,2 +1,45 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
// SPDX-License-Identifier: GPL-2.0-or-later
#include "homescreen.h"
#include "application.h"
#include "applicationfolder.h"
#include "pinnedmodel.h"
#include "windowlistener.h"
#include <KWindowSystem>
#include <QDebug>
#include <QQuickItem>
#include <QtQml>
HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
: Plasma::Containment{parent, data, args}
{
setHasConfigurationInterface(true);
WindowListener::instance(); // ensure it is created
ApplicationListModel *applicationListModel = new ApplicationListModel{this};
qmlRegisterSingletonType<ApplicationListModel>("org.kde.phone.homescreen.halcyon",
1,
0,
"ApplicationListModel",
[applicationListModel](QQmlEngine *, QJSEngine *) -> QObject * {
return applicationListModel;
});
PinnedModel *pinnedModel = new PinnedModel{this, this};
qmlRegisterSingletonType<PinnedModel>("org.kde.phone.homescreen.halcyon", 1, 0, "PinnedModel", [pinnedModel](QQmlEngine *, QJSEngine *) -> QObject * {
return pinnedModel;
});
qmlRegisterType<Application>("org.kde.phone.homescreen.halcyon", 1, 0, "Application");
qmlRegisterType<ApplicationFolder>("org.kde.phone.homescreen.halcyon", 1, 0, "ApplicationFolder");
}
HomeScreen::~HomeScreen() = default;
K_PLUGIN_CLASS_WITH_JSON(HomeScreen, "metadata.json")
#include "homescreen.moc"

View file

@ -1,2 +1,17 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Plasma/Containment>
#include <applicationlistmodel.h>
class HomeScreen : public Plasma::Containment
{
Q_OBJECT
public:
HomeScreen(QObject *parent, const KPluginMetaData &data, const QVariantList &args);
~HomeScreen() override;
};

View file

@ -13,12 +13,19 @@ import org.kde.kquickcontrolsaddons 2.0
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
import org.kde.kirigami 2.19 as Kirigami
MouseArea {
id: delegate
property alias iconItem: icon
property Halcyon.Application application: model.application
readonly property string applicationName: application ? application.name : ""
readonly property string applicationStorageId: application ? application.storageId : ""
readonly property string applicationIcon: application ? application.icon : ""
signal launch(int x, int y, var source, string title, string storageId)
signal dragStarted(string imageSource, int x, int y, string mimeData)
@ -33,8 +40,8 @@ MouseArea {
Math.min(delegate.iconItem.width, delegate.iconItem.height));
}
MobileShell.ApplicationListModel.setMinimizedDelegate(index, delegate);
MobileShell.ApplicationListModel.runApplication(storageId);
application.setMinimizedDelegate(delegate);
application.runApplication();
}
onPressAndHold: {
@ -44,10 +51,10 @@ MouseArea {
onClicked: {
// launch app
if (model.applicationRunning) {
delegate.launch(0, 0, "", model.applicationName, model.applicationStorageId);
if (application.running) {
delegate.launch(0, 0, "", applicationName, applicationStorageId);
} else {
delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, model.applicationName, model.applicationStorageId);
delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, applicationName, applicationStorageId);
}
}
hoverEnabled: true
@ -63,7 +70,7 @@ MouseArea {
icon.name: "emblem-favorite"
text: i18n("Remove from favourites")
onClicked: {
MobileShell.FavoritesModel.removeFavorite(model.index);
Halcyon.PinnedModel.removeApp(model.index);
}
}
onClosed: dialogLoader.active = false
@ -98,14 +105,14 @@ MouseArea {
Layout.preferredHeight: Layout.minimumHeight
usesPlasmaTheme: false
source: model.applicationIcon
source: applicationIcon
Rectangle {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
}
visible: model.applicationRunning
visible: application.running
radius: width
width: PlasmaCore.Units.smallSpacing
height: width
@ -132,7 +139,7 @@ MouseArea {
maximumLineCount: 1
elide: Text.ElideRight
text: model.applicationName
text: applicationName
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize
font.weight: Font.Bold

View file

@ -16,6 +16,7 @@ import org.kde.kquickcontrolsaddons 2.0
import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
import org.kde.kirigami 2.19 as Kirigami
@ -24,6 +25,8 @@ MouseArea {
width: GridView.view.cellWidth
height: GridView.view.cellHeight
property Halcyon.Application application: model.application
property int reservedSpaceForLabel
property alias iconItem: icon
@ -38,10 +41,10 @@ MouseArea {
function launchApp() {
// launch app
if (model.applicationRunning) {
delegate.launch(0, 0, "", model.applicationName, model.applicationStorageId);
if (application.running) {
delegate.launch(0, 0, "", application.name, application.storageId);
} else {
delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, model.applicationName, model.applicationStorageId);
delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, application.name, application.storageId);
}
}
@ -56,7 +59,7 @@ MouseArea {
icon.name: "emblem-favorite"
text: i18n("Add to favourites")
onClicked: {
MobileShell.FavoritesModel.addFavorite(model.applicationStorageId, 0, MobileShell.ApplicationListModel.Favorites);
Halcyon.PinnedModel.addApp(application.storageId, 0);
}
}
onClosed: dialogLoader.active = false
@ -131,14 +134,14 @@ MouseArea {
Layout.preferredHeight: Layout.minimumHeight
usesPlasmaTheme: false
source: model.applicationIcon
source: application.icon
Rectangle {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
}
visible: model.applicationRunning
visible: application.running
radius: width
width: PlasmaCore.Units.smallSpacing
height: width
@ -161,7 +164,7 @@ MouseArea {
verticalAlignment: Text.AlignTop
elide: Text.ElideRight
text: model.applicationName
text: application.name
font.pointSize: theme.defaultFont.pointSize * 0.85
font.weight: Font.Bold

View file

@ -15,6 +15,7 @@ import org.kde.kirigami 2.10 as Kirigami
import org.kde.plasma.private.nanoshell 2.0 as NanoShell
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
GridView {
id: gridView
@ -32,7 +33,7 @@ GridView {
cacheBuffer: Math.max(0, rows * cellHeight)
model: MobileShell.ApplicationListModel
model: Halcyon.ApplicationListModel
header: Controls.Control {
implicitWidth: gridView.width
@ -59,6 +60,8 @@ GridView {
delegate: GridAppDelegate {
id: delegate
property Halcyon.Application application: model.application
width: gridView.cellWidth
height: gridView.cellHeight
reservedSpaceForLabel: gridView.reservedSpaceForLabel
@ -73,8 +76,8 @@ GridView {
Math.min(delegate.iconItem.width, delegate.iconItem.height));
}
MobileShell.ApplicationListModel.setMinimizedDelegate(index, delegate);
MobileShell.ApplicationListModel.runApplication(storageId);
application.setMinimizedDelegate(delegate);
application.runApplication();
gridView.launched();
}
}

View file

@ -13,6 +13,7 @@ import org.kde.draganddrop 2.0 as DragDrop
import org.kde.kirigami 2.19 as Kirigami
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
Item {
id: root
@ -60,7 +61,7 @@ Item {
}
}
model: MobileShell.FavoritesModel
model: Halcyon.PinnedModel
header: MobileShell.BaseItem {
topPadding: Math.round(swipeView.height * 0.2)
bottomPadding: PlasmaCore.Units.largeSpacing

View file

@ -10,6 +10,7 @@ import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.phone.homescreen.halcyon 1.0 as Halcyon
MobileShell.HomeScreen {
id: root
@ -23,10 +24,7 @@ MobileShell.HomeScreen {
}
Component.onCompleted: {
MobileShell.ApplicationListModel.loadApplications();
MobileShell.FavoritesModel.applet = plasmoid;
MobileShell.FavoritesModel.loadApplications();
Halcyon.ApplicationListModel.loadApplications();
forceActiveFocus();
}
@ -61,7 +59,7 @@ MobileShell.HomeScreen {
Connections {
target: MobileShell.HomeScreenControls.taskSwitcher
function onVisibleChanged() {
searchWidget.close();
search.close();
}
}
}

View file

@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "pinnedmodel.h"
#include <QJsonArray>
#include <QJsonDocument>
PinnedModel::PinnedModel(QObject *parent, Plasma::Applet *applet)
: QAbstractListModel{parent}
, m_applet{applet}
{
}
PinnedModel::~PinnedModel() = default;
int PinnedModel::rowCount(const QModelIndex &parent) const
{
return m_applications.count();
}
QVariant PinnedModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
switch (role) {
case IsFolderRole:
return m_folders.at(index.row()) != nullptr;
case ApplicationRole:
return QVariant::fromValue(m_applications.at(index.row()));
case FolderRole:
return QVariant::fromValue(m_folders.at(index.row()));
}
return QVariant();
}
QHash<int, QByteArray> PinnedModel::roleNames() const
{
return {{IsFolderRole, "isFolder"}, {ApplicationRole, "application"}, {FolderRole, "folder"}};
}
void PinnedModel::addApp(const QString &storageId, int row)
{
if (row < 0 && row > m_applications.size()) {
return;
}
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
Application *app = new Application(this, service);
beginInsertRows(QModelIndex(), row, row);
m_applications.insert(row, app);
m_folders.insert(0, nullptr); // maintain indicies
endInsertRows();
save();
}
}
void PinnedModel::removeApp(int row)
{
if (row < 0 && row >= m_applications.size()) {
return;
}
beginRemoveRows(QModelIndex(), row, row);
m_applications[row]->deleteLater();
m_applications.removeAt(row);
m_folders.removeAt(row); // maintain indicies
endRemoveRows();
save();
}
void PinnedModel::load()
{
if (!m_applet) {
return;
}
QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Pinned", "{}").toUtf8());
beginResetModel();
for (QJsonValueRef r : doc.array()) {
QJsonObject obj = r.toObject();
if (obj[QStringLiteral("type")].toString() == "application") {
// read application
Application *app = Application::fromJson(obj, this);
if (app) {
m_applications.append(app);
m_folders.append(nullptr);
}
} else if (obj[QStringLiteral("type")].toString() == "folder") {
// read folder
ApplicationFolder *folder = ApplicationFolder::fromJson(obj, this);
if (folder) {
m_applications.append(nullptr);
m_folders.append(folder);
}
}
}
endResetModel();
}
void PinnedModel::save()
{
if (!m_applet) {
return;
}
QJsonArray arr;
for (int i = 0; i < m_applications.size() && i < m_folders.size(); i++) {
if (m_applications[i]) {
arr.push_back(m_applications[i]->toJson());
} else if (m_folders[i]) {
arr.push_back(m_folders[i]->toJson());
}
}
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
m_applet->config().writeEntry("Pinned", QString::fromStdString(data.toStdString()));
Q_EMIT m_applet->configNeedsSaving();
}

View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "application.h"
#include "applicationfolder.h"
#include <QAbstractListModel>
#include <QList>
#include <QObject>
#include <QQuickItem>
#include <QSet>
#include <Plasma/Applet>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
class PinnedModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles { IsFolderRole = Qt::UserRole + 1, ApplicationRole, FolderRole };
PinnedModel(QObject *parent = nullptr, Plasma::Applet *applet = nullptr);
~PinnedModel() override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void addApp(const QString &storageId, int row);
Q_INVOKABLE void removeApp(int row);
Q_INVOKABLE void load();
void save();
private:
QList<Application *> m_applications;
QList<ApplicationFolder *> m_folders;
Plasma::Applet *m_applet;
};

View file

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "windowlistener.h"
WindowListener::WindowListener(QObject *parent)
: QObject{parent}
{
// initialize wayland window checking
KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this);
if (!connection) {
return;
}
auto *registry = new KWayland::Client::Registry(this);
registry->create(connection);
connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, this, [this, registry](quint32 name, quint32 version) {
m_windowManagement = registry->createPlasmaWindowManagement(name, version, this);
connect(m_windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, this, &WindowListener::windowCreated);
});
registry->setup();
connection->roundtrip();
}
WindowListener *WindowListener::instance()
{
static WindowListener *listener = new WindowListener();
return listener;
}
QList<KWayland::Client::PlasmaWindow *> WindowListener::windowsFromStorageId(QString &storageId) const
{
if (!m_windows.contains(storageId)) {
return {};
}
return m_windows[storageId];
}
void WindowListener::windowCreated(KWayland::Client::PlasmaWindow *window)
{
QString storageId = window->appId() + QStringLiteral(".desktop");
// ignore empty windows
if (storageId == ".desktop" || storageId == "org.kde.plasmashell.desktop") {
return;
}
if (!m_windows.contains(storageId)) {
m_windows[storageId] = {};
}
m_windows[storageId].push_back(window);
// listen for window close
connect(window, &KWayland::Client::PlasmaWindow::unmapped, this, [this, storageId]() {
m_windows.remove(storageId);
Q_EMIT windowChanged(storageId);
});
Q_EMIT windowChanged(storageId);
}

View file

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QList>
#include <QObject>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
class WindowListener : public QObject
{
Q_OBJECT
public:
WindowListener(QObject *parent = nullptr);
static WindowListener *instance();
QList<KWayland::Client::PlasmaWindow *> windowsFromStorageId(QString &storageId) const;
public Q_SLOTS:
void windowCreated(KWayland::Client::PlasmaWindow *window);
Q_SIGNALS:
void windowChanged(QString storageId);
private:
KWayland::Client::PlasmaWindowManagement *m_windowManagement = nullptr;
QHash<QString, QList<KWayland::Client::PlasmaWindow *>> m_windows; // <storageId, window>
};