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
|
||||
KCMUtils
|
||||
Package
|
||||
DBusAddons
|
||||
)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
|
@ -104,6 +105,7 @@ add_subdirectory(kcms)
|
|||
add_subdirectory(kded)
|
||||
add_subdirectory(kwin)
|
||||
add_subdirectory(envmanager)
|
||||
add_subdirectory(initialstart)
|
||||
|
||||
find_program(PlasmaOpenSettings plasma-open-settings)
|
||||
set_package_properties(PlasmaOpenSettings PROPERTIES
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ add_library(ppc-mmqmlplugin)
|
|||
target_sources(ppc-mmqmlplugin PRIVATE
|
||||
mmqmlplugin.cpp
|
||||
signalindicator.cpp
|
||||
profilesettings.cpp
|
||||
)
|
||||
|
||||
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 "signalindicator.h"
|
||||
|
||||
SignalIndicator::SignalIndicator(QObject *parent)
|
||||
: QObject{parent}
|
||||
, 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()
|
||||
{
|
||||
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::availableConnectionAppeared, 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 "profilesettings.h"
|
||||
|
||||
// We make the assumption that there is only one modem.
|
||||
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 needsAPNAdded READ needsAPNAdded NOTIFY mobileDataEnabledChanged)
|
||||
Q_PROPERTY(QList<ProfileSettings *> profiles READ profileList NOTIFY profileListChanged)
|
||||
Q_PROPERTY(QString activeConnectionUni READ activeConnectionUni NOTIFY activeConnectionUniChanged)
|
||||
|
||||
public:
|
||||
SignalIndicator(QObject *parent = nullptr);
|
||||
|
|
@ -39,9 +43,23 @@ public:
|
|||
bool mobileDataSupported() const;
|
||||
bool mobileDataEnabled() const;
|
||||
bool needsAPNAdded() const;
|
||||
QString activeConnectionUni() const;
|
||||
|
||||
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:
|
||||
void strengthChanged();
|
||||
void nameChanged();
|
||||
|
|
@ -50,6 +68,8 @@ Q_SIGNALS:
|
|||
void simEmptyChanged();
|
||||
void mobileDataSupportedChanged();
|
||||
void mobileDataEnabledChanged();
|
||||
void profileListChanged();
|
||||
void activeConnectionUniChanged();
|
||||
|
||||
private:
|
||||
NetworkManager::ModemDevice::Ptr m_nmModem;
|
||||
|
|
@ -57,6 +77,8 @@ private:
|
|||
ModemManager::Modem::Ptr m_modem;
|
||||
ModemManager::Modem3gpp::Ptr m_3gppModem;
|
||||
|
||||
QList<ProfileSettings *> m_profileList;
|
||||
|
||||
void updateModemManagerModem();
|
||||
void updateNetworkManagerModem();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
#include <QProcess>
|
||||
|
||||
const QString CONFIG_FILE = QStringLiteral("plasmamobilerc");
|
||||
const QString INITIAL_START_CONFIG_GROUP = QStringLiteral("InitialStart");
|
||||
const QString SAVED_CONFIG_GROUP = QStringLiteral("SavedConfig");
|
||||
|
||||
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> &)
|
||||
: KDEDModule{parent}
|
||||
{
|
||||
auto *job = new KIO::CommandLauncherJob(QStringLiteral("plasma-mobile-envmanager --apply-settings"), {});
|
||||
job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
|
||||
job->setDesktopName(QStringLiteral("org.kde.plasma-mobile-envmanager"));
|
||||
job->start();
|
||||
auto *envmanagerJob = new KIO::CommandLauncherJob(QStringLiteral("plasma-mobile-envmanager --apply-settings"), {});
|
||||
envmanagerJob->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
|
||||
envmanagerJob->setDesktopName(QStringLiteral("org.kde.plasma-mobile-envmanager"));
|
||||
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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue