mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
initialstart: Add first login experience
Adds an experience for users on first login, allowing some basic configuration. This is separate from a first start wizard, which would run as a separate user with elevated permissions, and include options that an installer would have.
This commit is contained in:
parent
0d7fde0ea1
commit
5c0fd57600
63 changed files with 2845 additions and 5 deletions
|
|
@ -67,6 +67,7 @@ find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS
|
||||||
NetworkManagerQt
|
NetworkManagerQt
|
||||||
KCMUtils
|
KCMUtils
|
||||||
Package
|
Package
|
||||||
|
DBusAddons
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|
@ -104,6 +105,7 @@ add_subdirectory(kcms)
|
||||||
add_subdirectory(kded)
|
add_subdirectory(kded)
|
||||||
add_subdirectory(kwin)
|
add_subdirectory(kwin)
|
||||||
add_subdirectory(envmanager)
|
add_subdirectory(envmanager)
|
||||||
|
add_subdirectory(initialstart)
|
||||||
|
|
||||||
find_program(PlasmaOpenSettings plasma-open-settings)
|
find_program(PlasmaOpenSettings plasma-open-settings)
|
||||||
set_package_properties(PlasmaOpenSettings PROPERTIES
|
set_package_properties(PlasmaOpenSettings PROPERTIES
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ add_library(ppc-mmqmlplugin)
|
||||||
target_sources(ppc-mmqmlplugin PRIVATE
|
target_sources(ppc-mmqmlplugin PRIVATE
|
||||||
mmqmlplugin.cpp
|
mmqmlplugin.cpp
|
||||||
signalindicator.cpp
|
signalindicator.cpp
|
||||||
|
profilesettings.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(ppc-mmqmlplugin
|
target_link_libraries(ppc-mmqmlplugin
|
||||||
|
|
|
||||||
139
components/mmplugin/profilesettings.cpp
Normal file
139
components/mmplugin/profilesettings.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Devin Lin <espidev@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "profilesettings.h"
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
ProfileSettings::ProfileSettings(QObject *parent,
|
||||||
|
const QString &name,
|
||||||
|
const QString &apn,
|
||||||
|
const QString &user,
|
||||||
|
const QString &password,
|
||||||
|
NetworkManager::GsmSetting::NetworkType networkType,
|
||||||
|
const QString &connectionUni)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_name(name)
|
||||||
|
, m_apn(apn)
|
||||||
|
, m_user(user)
|
||||||
|
, m_password(password)
|
||||||
|
, m_networkType(networkTypeStr(networkType))
|
||||||
|
, m_connectionUni(connectionUni)
|
||||||
|
{
|
||||||
|
setParent(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileSettings::ProfileSettings(QObject *parent, NetworkManager::Setting::Ptr setting, NetworkManager::Connection::Ptr connection)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_connectionUni(connection->uuid())
|
||||||
|
{
|
||||||
|
NetworkManager::GsmSetting::Ptr gsmSetting = setting.staticCast<NetworkManager::GsmSetting>();
|
||||||
|
|
||||||
|
m_name = connection->name();
|
||||||
|
m_apn = gsmSetting->apn();
|
||||||
|
m_user = gsmSetting->username();
|
||||||
|
m_password = gsmSetting->password();
|
||||||
|
m_networkType = networkTypeStr(gsmSetting->networkType());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProfileSettings::name() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProfileSettings::apn() const
|
||||||
|
{
|
||||||
|
return m_apn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileSettings::setApn(const QString &apn)
|
||||||
|
{
|
||||||
|
if (apn != m_apn) {
|
||||||
|
m_apn = apn;
|
||||||
|
Q_EMIT apnChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProfileSettings::user() const
|
||||||
|
{
|
||||||
|
return m_user;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileSettings::setUser(const QString &user)
|
||||||
|
{
|
||||||
|
if (user != m_user) {
|
||||||
|
m_user = user;
|
||||||
|
Q_EMIT userChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProfileSettings::password() const
|
||||||
|
{
|
||||||
|
return m_password;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileSettings::setPassword(const QString &password)
|
||||||
|
{
|
||||||
|
if (password != m_password) {
|
||||||
|
m_password = password;
|
||||||
|
Q_EMIT passwordChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProfileSettings::networkType() const
|
||||||
|
{
|
||||||
|
return m_networkType;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileSettings::setNetworkType(const QString &networkType)
|
||||||
|
{
|
||||||
|
if (networkType != m_networkType) {
|
||||||
|
m_networkType = networkType;
|
||||||
|
Q_EMIT networkTypeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProfileSettings::connectionUni() const
|
||||||
|
{
|
||||||
|
return m_connectionUni;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProfileSettings::networkTypeStr(NetworkManager::GsmSetting::NetworkType networkType)
|
||||||
|
{
|
||||||
|
if (networkType == NetworkManager::GsmSetting::NetworkType::Any) {
|
||||||
|
return QStringLiteral("Any");
|
||||||
|
} else if (networkType == NetworkManager::GsmSetting::NetworkType::GprsEdgeOnly) {
|
||||||
|
return QStringLiteral("Only 2G");
|
||||||
|
} else if (networkType == NetworkManager::GsmSetting::NetworkType::Only3G) {
|
||||||
|
return QStringLiteral("Only 3G");
|
||||||
|
} else if (networkType == NetworkManager::GsmSetting::NetworkType::Only4GLte) {
|
||||||
|
return QStringLiteral("Only 4G");
|
||||||
|
} else if (networkType == NetworkManager::GsmSetting::NetworkType::Prefer2G) {
|
||||||
|
return QStringLiteral("2G");
|
||||||
|
} else if (networkType == NetworkManager::GsmSetting::NetworkType::Prefer3G) {
|
||||||
|
return QStringLiteral("3G/2G");
|
||||||
|
} else if (networkType == NetworkManager::GsmSetting::NetworkType::Prefer4GLte) {
|
||||||
|
return QStringLiteral("4G/3G/2G");
|
||||||
|
}
|
||||||
|
return QStringLiteral("Any");
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkManager::GsmSetting::NetworkType ProfileSettings::networkTypeFlag(const QString &networkType)
|
||||||
|
{
|
||||||
|
if (networkType == QStringLiteral("Any")) {
|
||||||
|
return NetworkManager::GsmSetting::NetworkType::Any;
|
||||||
|
} else if (networkType == QStringLiteral("Only 2G")) {
|
||||||
|
return NetworkManager::GsmSetting::NetworkType::GprsEdgeOnly;
|
||||||
|
} else if (networkType == QStringLiteral("Only 3G")) {
|
||||||
|
return NetworkManager::GsmSetting::NetworkType::Only3G;
|
||||||
|
} else if (networkType == QStringLiteral("Only 4G")) {
|
||||||
|
return NetworkManager::GsmSetting::NetworkType::Only4GLte;
|
||||||
|
} else if (networkType == QStringLiteral("2G")) {
|
||||||
|
return NetworkManager::GsmSetting::NetworkType::Prefer2G;
|
||||||
|
} else if (networkType == QStringLiteral("3G/2G")) {
|
||||||
|
return NetworkManager::GsmSetting::NetworkType::Prefer3G;
|
||||||
|
} else if (networkType == QStringLiteral("4G/3G/2G")) {
|
||||||
|
return NetworkManager::GsmSetting::NetworkType::Prefer4GLte;
|
||||||
|
}
|
||||||
|
return NetworkManager::GsmSetting::NetworkType::Any;
|
||||||
|
}
|
||||||
75
components/mmplugin/profilesettings.h
Normal file
75
components/mmplugin/profilesettings.h
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <espidev@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <NetworkManagerQt/CdmaSetting>
|
||||||
|
#include <NetworkManagerQt/ConnectionSettings>
|
||||||
|
#include <NetworkManagerQt/GsmSetting>
|
||||||
|
#include <NetworkManagerQt/Manager>
|
||||||
|
#include <NetworkManagerQt/ModemDevice>
|
||||||
|
#include <NetworkManagerQt/Settings>
|
||||||
|
|
||||||
|
#include <ModemManagerQt/GenericTypes>
|
||||||
|
#include <ModemManagerQt/Manager>
|
||||||
|
#include <ModemManagerQt/Modem3Gpp>
|
||||||
|
#include <ModemManagerQt/ModemDevice>
|
||||||
|
|
||||||
|
class ProfileSettings : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
|
Q_PROPERTY(QString apn READ apn WRITE setApn NOTIFY apnChanged)
|
||||||
|
Q_PROPERTY(QString user READ user WRITE setUser NOTIFY userChanged)
|
||||||
|
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
|
||||||
|
Q_PROPERTY(QString networkType READ networkType WRITE setNetworkType NOTIFY networkTypeChanged)
|
||||||
|
Q_PROPERTY(QString connectionUni READ connectionUni NOTIFY connectionUniChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
ProfileSettings(QObject *parent = nullptr)
|
||||||
|
: QObject{parent}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
ProfileSettings(QObject *parent,
|
||||||
|
const QString &name,
|
||||||
|
const QString &apn,
|
||||||
|
const QString &user,
|
||||||
|
const QString &password,
|
||||||
|
NetworkManager::GsmSetting::NetworkType networkType,
|
||||||
|
const QString &connectionUni);
|
||||||
|
ProfileSettings(QObject *parent, NetworkManager::Setting::Ptr settings, NetworkManager::Connection::Ptr connection);
|
||||||
|
|
||||||
|
QString name() const;
|
||||||
|
QString apn() const;
|
||||||
|
void setApn(const QString &apn);
|
||||||
|
QString user() const;
|
||||||
|
void setUser(const QString &user);
|
||||||
|
QString password() const;
|
||||||
|
void setPassword(const QString &password);
|
||||||
|
QString networkType() const;
|
||||||
|
void setNetworkType(const QString &ipType);
|
||||||
|
QString connectionUni() const;
|
||||||
|
|
||||||
|
// utilities
|
||||||
|
static QString networkTypeStr(NetworkManager::GsmSetting::NetworkType networkType);
|
||||||
|
static NetworkManager::GsmSetting::NetworkType networkTypeFlag(const QString &networkType);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void nameChanged();
|
||||||
|
void apnChanged();
|
||||||
|
void userChanged();
|
||||||
|
void passwordChanged();
|
||||||
|
void networkTypeChanged();
|
||||||
|
void connectionUniChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_name;
|
||||||
|
QString m_apn;
|
||||||
|
QString m_user;
|
||||||
|
QString m_password;
|
||||||
|
QString m_networkType;
|
||||||
|
QString m_connectionUni;
|
||||||
|
};
|
||||||
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
#include <KUser>
|
#include <KUser>
|
||||||
|
|
||||||
|
#include "signalindicator.h"
|
||||||
|
|
||||||
SignalIndicator::SignalIndicator(QObject *parent)
|
SignalIndicator::SignalIndicator(QObject *parent)
|
||||||
: QObject{parent}
|
: QObject{parent}
|
||||||
, m_nmModem{nullptr}
|
, m_nmModem{nullptr}
|
||||||
|
|
@ -139,6 +141,160 @@ void SignalIndicator::setMobileDataEnabled(bool enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SignalIndicator::activeConnectionUni() const
|
||||||
|
{
|
||||||
|
if (m_nmModem && m_nmModem->activeConnection() && m_nmModem->activeConnection()->connection()) {
|
||||||
|
return m_nmModem->activeConnection()->connection()->uuid();
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ProfileSettings *> &SignalIndicator::profileList()
|
||||||
|
{
|
||||||
|
return m_profileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalIndicator::refreshProfiles()
|
||||||
|
{
|
||||||
|
m_profileList.clear();
|
||||||
|
|
||||||
|
if (!m_nmModem) {
|
||||||
|
Q_EMIT profileListChanged();
|
||||||
|
qWarning() << "No NetworkManager modem found, cannot refresh profiles.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto connection : m_nmModem->availableConnections()) {
|
||||||
|
for (auto setting : connection->settings()->settings()) {
|
||||||
|
if (setting.dynamicCast<NetworkManager::GsmSetting>()) {
|
||||||
|
m_profileList.append(new ProfileSettings(this, setting.dynamicCast<NetworkManager::GsmSetting>(), connection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Q_EMIT profileListChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalIndicator::activateProfile(const QString &connectionUni)
|
||||||
|
{
|
||||||
|
if (!m_nmModem) {
|
||||||
|
qWarning() << "Cannot activate profile since there is no NetworkManager modem";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << QStringLiteral("Activating profile on modem") << m_nmModem->uni() << QStringLiteral("for connection") << connectionUni << ".";
|
||||||
|
|
||||||
|
NetworkManager::Connection::Ptr con;
|
||||||
|
|
||||||
|
// disable autoconnect for all other connections
|
||||||
|
for (auto connection : m_nmModem->availableConnections()) {
|
||||||
|
if (connection->uuid() == connectionUni) {
|
||||||
|
connection->settings()->setAutoconnect(true);
|
||||||
|
con = connection;
|
||||||
|
} else {
|
||||||
|
connection->settings()->setAutoconnect(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!con) {
|
||||||
|
qDebug() << QStringLiteral("Connection") << connectionUni << QStringLiteral("not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// activate connection manually
|
||||||
|
// despite the documentation saying otherwise, activateConnection seems to need the DBus path, not uuid of the connection
|
||||||
|
QDBusPendingReply<QDBusObjectPath> reply = NetworkManager::activateConnection(con->path(), m_nmModem->uni(), {});
|
||||||
|
reply.waitForFinished();
|
||||||
|
if (reply.isError()) {
|
||||||
|
qWarning() << QStringLiteral("Error activating connection:") << reply.error().message();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalIndicator::addProfile(const QString &name, const QString &apn, const QString &username, const QString &password, const QString &networkType)
|
||||||
|
{
|
||||||
|
if (!m_nmModem) {
|
||||||
|
qWarning() << "Cannot add profile since there is no NetworkManager modem";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkManager::ConnectionSettings::Ptr settings{new NetworkManager::ConnectionSettings(NetworkManager::ConnectionSettings::Gsm)};
|
||||||
|
settings->setId(name);
|
||||||
|
settings->setUuid(NetworkManager::ConnectionSettings::createNewUuid());
|
||||||
|
settings->setAutoconnect(true);
|
||||||
|
settings->addToPermissions(KUser().loginName(), QString());
|
||||||
|
|
||||||
|
NetworkManager::GsmSetting::Ptr gsmSetting = settings->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
|
||||||
|
gsmSetting->setApn(apn);
|
||||||
|
gsmSetting->setUsername(username);
|
||||||
|
gsmSetting->setPassword(password);
|
||||||
|
gsmSetting->setPasswordFlags(password.isEmpty() ? NetworkManager::Setting::NotRequired : NetworkManager::Setting::AgentOwned);
|
||||||
|
gsmSetting->setNetworkType(ProfileSettings::networkTypeFlag(networkType));
|
||||||
|
|
||||||
|
gsmSetting->setInitialized(true);
|
||||||
|
|
||||||
|
QDBusPendingReply<QDBusObjectPath> reply = NetworkManager::addAndActivateConnection(settings->toMap(), m_nmModem->uni(), {});
|
||||||
|
reply.waitForFinished();
|
||||||
|
if (reply.isError()) {
|
||||||
|
qWarning() << "Error adding connection:" << reply.error().message();
|
||||||
|
} else {
|
||||||
|
qDebug() << "Successfully added a new connection" << name << "with APN" << apn << ".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalIndicator::removeProfile(const QString &connectionUni)
|
||||||
|
{
|
||||||
|
NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni);
|
||||||
|
if (!con) {
|
||||||
|
qWarning() << "Could not find connection" << connectionUni << "to update!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusPendingReply reply = con->remove();
|
||||||
|
reply.waitForFinished();
|
||||||
|
if (reply.isError()) {
|
||||||
|
qWarning() << "Error removing connection" << reply.error().message();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalIndicator::updateProfile(const QString &connectionUni,
|
||||||
|
const QString &name,
|
||||||
|
const QString &apn,
|
||||||
|
const QString &username,
|
||||||
|
const QString &password,
|
||||||
|
const QString &networkType)
|
||||||
|
{
|
||||||
|
NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni);
|
||||||
|
if (!con) {
|
||||||
|
qWarning() << "Could not find connection" << connectionUni << "to update!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkManager::ConnectionSettings::Ptr conSettings = con->settings();
|
||||||
|
if (!conSettings) {
|
||||||
|
qWarning() << "Could not find connection settings for" << connectionUni << "to update!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
conSettings->setId(name);
|
||||||
|
|
||||||
|
NetworkManager::GsmSetting::Ptr gsmSetting = conSettings->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
|
||||||
|
gsmSetting->setApn(apn);
|
||||||
|
gsmSetting->setUsername(username);
|
||||||
|
gsmSetting->setPassword(password);
|
||||||
|
gsmSetting->setPasswordFlags(password.isEmpty() ? NetworkManager::Setting::NotRequired : NetworkManager::Setting::AgentOwned);
|
||||||
|
gsmSetting->setNetworkType(ProfileSettings::networkTypeFlag(networkType));
|
||||||
|
|
||||||
|
gsmSetting->setInitialized(true);
|
||||||
|
|
||||||
|
QDBusPendingReply reply = con->update(conSettings->toMap());
|
||||||
|
reply.waitForFinished();
|
||||||
|
if (reply.isError()) {
|
||||||
|
qWarning() << "Error updating connection settings for" << connectionUni << ":" << reply.error().message() << ".";
|
||||||
|
} else {
|
||||||
|
qDebug() << "Successfully updated connection settings" << connectionUni << ".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SignalIndicator::updateModemManagerModem()
|
void SignalIndicator::updateModemManagerModem()
|
||||||
{
|
{
|
||||||
m_modemDevice = nullptr;
|
m_modemDevice = nullptr;
|
||||||
|
|
@ -188,6 +344,14 @@ void SignalIndicator::updateNetworkManagerModem()
|
||||||
connect(m_nmModem.get(), &NetworkManager::Device::stateChanged, this, &SignalIndicator::mobileDataEnabledChanged);
|
connect(m_nmModem.get(), &NetworkManager::Device::stateChanged, this, &SignalIndicator::mobileDataEnabledChanged);
|
||||||
connect(m_nmModem.get(), &NetworkManager::Device::availableConnectionAppeared, this, &SignalIndicator::mobileDataEnabledChanged);
|
connect(m_nmModem.get(), &NetworkManager::Device::availableConnectionAppeared, this, &SignalIndicator::mobileDataEnabledChanged);
|
||||||
connect(m_nmModem.get(), &NetworkManager::Device::availableConnectionDisappeared, this, &SignalIndicator::mobileDataEnabledChanged);
|
connect(m_nmModem.get(), &NetworkManager::Device::availableConnectionDisappeared, this, &SignalIndicator::mobileDataEnabledChanged);
|
||||||
|
|
||||||
|
connect(m_nmModem.data(), &NetworkManager::ModemDevice::availableConnectionChanged, this, &SignalIndicator::refreshProfiles);
|
||||||
|
connect(m_nmModem.data(), &NetworkManager::ModemDevice::activeConnectionChanged, this, [this]() -> void {
|
||||||
|
refreshProfiles();
|
||||||
|
Q_EMIT activeConnectionUniChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshProfiles();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "profilesettings.h"
|
||||||
|
|
||||||
// We make the assumption that there is only one modem.
|
// We make the assumption that there is only one modem.
|
||||||
class SignalIndicator : public QObject
|
class SignalIndicator : public QObject
|
||||||
{
|
{
|
||||||
|
|
@ -27,6 +29,8 @@ class SignalIndicator : public QObject
|
||||||
Q_PROPERTY(bool mobileDataEnabled READ mobileDataEnabled WRITE setMobileDataEnabled NOTIFY mobileDataEnabledChanged)
|
Q_PROPERTY(bool mobileDataEnabled READ mobileDataEnabled WRITE setMobileDataEnabled NOTIFY mobileDataEnabledChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool needsAPNAdded READ needsAPNAdded NOTIFY mobileDataEnabledChanged)
|
Q_PROPERTY(bool needsAPNAdded READ needsAPNAdded NOTIFY mobileDataEnabledChanged)
|
||||||
|
Q_PROPERTY(QList<ProfileSettings *> profiles READ profileList NOTIFY profileListChanged)
|
||||||
|
Q_PROPERTY(QString activeConnectionUni READ activeConnectionUni NOTIFY activeConnectionUniChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SignalIndicator(QObject *parent = nullptr);
|
SignalIndicator(QObject *parent = nullptr);
|
||||||
|
|
@ -39,9 +43,23 @@ public:
|
||||||
bool mobileDataSupported() const;
|
bool mobileDataSupported() const;
|
||||||
bool mobileDataEnabled() const;
|
bool mobileDataEnabled() const;
|
||||||
bool needsAPNAdded() const;
|
bool needsAPNAdded() const;
|
||||||
|
QString activeConnectionUni() const;
|
||||||
|
|
||||||
void setMobileDataEnabled(bool enabled);
|
void setMobileDataEnabled(bool enabled);
|
||||||
|
|
||||||
|
// connection profiles
|
||||||
|
QList<ProfileSettings *> &profileList();
|
||||||
|
void refreshProfiles();
|
||||||
|
Q_INVOKABLE void activateProfile(const QString &connectionUni);
|
||||||
|
Q_INVOKABLE void addProfile(const QString &name, const QString &apn, const QString &username, const QString &password, const QString &networkType);
|
||||||
|
Q_INVOKABLE void removeProfile(const QString &connectionUni);
|
||||||
|
Q_INVOKABLE void updateProfile(const QString &connectionUni,
|
||||||
|
const QString &name,
|
||||||
|
const QString &apn,
|
||||||
|
const QString &username,
|
||||||
|
const QString &password,
|
||||||
|
const QString &networkType);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void strengthChanged();
|
void strengthChanged();
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
|
|
@ -50,6 +68,8 @@ Q_SIGNALS:
|
||||||
void simEmptyChanged();
|
void simEmptyChanged();
|
||||||
void mobileDataSupportedChanged();
|
void mobileDataSupportedChanged();
|
||||||
void mobileDataEnabledChanged();
|
void mobileDataEnabledChanged();
|
||||||
|
void profileListChanged();
|
||||||
|
void activeConnectionUniChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NetworkManager::ModemDevice::Ptr m_nmModem;
|
NetworkManager::ModemDevice::Ptr m_nmModem;
|
||||||
|
|
@ -57,6 +77,8 @@ private:
|
||||||
ModemManager::Modem::Ptr m_modem;
|
ModemManager::Modem::Ptr m_modem;
|
||||||
ModemManager::Modem3gpp::Ptr m_3gppModem;
|
ModemManager::Modem3gpp::Ptr m_3gppModem;
|
||||||
|
|
||||||
|
QList<ProfileSettings *> m_profileList;
|
||||||
|
|
||||||
void updateModemManagerModem();
|
void updateModemManagerModem();
|
||||||
void updateNetworkManagerModem();
|
void updateNetworkManagerModem();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
|
||||||
const QString CONFIG_FILE = QStringLiteral("plasmamobilerc");
|
const QString CONFIG_FILE = QStringLiteral("plasmamobilerc");
|
||||||
const QString INITIAL_START_CONFIG_GROUP = QStringLiteral("InitialStart");
|
|
||||||
const QString SAVED_CONFIG_GROUP = QStringLiteral("SavedConfig");
|
const QString SAVED_CONFIG_GROUP = QStringLiteral("SavedConfig");
|
||||||
|
|
||||||
const QString MOBILE_LOOK_AND_FEEL = QStringLiteral("org.kde.breeze.mobile");
|
const QString MOBILE_LOOK_AND_FEEL = QStringLiteral("org.kde.breeze.mobile");
|
||||||
|
|
|
||||||
30
initialstart/CMakeLists.txt
Normal file
30
initialstart/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
set(plasma-mobile-initial-start_SRCS
|
||||||
|
main.cpp
|
||||||
|
wizard.cpp
|
||||||
|
settings.cpp
|
||||||
|
utils.h
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_add_resources(RESOURCES resources.qrc)
|
||||||
|
|
||||||
|
add_subdirectory(modules)
|
||||||
|
|
||||||
|
add_executable(plasma-mobile-initial-start ${plasma-mobile-initial-start_SRCS} ${RESOURCES})
|
||||||
|
target_link_libraries(plasma-mobile-initial-start
|
||||||
|
Qt::Qml
|
||||||
|
Qt::Gui
|
||||||
|
Qt::Widgets
|
||||||
|
Qt::Quick
|
||||||
|
KF6::I18n
|
||||||
|
KF6::ConfigCore
|
||||||
|
KF6::ConfigGui
|
||||||
|
KF6::CoreAddons
|
||||||
|
KF6::DBusAddons
|
||||||
|
KF6::Package
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(plasma-mobile-initial-start PRIVATE ${CMAKE_BINARY_DIR})
|
||||||
|
install(TARGETS plasma-mobile-initial-start ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
6
initialstart/Messages.sh
Normal file
6
initialstart/Messages.sh
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
$XGETTEXT `find . -name \*.js -o -name \*.qml -o -name \*.cpp` -o $podir/plasma_org.kde.plasma.mobileinitialstart.pot
|
||||||
BIN
initialstart/assets/background.png
Normal file
BIN
initialstart/assets/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 MiB |
3
initialstart/assets/background.png.license
Normal file
3
initialstart/assets/background.png.license
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
SPDX-FileCopyrightText: None
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
72
initialstart/main.cpp
Normal file
72
initialstart/main.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QQmlApplicationEngine>
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <KAboutData>
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
#include "settings.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "wizard.h"
|
||||||
|
|
||||||
|
QCommandLineParser *createParser()
|
||||||
|
{
|
||||||
|
QCommandLineParser *parser = new QCommandLineParser;
|
||||||
|
parser->addOption(QCommandLineOption(QStringLiteral("test-wizard"), i18n("Opens the initial start wizard without modifying configuration")));
|
||||||
|
parser->addVersionOption();
|
||||||
|
parser->addHelpOption();
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
// parse command
|
||||||
|
QScopedPointer<QCommandLineParser> parser{createParser()};
|
||||||
|
parser->process(app);
|
||||||
|
|
||||||
|
bool testWizard = parser->isSet(QStringLiteral("test-wizard"));
|
||||||
|
|
||||||
|
if (!testWizard) {
|
||||||
|
// if the wizard has already been run, or we aren't in plasma mobile
|
||||||
|
if (!Settings::self()->shouldStartWizard()) {
|
||||||
|
qDebug() << "Wizard will not be started since either it has already been run, or the current session is not Plasma Mobile.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start wizard
|
||||||
|
KLocalizedString::setApplicationDomain("plasma-mobile-initial-start");
|
||||||
|
KAboutData aboutData(QStringLiteral("plasma-mobile-initial-start"),
|
||||||
|
QStringLiteral("Initial Start"),
|
||||||
|
QStringLiteral(PLASMA_MOBILE_VERSION_STRING),
|
||||||
|
QStringLiteral(""),
|
||||||
|
KAboutLicense::GPL,
|
||||||
|
i18n("© 2023 KDE Community"));
|
||||||
|
aboutData.addAuthor(i18n("Devin Lin"), QString(), QStringLiteral("devin@kde.org"));
|
||||||
|
KAboutData::setApplicationData(aboutData);
|
||||||
|
|
||||||
|
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
|
||||||
|
engine->rootContext()->setContextObject(new KLocalizedContext{engine});
|
||||||
|
|
||||||
|
Wizard *wizard = new Wizard{nullptr, engine};
|
||||||
|
wizard->setTestingMode(testWizard);
|
||||||
|
wizard->load();
|
||||||
|
|
||||||
|
qmlRegisterSingletonType<Wizard>("initialstart", 1, 0, "Wizard", [wizard](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||||
|
return wizard;
|
||||||
|
});
|
||||||
|
|
||||||
|
engine->load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
|
||||||
|
|
||||||
|
app.setWindowIcon(QIcon::fromTheme(QStringLiteral("start-here-symbolic")));
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
8
initialstart/modules/CMakeLists.txt
Normal file
8
initialstart/modules/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
plasma_install_package(finished org.kde.plasma.mobileinitialstart.finished mobileinitialstart)
|
||||||
|
add_subdirectory(cellular)
|
||||||
|
add_subdirectory(prepare)
|
||||||
|
add_subdirectory(time)
|
||||||
|
add_subdirectory(wifi)
|
||||||
33
initialstart/modules/cellular/CMakeLists.txt
Normal file
33
initialstart/modules/cellular/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
set(cellularplugin_SRCS
|
||||||
|
cellularplugin.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(cellularplugin ${cellularplugin_SRCS})
|
||||||
|
|
||||||
|
target_link_libraries(cellularplugin
|
||||||
|
Qt::Core
|
||||||
|
Qt::DBus
|
||||||
|
Qt::Quick
|
||||||
|
KF6::CoreAddons
|
||||||
|
KF6::ConfigCore
|
||||||
|
KF6::ConfigGui
|
||||||
|
KF6::I18n
|
||||||
|
KF6::Notifications
|
||||||
|
KF6::NetworkManagerQt
|
||||||
|
KF6::ModemManagerQt
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET cellularplugin PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/plasma/mobileinitialstart/cellular)
|
||||||
|
file(COPY qmldir DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/plasma/mobileinitialstart/cellular)
|
||||||
|
|
||||||
|
install(TARGETS cellularplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/mobileinitialstart/cellular)
|
||||||
|
install(FILES qmldir ${qml_SRC} DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/mobileinitialstart/cellular)
|
||||||
|
|
||||||
|
plasma_install_package(package org.kde.plasma.mobileinitialstart.cellular mobileinitialstart)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
12
initialstart/modules/cellular/cellularplugin.cpp
Normal file
12
initialstart/modules/cellular/cellularplugin.cpp
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "cellularplugin.h"
|
||||||
|
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QQuickItem>
|
||||||
|
|
||||||
|
void CellularPlugin::registerTypes(const char *uri)
|
||||||
|
{
|
||||||
|
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.mobileinitialstart.cellular"));
|
||||||
|
}
|
||||||
18
initialstart/modules/cellular/cellularplugin.h
Normal file
18
initialstart/modules/cellular/cellularplugin.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQmlExtensionPlugin>
|
||||||
|
|
||||||
|
class CellularPlugin : public QQmlExtensionPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
|
||||||
|
|
||||||
|
public:
|
||||||
|
void registerTypes(const char *uri) override;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 Devin Lin <espidev@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Layouts 1.2
|
||||||
|
import QtQuick.Controls 2.12 as Controls
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.19 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.plasma.mm 1.0 as PlasmaMM
|
||||||
|
|
||||||
|
Kirigami.Dialog {
|
||||||
|
id: dialog
|
||||||
|
title: i18n("Edit APN")
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
property var profile
|
||||||
|
|
||||||
|
standardButtons: Controls.Dialog.Ok | Controls.Dialog.Cancel
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
if (profile === null) { // create new profile
|
||||||
|
PlasmaMM.SignalIndicator.addProfile(profileName.text, profileApn.text, profileUsername.text, profilePassword.text, profileNetworkType.value);
|
||||||
|
} else { // edit existing profile
|
||||||
|
PlasmaMM.SignalIndicator.updateProfile(profile.connectionUni, profileName.text, profileApn.text, profileUsername.text, profilePassword.text, profileNetworkType.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preferredWidth: Kirigami.Units.gridUnit * 20
|
||||||
|
padding: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wideMode: false
|
||||||
|
|
||||||
|
Controls.TextField {
|
||||||
|
id: profileName
|
||||||
|
Kirigami.FormData.label: i18n("Name")
|
||||||
|
text: profile !== null ? profile.name : ""
|
||||||
|
}
|
||||||
|
Controls.TextField {
|
||||||
|
id: profileApn
|
||||||
|
Kirigami.FormData.label: i18n("APN")
|
||||||
|
text: profile != null ? profile.apn : ""
|
||||||
|
}
|
||||||
|
Controls.TextField {
|
||||||
|
id: profileUsername
|
||||||
|
Kirigami.FormData.label: i18n("Username")
|
||||||
|
text: profile != null ? profile.user : ""
|
||||||
|
}
|
||||||
|
Controls.TextField {
|
||||||
|
id: profilePassword
|
||||||
|
Kirigami.FormData.label: i18n("Password")
|
||||||
|
text: profile != null ? profile.password : ""
|
||||||
|
}
|
||||||
|
Controls.ComboBox {
|
||||||
|
id: profileNetworkType
|
||||||
|
Kirigami.FormData.label: i18n("Network type")
|
||||||
|
model: [i18n("4G/3G/2G"), i18n("3G/2G"), i18n("2G"), i18n("Only 4G"), i18n("Only 3G"), i18n("Only 2G"), i18n("Any")]
|
||||||
|
Component.onCompleted: if (profile !== null) {
|
||||||
|
currentIndex = indexOfValue(profile.networkType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
152
initialstart/modules/cellular/package/contents/ui/main.qml
Normal file
152
initialstart/modules/cellular/package/contents/ui/main.qml
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||||
|
import org.kde.plasma.mm 1.0 as PlasmaMM
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property string name: i18n("Cellular")
|
||||||
|
|
||||||
|
readonly property real cardWidth: Math.min(Kirigami.Units.gridUnit * 30, root.width - Kirigami.Units.gridUnit * 2)
|
||||||
|
|
||||||
|
function toggleMobileData() {
|
||||||
|
if (PlasmaMM.SignalIndicator.needsAPNAdded || !PlasmaMM.SignalIndicator.mobileDataSupported) {
|
||||||
|
// open settings if unable to toggle mobile data
|
||||||
|
MobileShell.ShellUtil.executeCommand("plasma-open-settings kcm_cellular_network");
|
||||||
|
} else {
|
||||||
|
PlasmaMM.SignalIndicator.mobileDataEnabled = !PlasmaMM.SignalIndicator.mobileDataEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditProfileDialog {
|
||||||
|
id: profileDialog
|
||||||
|
profile: null
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
topMargin: Kirigami.Units.gridUnit
|
||||||
|
bottomMargin: Kirigami.Units.largeSpacing
|
||||||
|
}
|
||||||
|
width: root.width
|
||||||
|
spacing: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: {
|
||||||
|
if (!PlasmaMM.SignalIndicator.modemAvailable) {
|
||||||
|
return i18n("Your device does not have a modem available.");
|
||||||
|
} else if (PlasmaMM.SignalIndicator.needsAPNAdded) {
|
||||||
|
return i18n("Please configure your APN below for mobile data, further information will be available with your carrier.");
|
||||||
|
} else if (PlasmaMM.SignalIndicator.mobileDataSupported) {
|
||||||
|
return i18n("You are connected to the mobile network.");
|
||||||
|
} else if (PlasmaMM.SignalIndicator.simEmpty) {
|
||||||
|
return i18n("Please insert a SIM card into your device.");
|
||||||
|
} else {
|
||||||
|
return i18n("Your device does not have a modem available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileForm.FormCard {
|
||||||
|
visible: PlasmaMM.SignalIndicator.modemAvailable && PlasmaMM.SignalIndicator.mobileDataSupported
|
||||||
|
maximumWidth: root.cardWidth
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
MobileForm.FormSwitchDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Mobile Data")
|
||||||
|
checked: PlasmaMM.SignalIndicator.mobileDataEnabled
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked !== PlasmaMM.SignalIndicator.mobileDataEnabled) {
|
||||||
|
root.toggleMobileData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileForm.FormCard {
|
||||||
|
visible: PlasmaMM.SignalIndicator.modemAvailable && !PlasmaMM.SignalIndicator.simEmpty
|
||||||
|
maximumWidth: root.cardWidth
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: listView
|
||||||
|
currentIndex: -1
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
model: PlasmaMM.SignalIndicator.profiles
|
||||||
|
|
||||||
|
delegate: MobileForm.FormRadioDelegate {
|
||||||
|
width: listView.width
|
||||||
|
text: modelData.name
|
||||||
|
description: modelData.apn
|
||||||
|
checked: modem.activeConnectionUni == modelData.connectionUni
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked) {
|
||||||
|
PlasmaMM.SignalIndicator.activateProfile(modelData.connectionUni);
|
||||||
|
checked = Qt.binding(() => { return modem.activeConnectionUni == modelData.connectionUni });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trailing: RowLayout {
|
||||||
|
ToolButton {
|
||||||
|
icon.name: "entry-edit"
|
||||||
|
text: i18n("Edit")
|
||||||
|
onClicked: {
|
||||||
|
profileDialog.profile = modelData;
|
||||||
|
profileDialog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
icon.name: "delete"
|
||||||
|
text: i18n("Delete")
|
||||||
|
onClicked: PlasmaMM.SignalIndicator.removeProfile(modelData.connectionUni)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileForm.FormButtonDelegate {
|
||||||
|
icon.name: "list-add"
|
||||||
|
text: i18n("Add APN")
|
||||||
|
onClicked: {
|
||||||
|
profileDialog.profile = null;
|
||||||
|
profileDialog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
20
initialstart/modules/cellular/package/metadata.json
Normal file
20
initialstart/modules/cellular/package/metadata.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"KPackageStructure": "KPackage/GenericQML",
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Email": "devin@kde.org",
|
||||||
|
"Name": "Devin Lin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Description": "Cellular initialstart module for Plasma Mobile",
|
||||||
|
"Id": "org.kde.plasma.mobileinitialstart.cellular",
|
||||||
|
"License": "GPL-2.0+",
|
||||||
|
"Name": "Cellular",
|
||||||
|
"ServiceTypes": [
|
||||||
|
"KPackage/GenericQML"
|
||||||
|
],
|
||||||
|
"Version": "0.1",
|
||||||
|
"Website": "https://kde.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
initialstart/modules/cellular/qmldir
Normal file
7
initialstart/modules/cellular/qmldir
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
module org.kde.plasma.mobileinitialstart.cellular
|
||||||
|
plugin cellularplugin
|
||||||
|
classname CellularPlugin
|
||||||
|
|
||||||
BIN
initialstart/modules/finished/contents/ui/konqi-calling.png
Normal file
BIN
initialstart/modules/finished/contents/ui/konqi-calling.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
|
|
@ -0,0 +1,3 @@
|
||||||
|
SPDX-FileCopyrightText: None
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
36
initialstart/modules/finished/contents/ui/main.qml
Normal file
36
initialstart/modules/finished/contents/ui/main.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string name: i18n("Complete!")
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||||
|
text: i18n("Your device is now ready. <br /><br />Enjoy <b>Plasma 6.0</b>!")
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
source: "konqi-calling.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
20
initialstart/modules/finished/metadata.json
Normal file
20
initialstart/modules/finished/metadata.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"KPackageStructure": "KPackage/GenericQML",
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Email": "devin@kde.org",
|
||||||
|
"Name": "Devin Lin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Description": "Finished initialstart module for Plasma Mobile",
|
||||||
|
"Id": "org.kde.plasma.mobileinitialstart.finished",
|
||||||
|
"License": "GPL-2.0+",
|
||||||
|
"Name": "Finished",
|
||||||
|
"ServiceTypes": [
|
||||||
|
"KPackage/GenericQML"
|
||||||
|
],
|
||||||
|
"Version": "0.1",
|
||||||
|
"Website": "https://kde.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
31
initialstart/modules/prepare/CMakeLists.txt
Normal file
31
initialstart/modules/prepare/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
set(prepareplugin_SRCS
|
||||||
|
prepareplugin.cpp
|
||||||
|
prepareutil.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(prepareplugin ${prepareplugin_SRCS})
|
||||||
|
|
||||||
|
target_link_libraries(prepareplugin
|
||||||
|
Qt::Core
|
||||||
|
Qt::DBus
|
||||||
|
Qt::Quick
|
||||||
|
KF6::CoreAddons
|
||||||
|
KF6::ConfigCore
|
||||||
|
KF6::ConfigGui
|
||||||
|
KF6::I18n
|
||||||
|
KF6::Notifications
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET prepareplugin PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/plasma/mobileinitialstart/prepare)
|
||||||
|
file(COPY qmldir DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/plasma/mobileinitialstart/prepare)
|
||||||
|
|
||||||
|
install(TARGETS prepareplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/mobileinitialstart/prepare)
|
||||||
|
install(FILES qmldir ${qml_SRC} DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/mobileinitialstart/prepare)
|
||||||
|
|
||||||
|
plasma_install_package(package org.kde.plasma.mobileinitialstart.prepare mobileinitialstart)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
163
initialstart/modules/prepare/package/contents/ui/main.qml
Normal file
163
initialstart/modules/prepare/package/contents/ui/main.qml
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||||
|
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||||
|
import org.kde.plasma.mobileinitialstart.prepare 1.0 as Prepare
|
||||||
|
import org.kde.plasma.plasma5support 2.0 as P5Support
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property string name: i18n("Before we get started…")
|
||||||
|
|
||||||
|
readonly property real cardWidth: Math.min(Kirigami.Units.gridUnit * 30, root.width - Kirigami.Units.gridUnit * 2)
|
||||||
|
|
||||||
|
// brightness controls
|
||||||
|
property int screenBrightness: 0
|
||||||
|
property bool disableBrightnessUpdate: true
|
||||||
|
readonly property int maximumScreenBrightness: pmSource.data["PowerDevil"] ? pmSource.data["PowerDevil"]["Maximum Screen Brightness"] || 0 : 0
|
||||||
|
property QtObject updateScreenBrightnessJob
|
||||||
|
|
||||||
|
function updateBrightnessUI() {
|
||||||
|
if (updateScreenBrightnessJob)
|
||||||
|
return;
|
||||||
|
|
||||||
|
root.disableBrightnessUpdate = true;
|
||||||
|
root.screenBrightness = pmSource.data["PowerDevil"]["Screen Brightness"];
|
||||||
|
root.disableBrightnessUpdate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onScreenBrightnessChanged: {
|
||||||
|
brightnessSlider.value = root.screenBrightness
|
||||||
|
|
||||||
|
if (!disableBrightnessUpdate) {
|
||||||
|
const service = pmSource.serviceForSource("PowerDevil");
|
||||||
|
const operation = service.operationDescription("setBrightness");
|
||||||
|
operation.brightness = screenBrightness;
|
||||||
|
operation.silent = true; // don't show OSD
|
||||||
|
|
||||||
|
updateScreenBrightnessJob = service.startOperationCall(operation);
|
||||||
|
updateScreenBrightnessJob.finished.connect(function (job) {
|
||||||
|
root.updateBrightnessUI();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
P5Support.DataSource {
|
||||||
|
id: pmSource
|
||||||
|
engine: "powermanagement"
|
||||||
|
connectedSources: ["PowerDevil"]
|
||||||
|
onSourceAdded: if (source === "PowerDevil") {
|
||||||
|
disconnectSource(source);
|
||||||
|
connectSource(source);
|
||||||
|
}
|
||||||
|
onDataChanged: root.updateBrightnessUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
topMargin: Kirigami.Units.gridUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
contentWidth: -1
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: root.width
|
||||||
|
spacing: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: i18n("Adjust the screen brightness to be comfortable for the installation process.")
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileForm.FormCard {
|
||||||
|
maximumWidth: root.cardWidth
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
MobileForm.AbstractFormDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
background: Item {}
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
source: "brightness-low"
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
id: brightnessSlider
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 1
|
||||||
|
to: root.maximumScreenBrightness
|
||||||
|
value: root.screenBrightness
|
||||||
|
onMoved: root.screenBrightness = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
source: "brightness-high"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: i18n("Adjust the size of elements on the screen.")
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileForm.FormCard {
|
||||||
|
maximumWidth: root.cardWidth
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
id: displayScaling
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("Display Scaling")
|
||||||
|
displayMode: MobileForm.FormComboBoxDelegate.Dialog
|
||||||
|
currentIndex: Prepare.PrepareUtil.scalingOptions.indexOf(Prepare.PrepareUtil.scaling.toString() + "%");
|
||||||
|
model: Prepare.PrepareUtil.scalingOptions
|
||||||
|
|
||||||
|
// remove % suffix
|
||||||
|
onCurrentValueChanged: Prepare.PrepareUtil.scaling = parseInt(currentValue.substring(0, currentValue.length - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
initialstart/modules/prepare/package/metadata.json
Normal file
20
initialstart/modules/prepare/package/metadata.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"KPackageStructure": "KPackage/GenericQML",
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Email": "devin@kde.org",
|
||||||
|
"Name": "Devin Lin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Description": "Prepare initialstart module for Plasma Mobile",
|
||||||
|
"Id": "org.kde.plasma.mobileinitialstart.prepare",
|
||||||
|
"License": "GPL-2.0+",
|
||||||
|
"Name": "Prepare",
|
||||||
|
"ServiceTypes": [
|
||||||
|
"KPackage/GenericQML"
|
||||||
|
],
|
||||||
|
"Version": "0.1",
|
||||||
|
"Website": "https://kde.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
initialstart/modules/prepare/prepareplugin.cpp
Normal file
18
initialstart/modules/prepare/prepareplugin.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "prepareplugin.h"
|
||||||
|
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QQuickItem>
|
||||||
|
|
||||||
|
#include "prepareutil.h"
|
||||||
|
|
||||||
|
void PreparePlugin::registerTypes(const char *uri)
|
||||||
|
{
|
||||||
|
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.mobileinitialstart.prepare"));
|
||||||
|
|
||||||
|
qmlRegisterSingletonType<PrepareUtil>(uri, 1, 0, "PrepareUtil", [](QQmlEngine *, QJSEngine *) {
|
||||||
|
return new PrepareUtil;
|
||||||
|
});
|
||||||
|
}
|
||||||
18
initialstart/modules/prepare/prepareplugin.h
Normal file
18
initialstart/modules/prepare/prepareplugin.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQmlExtensionPlugin>
|
||||||
|
|
||||||
|
class PreparePlugin : public QQmlExtensionPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
|
||||||
|
|
||||||
|
public:
|
||||||
|
void registerTypes(const char *uri) override;
|
||||||
|
};
|
||||||
69
initialstart/modules/prepare/prepareutil.cpp
Normal file
69
initialstart/modules/prepare/prepareutil.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "prepareutil.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
PrepareUtil::PrepareUtil(QObject *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_process{new QProcess{this}}
|
||||||
|
{
|
||||||
|
connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &PrepareUtil::receiveScalingFactor);
|
||||||
|
|
||||||
|
// HACK: we are using kscreen-doctor to determine scaling, should switch to API
|
||||||
|
m_process->start("kscreen-doctor", {"-o"});
|
||||||
|
}
|
||||||
|
|
||||||
|
int PrepareUtil::scaling() const
|
||||||
|
{
|
||||||
|
return m_scaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrepareUtil::setScaling(int scaling)
|
||||||
|
{
|
||||||
|
if (scaling != m_scaling) {
|
||||||
|
const QString scalingNum = QString::number(((double)scaling) / 100);
|
||||||
|
qDebug() << "scaling" << scalingNum;
|
||||||
|
|
||||||
|
m_process->start("kscreen-doctor", {"output." + m_display + ".scale." + scalingNum});
|
||||||
|
|
||||||
|
m_scaling = scaling;
|
||||||
|
Q_EMIT scalingChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList PrepareUtil::scalingOptions()
|
||||||
|
{
|
||||||
|
return {"50%", "100%", "150%", "200%", "250%", "300%"};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrepareUtil::receiveScalingFactor(int exitCode, QProcess::ExitStatus exitStatus)
|
||||||
|
{
|
||||||
|
Q_UNUSED(exitCode)
|
||||||
|
Q_UNUSED(exitStatus)
|
||||||
|
|
||||||
|
// only trigger this slot once, on first time
|
||||||
|
disconnect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &PrepareUtil::receiveScalingFactor);
|
||||||
|
|
||||||
|
// remove ansi color codes
|
||||||
|
const auto ansiEscape = QRegularExpression{"\\\u001B\\[.*?m"};
|
||||||
|
const auto output = QString::fromUtf8(m_process->readAllStandardOutput()).replace(ansiEscape, "").replace("\\n", " ");
|
||||||
|
auto split = output.split(" ");
|
||||||
|
|
||||||
|
// HACK: hardcode how we get the output from kscreen-doctor
|
||||||
|
// we assume the first display is the phone screen
|
||||||
|
for (int i = 0; i < split.size(); ++i) {
|
||||||
|
if (i == 2) {
|
||||||
|
m_display = split[i];
|
||||||
|
} else if (split[i] == "Scale:") {
|
||||||
|
if (i + 1 < split.size()) {
|
||||||
|
m_scaling = split[i + 1].toDouble() * 100;
|
||||||
|
Q_EMIT scalingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
initialstart/modules/prepare/prepareutil.h
Normal file
34
initialstart/modules/prepare/prepareutil.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
class PrepareUtil : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(int scaling READ scaling WRITE setScaling NOTIFY scalingChanged);
|
||||||
|
Q_PROPERTY(QStringList scalingOptions READ scalingOptions CONSTANT);
|
||||||
|
|
||||||
|
public:
|
||||||
|
PrepareUtil(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
int scaling() const;
|
||||||
|
void setScaling(int scaling);
|
||||||
|
|
||||||
|
QStringList scalingOptions();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void scalingChanged();
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void receiveScalingFactor(int exitCode, QProcess::ExitStatus exitStatus);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_scaling;
|
||||||
|
QString m_display;
|
||||||
|
|
||||||
|
QProcess *m_process;
|
||||||
|
};
|
||||||
6
initialstart/modules/prepare/qmldir
Normal file
6
initialstart/modules/prepare/qmldir
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
module org.kde.plasma.mobileinitialstart.prepare
|
||||||
|
plugin prepareplugin
|
||||||
|
classname PreparePlugin
|
||||||
33
initialstart/modules/time/CMakeLists.txt
Normal file
33
initialstart/modules/time/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
set(timeplugin_SRCS
|
||||||
|
timeplugin.cpp
|
||||||
|
timeutil.cpp
|
||||||
|
timezonemodel.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(timeplugin ${timeplugin_SRCS})
|
||||||
|
|
||||||
|
target_link_libraries(timeplugin
|
||||||
|
Qt::Core
|
||||||
|
Qt::DBus
|
||||||
|
Qt::Quick
|
||||||
|
KF6::CoreAddons
|
||||||
|
KF6::ConfigCore
|
||||||
|
KF6::ConfigGui
|
||||||
|
KF6::I18n
|
||||||
|
KF6::Notifications
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET timeplugin PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/plasma/mobileinitialstart/time)
|
||||||
|
file(COPY qmldir DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/plasma/mobileinitialstart/time)
|
||||||
|
|
||||||
|
install(TARGETS timeplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/mobileinitialstart/time)
|
||||||
|
install(FILES qmldir ${qml_SRC} DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/mobileinitialstart/time)
|
||||||
|
|
||||||
|
plasma_install_package(package org.kde.plasma.mobileinitialstart.time mobileinitialstart)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
121
initialstart/modules/time/package/contents/ui/main.qml
Normal file
121
initialstart/modules/time/package/contents/ui/main.qml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||||
|
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||||
|
import org.kde.plasma.mobileinitialstart.time 1.0 as Time
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property string name: i18n("Time and Date")
|
||||||
|
|
||||||
|
readonly property real cardWidth: Math.min(Kirigami.Units.gridUnit * 30, root.width - Kirigami.Units.gridUnit * 2)
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
topMargin: Kirigami.Units.gridUnit
|
||||||
|
bottomMargin: Kirigami.Units.largeSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
width: root.width
|
||||||
|
spacing: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: i18n("Select your time zone and preferred time format.")
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileForm.FormCard {
|
||||||
|
maximumWidth: root.cardWidth
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
MobileForm.FormSwitchDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: i18n("24-Hour Format")
|
||||||
|
checked: Time.TimeUtil.is24HourTime
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked !== Time.TimeUtil.is24HourTime) {
|
||||||
|
Time.TimeUtil.is24HourTime = checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileForm.FormCard {
|
||||||
|
maximumWidth: root.cardWidth
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
clip: true
|
||||||
|
id: listView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
model: Time.TimeUtil.timeZones
|
||||||
|
|
||||||
|
header: Control {
|
||||||
|
width: listView.width
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
contentItem: Kirigami.SearchField {
|
||||||
|
id: searchField
|
||||||
|
|
||||||
|
onTextChanged: {
|
||||||
|
Time.TimeUtil.timeZones.filterString = text;
|
||||||
|
// HACK: search field seems to lose focus every time the text changes
|
||||||
|
focusTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: focusTimer
|
||||||
|
interval: 1
|
||||||
|
onTriggered: searchField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: MobileForm.FormRadioDelegate {
|
||||||
|
required property string timeZoneId
|
||||||
|
|
||||||
|
width: ListView.view.width
|
||||||
|
text: timeZoneId
|
||||||
|
checked: Time.TimeUtil.currentTimeZone === timeZoneId
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked && timeZoneId !== Time.TimeUtil.currentTimeZone) {
|
||||||
|
Time.TimeUtil.currentTimeZone = model.timeZoneId;
|
||||||
|
checked = Qt.binding(() => Time.TimeUtil.currentTimeZone === timeZoneId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
21
initialstart/modules/time/package/metadata.json
Normal file
21
initialstart/modules/time/package/metadata.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"KPackageStructure": "KPackage/GenericQML",
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Email": "devin@kde.org",
|
||||||
|
"Name": "Devin Lin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Description": "Time initialstart module for Plasma Mobile",
|
||||||
|
"Id": "org.kde.plasma.mobileinitialstart.time",
|
||||||
|
"License": "GPL-2.0+",
|
||||||
|
"Name": "Time",
|
||||||
|
"ServiceTypes": [
|
||||||
|
"KPackage/GenericQML"
|
||||||
|
],
|
||||||
|
"Version": "0.1",
|
||||||
|
"Website": "https://kde.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
7
initialstart/modules/time/qmldir
Normal file
7
initialstart/modules/time/qmldir
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
module org.kde.plasma.mobileinitialstart.time
|
||||||
|
plugin timeplugin
|
||||||
|
classname TimePlugin
|
||||||
|
|
||||||
20
initialstart/modules/time/timeplugin.cpp
Normal file
20
initialstart/modules/time/timeplugin.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "timeplugin.h"
|
||||||
|
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QQuickItem>
|
||||||
|
|
||||||
|
#include "timeutil.h"
|
||||||
|
|
||||||
|
void TimePlugin::registerTypes(const char *uri)
|
||||||
|
{
|
||||||
|
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.mobileinitialstart.time"));
|
||||||
|
|
||||||
|
qmlRegisterSingletonType<TimeUtil>(uri, 1, 0, "TimeUtil", [](QQmlEngine *, QJSEngine *) {
|
||||||
|
return new TimeUtil;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// #include "moc_flashlightplugin.cpp"
|
||||||
18
initialstart/modules/time/timeplugin.h
Normal file
18
initialstart/modules/time/timeplugin.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQmlExtensionPlugin>
|
||||||
|
|
||||||
|
class TimePlugin : public QQmlExtensionPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
|
||||||
|
|
||||||
|
public:
|
||||||
|
void registerTypes(const char *uri) override;
|
||||||
|
};
|
||||||
56
initialstart/modules/time/timeutil.cpp
Normal file
56
initialstart/modules/time/timeutil.cpp
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "timeutil.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QTimeZone>
|
||||||
|
|
||||||
|
#include <KConfigGroup>
|
||||||
|
#include <KSharedConfig>
|
||||||
|
|
||||||
|
#define FORMAT24H "HH:mm:ss"
|
||||||
|
#define FORMAT12H "h:mm:ss ap"
|
||||||
|
|
||||||
|
TimeUtil::TimeUtil(QObject *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_timeZoneModel{new TimeZoneModel{this}}
|
||||||
|
, m_filterModel{new TimeZoneFilterProxy{this}}
|
||||||
|
{
|
||||||
|
m_filterModel->setSourceModel(m_timeZoneModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeUtil::is24HourTime() const
|
||||||
|
{
|
||||||
|
return m_is24HourTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeUtil::setIs24HourTime(bool is24HourTime)
|
||||||
|
{
|
||||||
|
if (is24HourTime != m_is24HourTime) {
|
||||||
|
auto config = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig);
|
||||||
|
auto group = KConfigGroup(config, "Locale");
|
||||||
|
group.writeEntry(QStringLiteral("TimeFormat"), is24HourTime ? FORMAT24H : FORMAT12H, KConfig::Notify);
|
||||||
|
config->sync();
|
||||||
|
|
||||||
|
m_is24HourTime = is24HourTime;
|
||||||
|
Q_EMIT is24HourTimeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TimeUtil::currentTimeZone() const
|
||||||
|
{
|
||||||
|
return QString{QTimeZone::systemTimeZoneId()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeUtil::setCurrentTimeZone(QString timeZone)
|
||||||
|
{
|
||||||
|
QProcess::execute("timedatectl", {"set-timezone", timeZone});
|
||||||
|
Q_EMIT currentTimeZoneChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeZoneFilterProxy *TimeUtil::timeZones() const
|
||||||
|
{
|
||||||
|
return m_filterModel;
|
||||||
|
}
|
||||||
38
initialstart/modules/time/timeutil.h
Normal file
38
initialstart/modules/time/timeutil.h
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
#include "timezonemodel.h"
|
||||||
|
|
||||||
|
class TimeUtil : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(bool is24HourTime READ is24HourTime WRITE setIs24HourTime NOTIFY is24HourTimeChanged);
|
||||||
|
Q_PROPERTY(QString currentTimeZone READ currentTimeZone WRITE setCurrentTimeZone NOTIFY currentTimeZoneChanged);
|
||||||
|
Q_PROPERTY(TimeZoneFilterProxy *timeZones READ timeZones CONSTANT);
|
||||||
|
|
||||||
|
public:
|
||||||
|
TimeUtil(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool is24HourTime() const;
|
||||||
|
void setIs24HourTime(bool is24HourTime);
|
||||||
|
|
||||||
|
QString currentTimeZone() const;
|
||||||
|
void setCurrentTimeZone(QString timeZone);
|
||||||
|
|
||||||
|
TimeZoneFilterProxy *timeZones() const;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void is24HourTimeChanged();
|
||||||
|
void currentTimeZoneChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_is24HourTime;
|
||||||
|
|
||||||
|
TimeZoneModel *m_timeZoneModel;
|
||||||
|
TimeZoneFilterProxy *m_filterModel;
|
||||||
|
};
|
||||||
88
initialstart/modules/time/timezonemodel.cpp
Normal file
88
initialstart/modules/time/timezonemodel.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
// SPDX-FileCopyrightText: 2014 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
// SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "timezonemodel.h"
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QStringMatcher>
|
||||||
|
#include <QTimeZone>
|
||||||
|
|
||||||
|
TimeZoneFilterProxy::TimeZoneFilterProxy(QObject *parent)
|
||||||
|
: QSortFilterProxyModel(parent)
|
||||||
|
{
|
||||||
|
m_stringMatcher.setCaseSensitivity(Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeZoneFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||||
|
{
|
||||||
|
if (!sourceModel() || m_filterString.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const QString id = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::TimeZoneIdRole).toString();
|
||||||
|
return m_stringMatcher.indexIn(id) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeZoneFilterProxy::setFilterString(const QString &filterString)
|
||||||
|
{
|
||||||
|
m_filterString = filterString;
|
||||||
|
m_stringMatcher.setPattern(filterString);
|
||||||
|
Q_EMIT filterStringChanged();
|
||||||
|
invalidateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeZoneModel::TimeZoneModel(QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeZoneModel::~TimeZoneModel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeZoneModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
return m_data.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant TimeZoneModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (index.isValid()) {
|
||||||
|
switch (role) {
|
||||||
|
case TimeZoneIdRole:
|
||||||
|
return m_data[index.row()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeZoneModel::update()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_data.clear();
|
||||||
|
|
||||||
|
QTimeZone localZone = QTimeZone(QTimeZone::systemTimeZoneId());
|
||||||
|
m_data.append(localZone.id());
|
||||||
|
|
||||||
|
QStringList cities;
|
||||||
|
QHash<QString, QTimeZone> zonesByCity;
|
||||||
|
|
||||||
|
const QList<QByteArray> systemTimeZones = QTimeZone::availableTimeZoneIds();
|
||||||
|
|
||||||
|
for (QByteArray arr : systemTimeZones) {
|
||||||
|
m_data.append(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> TimeZoneModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {{TimeZoneIdRole, "timeZoneId"}};
|
||||||
|
}
|
||||||
51
initialstart/modules/time/timezonemodel.h
Normal file
51
initialstart/modules/time/timezonemodel.h
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
// SPDX-FileCopyrightText: 2014 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
// SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QTimeZone>
|
||||||
|
|
||||||
|
class TimeZoneFilterProxy : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString filterString WRITE setFilterString MEMBER m_filterString NOTIFY filterStringChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TimeZoneFilterProxy(QObject *parent = nullptr);
|
||||||
|
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||||
|
|
||||||
|
void setFilterString(const QString &filterString);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void filterStringChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_filterString;
|
||||||
|
QStringMatcher m_stringMatcher;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TimeZoneModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TimeZoneModel(QObject *parent = nullptr);
|
||||||
|
~TimeZoneModel() override;
|
||||||
|
|
||||||
|
enum Roles { TimeZoneIdRole = Qt::UserRole + 1 };
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
||||||
|
void update();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<QString> m_data;
|
||||||
|
};
|
||||||
33
initialstart/modules/wifi/CMakeLists.txt
Normal file
33
initialstart/modules/wifi/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
set(wifiplugin_SRCS
|
||||||
|
wifiplugin.cpp
|
||||||
|
wifiutil.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(wifiplugin ${wifiplugin_SRCS})
|
||||||
|
|
||||||
|
target_link_libraries(wifiplugin
|
||||||
|
Qt::Core
|
||||||
|
Qt::DBus
|
||||||
|
Qt::Quick
|
||||||
|
KF6::CoreAddons
|
||||||
|
KF6::ConfigCore
|
||||||
|
KF6::ConfigGui
|
||||||
|
KF6::NetworkManagerQt
|
||||||
|
KF6::I18n
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET wifiplugin PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/plasma/mobileinitialstart/wifi)
|
||||||
|
file(COPY qmldir DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/plasma/mobileinitialstart/wifi)
|
||||||
|
|
||||||
|
install(TARGETS wifiplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/mobileinitialstart/wifi)
|
||||||
|
install(FILES qmldir ${qml_SRC} DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/mobileinitialstart/wifi)
|
||||||
|
|
||||||
|
plasma_install_package(package org.kde.plasma.mobileinitialstart.wifi mobileinitialstart)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15 as Controls
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
Kirigami.PromptDialog {
|
||||||
|
id: dialogRoot
|
||||||
|
property int securityType
|
||||||
|
property string headingText
|
||||||
|
property string devicePath
|
||||||
|
property string specificPath
|
||||||
|
|
||||||
|
signal donePressed(string password)
|
||||||
|
|
||||||
|
function openAndClear() {
|
||||||
|
warning.visible = false;
|
||||||
|
this.open();
|
||||||
|
passwordField.text = "";
|
||||||
|
passwordField.focus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
title: headingText
|
||||||
|
standardButtons: Controls.Dialog.Ok | Controls.Dialog.Cancel
|
||||||
|
|
||||||
|
onOpened: passwordField.forceActiveFocus()
|
||||||
|
onRejected: {
|
||||||
|
dialogRoot.close();
|
||||||
|
passwordField.focus = false;
|
||||||
|
}
|
||||||
|
onAccepted: {
|
||||||
|
if (passwordField.acceptableInput) {
|
||||||
|
dialogRoot.close();
|
||||||
|
handler.addAndActivateConnection(devicePath, specificPath, passwordField.text);
|
||||||
|
} else {
|
||||||
|
warning.visible = true;
|
||||||
|
}
|
||||||
|
passwordField.focus = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: column
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
PasswordField {
|
||||||
|
id: passwordField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
securityType: dialogRoot.securityType
|
||||||
|
onAccepted: dialogRoot.accept()
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.Label {
|
||||||
|
id: warning
|
||||||
|
text: i18n("Invalid input.")
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
// SPDX-FileCopyrightText: 2017 Martin Kacej <m.kacej@atlas.sk>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15 as Controls
|
||||||
|
|
||||||
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||||
|
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
|
||||||
|
import org.kde.kirigami 2.2 as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||||
|
|
||||||
|
MobileForm.AbstractFormDelegate {
|
||||||
|
topPadding: Kirigami.Units.smallSpacing
|
||||||
|
bottomPadding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
property var map : []
|
||||||
|
property bool predictableWirelessPassword: !Uuid && Type == PlasmaNM.Enums.Wireless &&
|
||||||
|
(SecurityType == PlasmaNM.Enums.StaticWep ||
|
||||||
|
SecurityType == PlasmaNM.Enums.WpaPsk ||
|
||||||
|
SecurityType == PlasmaNM.Enums.Wpa2Psk ||
|
||||||
|
SecurityType == PlasmaNM.Enums.SAE)
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
changeState()
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeState() {
|
||||||
|
if (Uuid || !predictableWirelessPassword) {
|
||||||
|
if (ConnectionState == PlasmaNM.Enums.Deactivated) {
|
||||||
|
if (!predictableWirelessPassword && !Uuid) {
|
||||||
|
handler.addAndActivateConnection(DevicePath, SpecificPath);
|
||||||
|
} else {
|
||||||
|
handler.activateConnection(ConnectionPath, DevicePath, SpecificPath);
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
//show popup
|
||||||
|
}
|
||||||
|
} else if (predictableWirelessPassword) {
|
||||||
|
connectionDialog.headingText = i18n("Connect to") + " " + ItemUniqueName;
|
||||||
|
connectionDialog.devicePath = DevicePath;
|
||||||
|
connectionDialog.specificPath = SpecificPath;
|
||||||
|
connectionDialog.securityType = SecurityType;
|
||||||
|
connectionDialog.openAndClear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
PlasmaCore.SvgItem {
|
||||||
|
id: connectionSvgIcon
|
||||||
|
elementId: mobileProxyModel.showSavedMode ? "network-wireless-connected-100" : ConnectionIcon
|
||||||
|
|
||||||
|
svg: PlasmaCore.Svg {
|
||||||
|
multipleImages: true
|
||||||
|
imagePath: "icons/network"
|
||||||
|
colorGroup: PlasmaCore.ColorScope.colorGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.BusyIndicator {
|
||||||
|
id: connectingIndicator
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: connectionSvgIcon.horizontalCenter
|
||||||
|
verticalCenter: connectionSvgIcon.verticalCenter
|
||||||
|
}
|
||||||
|
running: ConnectionState == PlasmaNM.Enums.Activating
|
||||||
|
visible: running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.Label {
|
||||||
|
id: connectionNameLabel
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: ItemUniqueName
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.ToolButton {
|
||||||
|
icon.name: "network-connect"
|
||||||
|
visible: ConnectionState != PlasmaNM.Enums.Activated
|
||||||
|
onClicked: changeState()
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.ToolButton {
|
||||||
|
icon.name: "network-disconnect"
|
||||||
|
visible: ConnectionState == PlasmaNM.Enums.Activated
|
||||||
|
onClicked: handler.deactivateConnection(ConnectionPath, DevicePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// SPDX-FileCopyrightText: 2013-2017 Jan Grulich <jgrulich@redhat.com>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import org.kde.kirigami 2.10 as Kirigami
|
||||||
|
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
|
||||||
|
|
||||||
|
Kirigami.PasswordField {
|
||||||
|
property int securityType
|
||||||
|
placeholderText: i18n("Password…")
|
||||||
|
validator: RegularExpressionValidator {
|
||||||
|
regularExpression: if (securityType == PlasmaNM.Enums.StaticWep) {
|
||||||
|
/^(?:.{5}|[0-9a-fA-F]{10}|.{13}|[0-9a-fA-F]{26}){1}$/
|
||||||
|
} else {
|
||||||
|
/^(?:.{8,64}){1}$/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
118
initialstart/modules/wifi/package/contents/ui/main.qml
Normal file
118
initialstart/modules/wifi/package/contents/ui/main.qml
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
// SPDX-FileCopyrightText: 2017 Martin Kacej <m.kacej@atlas.sk>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||||
|
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||||
|
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
|
||||||
|
import org.kde.plasma.mobileinitialstart.wifi 1.0 as WiFi
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property string name: i18n("Network")
|
||||||
|
|
||||||
|
readonly property real cardWidth: Math.min(Kirigami.Units.gridUnit * 30, root.width - Kirigami.Units.gridUnit * 2)
|
||||||
|
|
||||||
|
PlasmaNM.Handler {
|
||||||
|
id: handler
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaNM.EnabledConnections {
|
||||||
|
id: enabledConnections
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaNM.NetworkModel {
|
||||||
|
id: connectionModel
|
||||||
|
}
|
||||||
|
|
||||||
|
PlasmaNM.MobileProxyModel {
|
||||||
|
id: mobileProxyModel
|
||||||
|
sourceModel: connectionModel
|
||||||
|
showSavedMode: false
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectDialog {
|
||||||
|
id: connectionDialog
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: handler.requestScan()
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: scanTimer
|
||||||
|
interval: 10200
|
||||||
|
repeat: true
|
||||||
|
running: parent.visible
|
||||||
|
|
||||||
|
onTriggered: handler.requestScan()
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: Kirigami.Units.gridUnit
|
||||||
|
anchors.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
|
width: root.width
|
||||||
|
spacing: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: i18n("Connect to a WiFi network for network access.")
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileForm.FormCard {
|
||||||
|
maximumWidth: root.cardWidth
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: listView
|
||||||
|
currentIndex: -1
|
||||||
|
clip: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
section.property: "Section"
|
||||||
|
section.delegate: Kirigami.ListSectionHeader {
|
||||||
|
text: section
|
||||||
|
}
|
||||||
|
|
||||||
|
model: mobileProxyModel
|
||||||
|
|
||||||
|
Kirigami.PlaceholderMessage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||||
|
visible: !enabledConnections.wirelessEnabled
|
||||||
|
text: i18n("Wi-Fi is disabled")
|
||||||
|
icon.name: "network-wireless-disconnected"
|
||||||
|
helpfulAction: Kirigami.Action {
|
||||||
|
iconName: "network-wireless-connected"
|
||||||
|
text: i18n("Enable")
|
||||||
|
onTriggered: handler.enableWireless(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: ConnectionItemDelegate {
|
||||||
|
width: listView.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
22
initialstart/modules/wifi/package/metadata.json
Normal file
22
initialstart/modules/wifi/package/metadata.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"KPackageStructure": "KPackage/GenericQML",
|
||||||
|
"KPlugin": {
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Email": "devin@kde.org",
|
||||||
|
"Name": "Devin Lin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Description": "WiFi initialstart module for Plasma Mobile",
|
||||||
|
"Id": "org.kde.plasma.mobileinitialstart.wifi",
|
||||||
|
"License": "GPL-2.0+",
|
||||||
|
"Name": "WiFi",
|
||||||
|
"ServiceTypes": [
|
||||||
|
"KPackage/GenericQML"
|
||||||
|
],
|
||||||
|
"Version": "0.1",
|
||||||
|
"Website": "https://kde.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
8
initialstart/modules/wifi/qmldir
Normal file
8
initialstart/modules/wifi/qmldir
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
module org.kde.plasma.mobileinitialstart.wifi
|
||||||
|
plugin wifiplugin
|
||||||
|
classname WiFiPlugin
|
||||||
|
|
||||||
|
|
||||||
18
initialstart/modules/wifi/wifiplugin.cpp
Normal file
18
initialstart/modules/wifi/wifiplugin.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "wifiplugin.h"
|
||||||
|
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QQuickItem>
|
||||||
|
|
||||||
|
#include "wifiutil.h"
|
||||||
|
|
||||||
|
void WiFiPlugin::registerTypes(const char *uri)
|
||||||
|
{
|
||||||
|
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.plasma.mobileinitialstart.wifi"));
|
||||||
|
|
||||||
|
qmlRegisterSingletonType<WiFiUtil>(uri, 1, 0, "WiFiUtil", [](QQmlEngine *, QJSEngine *) {
|
||||||
|
return new WiFiUtil;
|
||||||
|
});
|
||||||
|
}
|
||||||
18
initialstart/modules/wifi/wifiplugin.h
Normal file
18
initialstart/modules/wifi/wifiplugin.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQmlExtensionPlugin>
|
||||||
|
|
||||||
|
class WiFiPlugin : public QQmlExtensionPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
|
||||||
|
|
||||||
|
public:
|
||||||
|
void registerTypes(const char *uri) override;
|
||||||
|
};
|
||||||
16
initialstart/modules/wifi/wifiutil.cpp
Normal file
16
initialstart/modules/wifi/wifiutil.cpp
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "wifiutil.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QTimeZone>
|
||||||
|
|
||||||
|
#include <KConfigGroup>
|
||||||
|
#include <KSharedConfig>
|
||||||
|
|
||||||
|
WiFiUtil::WiFiUtil(QObject *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
{
|
||||||
|
}
|
||||||
15
initialstart/modules/wifi/wifiutil.h
Normal file
15
initialstart/modules/wifi/wifiutil.h
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 by Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
class WiFiUtil : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
WiFiUtil(QObject *parent = nullptr);
|
||||||
|
};
|
||||||
113
initialstart/qml/LandingComponent.qml
Normal file
113
initialstart/qml/LandingComponent.qml
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property real scaleStart: 1.4
|
||||||
|
readonly property real scaleLanding: 1.2
|
||||||
|
readonly property real scaleSteps: 1
|
||||||
|
|
||||||
|
signal requestNextPage()
|
||||||
|
|
||||||
|
function returnToLanding() {
|
||||||
|
backgroundImage.scale = scaleLanding;
|
||||||
|
contentOpacityAnim.to = 1;
|
||||||
|
contentOpacityAnim.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
property real contentOpacity: 0
|
||||||
|
NumberAnimation on contentOpacity {
|
||||||
|
id: contentOpacityAnim
|
||||||
|
running: true
|
||||||
|
duration: 1000
|
||||||
|
to: 1
|
||||||
|
|
||||||
|
// shorten animation after initial run
|
||||||
|
onFinished: duration = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: backgroundImage
|
||||||
|
anchors.fill: parent
|
||||||
|
source: "qrc:/assets/background.png"
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
NumberAnimation on opacity {
|
||||||
|
running: true
|
||||||
|
duration: 400
|
||||||
|
to: 1
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
// zoom animation
|
||||||
|
scale: scaleStart
|
||||||
|
Component.onCompleted: scale = scaleLanding
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 2000
|
||||||
|
easing.type: Easing.OutExpo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// darken image slightly
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
opacity: root.contentOpacity
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.leftMargin: Kirigami.Units.gridUnit * 4
|
||||||
|
anchors.rightMargin: Kirigami.Units.gridUnit * 4
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: i18n("Welcome to <b>Plasma</b>")
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
|
font.pointSize: 18
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: button
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
leftPadding: Kirigami.Units.gridUnit
|
||||||
|
rightPadding: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
opacity: root.contentOpacity
|
||||||
|
text: i18n("Next")
|
||||||
|
icon.name: "go-next-symbolic"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
backgroundImage.scale = scaleSteps;
|
||||||
|
contentOpacityAnim.to = 0;
|
||||||
|
contentOpacityAnim.restart();
|
||||||
|
root.requestNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
340
initialstart/qml/Wizard.qml
Normal file
340
initialstart/qml/Wizard.qml
Normal file
|
|
@ -0,0 +1,340 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
|
||||||
|
|
||||||
|
import initialstart 1.0 as InitialStart
|
||||||
|
|
||||||
|
Kirigami.Page {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
|
||||||
|
property int currentIndex: 0
|
||||||
|
property int stepCount: 0
|
||||||
|
property bool showingLanding: true
|
||||||
|
|
||||||
|
// filled by items
|
||||||
|
property var currentStepItem
|
||||||
|
property var nextStepItem
|
||||||
|
property var previousStepItem
|
||||||
|
|
||||||
|
readonly property bool onFinalPage: currentIndex === (stepCount - 1)
|
||||||
|
|
||||||
|
// step animation
|
||||||
|
// manually doing the animation is more performant and less glitchy with window resize than a SwipeView
|
||||||
|
property real previousStepItemX: 0
|
||||||
|
property real currentStepItemX: 0
|
||||||
|
property real nextStepItemX: 0
|
||||||
|
|
||||||
|
NumberAnimation on previousStepItemX {
|
||||||
|
id: previousStepAnim
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.OutExpo
|
||||||
|
onFinished: {
|
||||||
|
if (root.previousStepItemX != 0) {
|
||||||
|
root.previousStepItem.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation on currentStepItemX {
|
||||||
|
id: currentStepAnim
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.OutExpo
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation on nextStepItemX {
|
||||||
|
id: nextStepAnim
|
||||||
|
duration: 400
|
||||||
|
easing.type: Easing.OutExpo
|
||||||
|
onFinished: {
|
||||||
|
if (root.nextStepItemX != 0) {
|
||||||
|
root.nextStepItem.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishFinalPage() {
|
||||||
|
InitialStart.Wizard.wizardFinished();
|
||||||
|
applicationWindow().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestNextPage() {
|
||||||
|
if (previousStepAnim.running || currentStepAnim.running || nextStepAnim.running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousStepItemX = 0;
|
||||||
|
|
||||||
|
currentIndex++;
|
||||||
|
stepHeading.changeText(currentStepItem.name);
|
||||||
|
|
||||||
|
currentStepItemX = root.width;
|
||||||
|
currentStepItem.visible = true;
|
||||||
|
|
||||||
|
previousStepAnim.to = -root.width;
|
||||||
|
previousStepAnim.restart();
|
||||||
|
currentStepAnim.to = 0;
|
||||||
|
currentStepAnim.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestPreviousPage() {
|
||||||
|
if (previousStepAnim.running || currentStepAnim.running || nextStepAnim.running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIndex === 0) {
|
||||||
|
root.showingLanding = true;
|
||||||
|
landingComponent.returnToLanding();
|
||||||
|
} else {
|
||||||
|
nextStepItemX = 0;
|
||||||
|
|
||||||
|
currentIndex--;
|
||||||
|
stepHeading.changeText(currentStepItem.name);
|
||||||
|
|
||||||
|
currentStepItemX = -root.width;
|
||||||
|
currentStepItem.visible = true;
|
||||||
|
|
||||||
|
nextStepAnim.to = root.width;
|
||||||
|
nextStepAnim.restart();
|
||||||
|
currentStepAnim.to = 0;
|
||||||
|
currentStepAnim.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// top status bar
|
||||||
|
MobileShell.StatusBar {
|
||||||
|
id: statusBar
|
||||||
|
z: 1
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
height: Kirigami.Units.gridUnit * 1.25
|
||||||
|
|
||||||
|
colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
|
||||||
|
backgroundColor: "transparent"
|
||||||
|
|
||||||
|
showSecondRow: false
|
||||||
|
showDropShadow: true
|
||||||
|
showTime: true
|
||||||
|
disableSystemTray: true // prevent SIGABRT, since loading the system tray leads to bad... things
|
||||||
|
}
|
||||||
|
|
||||||
|
LandingComponent {
|
||||||
|
id: landingComponent
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onRequestNextPage: {
|
||||||
|
root.showingLanding = false;
|
||||||
|
stepHeading.changeText(root.currentStepItem.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: stepsComponent
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// animation when we switch to step stage
|
||||||
|
opacity: root.showingLanding ? 0 : 1
|
||||||
|
property real translateY: root.showingLanding ? overlaySteps.height : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 1000
|
||||||
|
easing.type: Easing.OutExpo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on translateY {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 1000
|
||||||
|
easing.type: Easing.OutExpo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transform: Translate { y: stepsComponent.translateY }
|
||||||
|
|
||||||
|
// heading for all the wizard steps
|
||||||
|
Label {
|
||||||
|
id: stepHeading
|
||||||
|
opacity: 0
|
||||||
|
color: "white"
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.pointSize: 18
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Kirigami.Units.gridUnit
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Kirigami.Units.gridUnit
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: root.height * 0.7 + Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
property string toText
|
||||||
|
|
||||||
|
function changeText(text) {
|
||||||
|
toText = text;
|
||||||
|
toHidden.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation on opacity {
|
||||||
|
id: toHidden
|
||||||
|
duration: 200
|
||||||
|
to: 0
|
||||||
|
onFinished: {
|
||||||
|
stepHeading.text = stepHeading.toText;
|
||||||
|
toShown.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation on opacity {
|
||||||
|
id: toShown
|
||||||
|
duration: 200
|
||||||
|
to: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: overlaySteps
|
||||||
|
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
|
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: root.height * 0.3
|
||||||
|
|
||||||
|
// all steps are in this container
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.bottomMargin: stepFooter.implicitHeight
|
||||||
|
|
||||||
|
// setup steps
|
||||||
|
Repeater {
|
||||||
|
model: InitialStart.Wizard.steps
|
||||||
|
|
||||||
|
delegate: MobileShell.BaseItem {
|
||||||
|
id: item
|
||||||
|
visible: model.index === 0 // the binding is broken later
|
||||||
|
contentItem: modelData
|
||||||
|
transform: Translate {
|
||||||
|
x: {
|
||||||
|
if (item.currentIndex === root.currentIndex - 1) {
|
||||||
|
return previousStepItemX;
|
||||||
|
} else if (item.currentIndex === root.currentIndex + 1) {
|
||||||
|
return nextStepItemX;
|
||||||
|
} else if (item.currentIndex === root.currentIndex) {
|
||||||
|
return currentStepItemX;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// pass up the property
|
||||||
|
property string name: contentItem.name
|
||||||
|
property int currentIndex: model.index
|
||||||
|
|
||||||
|
function updateRootItems() {
|
||||||
|
if (model.index === root.currentIndex) {
|
||||||
|
root.currentStepItem = item;
|
||||||
|
} else if (model.index === root.currentIndex - 1) {
|
||||||
|
root.previousStepItem = item;
|
||||||
|
} else if (model.index === root.currentIndex + 1) {
|
||||||
|
root.nextStepItem = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.stepCount++
|
||||||
|
updateRootItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep root properties updated
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
|
||||||
|
function onCurrentIndexChanged() {
|
||||||
|
item.updateRootItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bottom footer
|
||||||
|
RowLayout {
|
||||||
|
id: stepFooter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.bottomMargin: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
leftPadding: Kirigami.Units.gridUnit
|
||||||
|
rightPadding: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
text: i18n("Back")
|
||||||
|
icon.name: "arrow-left"
|
||||||
|
|
||||||
|
onClicked: root.requestPreviousPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.bottomMargin: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
leftPadding: Kirigami.Units.gridUnit
|
||||||
|
rightPadding: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
visible: !root.onFinalPage
|
||||||
|
text: i18n("Next")
|
||||||
|
icon.name: "arrow-right"
|
||||||
|
|
||||||
|
onClicked: root.requestNextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||||
|
Layout.bottomMargin: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
leftPadding: Kirigami.Units.gridUnit
|
||||||
|
rightPadding: Kirigami.Units.gridUnit
|
||||||
|
|
||||||
|
visible: root.onFinalPage
|
||||||
|
text: i18n("Finish")
|
||||||
|
icon.name: "dialog-ok"
|
||||||
|
|
||||||
|
onClicked: root.finishFinalPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
25
initialstart/qml/main.qml
Normal file
25
initialstart/qml/main.qml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
import initialstart 1.0 as InitialStart
|
||||||
|
|
||||||
|
Kirigami.ApplicationWindow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
width: 360
|
||||||
|
height: 720
|
||||||
|
visibility: InitialStart.Wizard.testingMode ? "Windowed" : "FullScreen"
|
||||||
|
|
||||||
|
title: i18n("Initial Start")
|
||||||
|
|
||||||
|
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.None
|
||||||
|
pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn
|
||||||
|
|
||||||
|
pageStack.initialPage: Wizard {}
|
||||||
|
}
|
||||||
15
initialstart/resources.qrc
Normal file
15
initialstart/resources.qrc
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!--
|
||||||
|
- Copyright 2023 Devin Lin <devin@kde.org>
|
||||||
|
- SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
-->
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
|
||||||
|
<file>qml/main.qml</file>
|
||||||
|
<file>qml/Wizard.qml</file>
|
||||||
|
<file>qml/LandingComponent.qml</file>
|
||||||
|
|
||||||
|
<file>assets/background.png</file>
|
||||||
|
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
40
initialstart/settings.cpp
Normal file
40
initialstart/settings.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include <KConfigGroup>
|
||||||
|
#include <KRuntimePlatform>
|
||||||
|
|
||||||
|
const QString CONFIG_FILE = QStringLiteral("plasmamobilerc");
|
||||||
|
const QString INITIAL_START_CONFIG_GROUP = QStringLiteral("InitialStart");
|
||||||
|
|
||||||
|
Settings::Settings(QObject *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_mobileConfig{KSharedConfig::openConfig(CONFIG_FILE, KConfig::SimpleConfig)}
|
||||||
|
, m_isMobilePlatform{KRuntimePlatform::runtimePlatform().contains(QStringLiteral("phone"))}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Settings::shouldStartWizard()
|
||||||
|
{
|
||||||
|
if (!m_isMobilePlatform) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto group = KConfigGroup{m_mobileConfig, INITIAL_START_CONFIG_GROUP};
|
||||||
|
return !group.readEntry("wizardRun", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setWizardFinished()
|
||||||
|
{
|
||||||
|
auto group = KConfigGroup{m_mobileConfig, INITIAL_START_CONFIG_GROUP};
|
||||||
|
group.writeEntry("wizardRun", true, KConfigGroup::Notify);
|
||||||
|
m_mobileConfig->sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings *Settings::self()
|
||||||
|
{
|
||||||
|
static auto instance = new Settings;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
22
initialstart/settings.h
Normal file
22
initialstart/settings.h
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <KConfig>
|
||||||
|
#include <KSharedConfig>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class Settings : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Settings(QObject *parent = nullptr);
|
||||||
|
static Settings *self();
|
||||||
|
|
||||||
|
bool shouldStartWizard();
|
||||||
|
void setWizardFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
KSharedConfig::Ptr m_mobileConfig;
|
||||||
|
bool m_isMobilePlatform;
|
||||||
|
};
|
||||||
12
initialstart/utils.h
Normal file
12
initialstart/utils.h
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
static const QLoggingCategory &LOGGING_CATEGORY()
|
||||||
|
{
|
||||||
|
static const QLoggingCategory category("plasma-mobile-initial-start");
|
||||||
|
return category;
|
||||||
|
}
|
||||||
99
initialstart/wizard.cpp
Normal file
99
initialstart/wizard.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "wizard.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <KPackage/PackageLoader>
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QQmlComponent>
|
||||||
|
|
||||||
|
// TODO read distro provided config file
|
||||||
|
const QList<QString> WIZARD_MODULE_ORDER = {QStringLiteral("org.kde.plasma.mobileinitialstart.prepare"),
|
||||||
|
QStringLiteral("org.kde.plasma.mobileinitialstart.time"),
|
||||||
|
QStringLiteral("org.kde.plasma.mobileinitialstart.wifi"),
|
||||||
|
QStringLiteral("org.kde.plasma.mobileinitialstart.cellular"),
|
||||||
|
QStringLiteral("org.kde.plasma.mobileinitialstart.finished")};
|
||||||
|
|
||||||
|
Wizard::Wizard(QObject *parent, QQmlEngine *engine)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_engine{engine}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wizard::load()
|
||||||
|
{
|
||||||
|
if (!m_engine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(LOGGING_CATEGORY) << "Loading initialstart packages...";
|
||||||
|
|
||||||
|
// load initialstart packages
|
||||||
|
const auto packages = KPackage::PackageLoader::self()->listPackages(QStringLiteral("KPackage/GenericQML"), QStringLiteral("plasma/mobileinitialstart"));
|
||||||
|
for (auto &metaData : packages) {
|
||||||
|
KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/GenericQML"), QFileInfo(metaData.fileName()).path());
|
||||||
|
if (!package.isValid()) {
|
||||||
|
qCWarning(LOGGING_CATEGORY) << "initialstart package invalid:" << metaData.fileName();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_modulePackages.push_back({new KPluginMetaData{metaData}, package});
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort modules by order
|
||||||
|
std::sort(m_modulePackages.begin(), m_modulePackages.end(), [](const auto &lhs, const auto &rhs) {
|
||||||
|
return WIZARD_MODULE_ORDER.indexOf(lhs.first->pluginId()) < WIZARD_MODULE_ORDER.indexOf(rhs.first->pluginId());
|
||||||
|
});
|
||||||
|
|
||||||
|
QQmlComponent *c = new QQmlComponent(m_engine, this);
|
||||||
|
|
||||||
|
// load initialstart QML items
|
||||||
|
for (auto &pair : m_modulePackages) {
|
||||||
|
// load QML from kpackage
|
||||||
|
c->loadUrl(pair.second.fileUrl("mainscript"), QQmlComponent::PreferSynchronous);
|
||||||
|
|
||||||
|
auto created = c->create(m_engine->rootContext());
|
||||||
|
auto createdItem = qobject_cast<QQuickItem *>(created);
|
||||||
|
|
||||||
|
// print errors if there were issues loading
|
||||||
|
if (!createdItem) {
|
||||||
|
qCWarning(LOGGING_CATEGORY) << "Unable to load initialstart module:" << created;
|
||||||
|
for (auto error : c->errors()) {
|
||||||
|
qCWarning(LOGGING_CATEGORY) << error;
|
||||||
|
}
|
||||||
|
delete created;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_moduleItems.push_back(createdItem);
|
||||||
|
|
||||||
|
qCDebug(LOGGING_CATEGORY) << "Loaded initialstart module" << pair.first->pluginId();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wizard::setTestingMode(bool testingMode)
|
||||||
|
{
|
||||||
|
if (testingMode != m_testingMode) {
|
||||||
|
m_testingMode = testingMode;
|
||||||
|
Q_EMIT testingModeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Wizard::testingMode()
|
||||||
|
{
|
||||||
|
return m_testingMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QQuickItem *> Wizard::steps()
|
||||||
|
{
|
||||||
|
return m_moduleItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Wizard::wizardFinished()
|
||||||
|
{
|
||||||
|
Settings::self()->setWizardFinished();
|
||||||
|
}
|
||||||
41
initialstart/wizard.h
Normal file
41
initialstart/wizard.h
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QQuickItem>
|
||||||
|
|
||||||
|
#include <KPackage/PackageLoader>
|
||||||
|
#include <KPluginMetaData>
|
||||||
|
|
||||||
|
class Wizard : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QList<QQuickItem *> steps READ steps CONSTANT)
|
||||||
|
Q_PROPERTY(bool testingMode READ testingMode NOTIFY testingModeChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
Wizard(QObject *parent = nullptr, QQmlEngine *engine = nullptr);
|
||||||
|
|
||||||
|
void load();
|
||||||
|
|
||||||
|
void setTestingMode(bool testingMode);
|
||||||
|
bool testingMode();
|
||||||
|
|
||||||
|
QList<QQuickItem *> steps();
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void wizardFinished();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void testingModeChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<std::pair<KPluginMetaData *, KPackage::Package>> m_modulePackages;
|
||||||
|
QList<QQuickItem *> m_moduleItems;
|
||||||
|
|
||||||
|
bool m_testingMode;
|
||||||
|
QQmlEngine *m_engine;
|
||||||
|
};
|
||||||
|
|
@ -12,10 +12,15 @@
|
||||||
Start::Start(QObject *parent, const QList<QVariant> &)
|
Start::Start(QObject *parent, const QList<QVariant> &)
|
||||||
: KDEDModule{parent}
|
: KDEDModule{parent}
|
||||||
{
|
{
|
||||||
auto *job = new KIO::CommandLauncherJob(QStringLiteral("plasma-mobile-envmanager --apply-settings"), {});
|
auto *envmanagerJob = new KIO::CommandLauncherJob(QStringLiteral("plasma-mobile-envmanager --apply-settings"), {});
|
||||||
job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
|
envmanagerJob->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
|
||||||
job->setDesktopName(QStringLiteral("org.kde.plasma-mobile-envmanager"));
|
envmanagerJob->setDesktopName(QStringLiteral("org.kde.plasma-mobile-envmanager"));
|
||||||
job->start();
|
envmanagerJob->start();
|
||||||
|
|
||||||
|
auto *initialstartJob = new KIO::CommandLauncherJob(QStringLiteral("plasma-mobile-initial-start"), {});
|
||||||
|
initialstartJob->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
|
||||||
|
initialstartJob->setDesktopName(QStringLiteral("org.kde.plasma-mobile-initial-start"));
|
||||||
|
initialstartJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "start.moc"
|
#include "start.moc"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue