kcms: Move KCMs from plasma-settings

Having the KCMs that are mobile specific here makes more sense than in the settings application. Historically plasma-settings had a faster release cycle than Plasma, but the application is now moving to the Plasma release schedule and so it makes sense do this now.
This commit is contained in:
Devin Lin 2023-03-13 18:45:47 -07:00
parent c3882691bc
commit e147f98aea
78 changed files with 9745 additions and 1 deletions

View file

@ -67,8 +67,13 @@ find_package(KF6 ${KF6_MIN_VERSION} REQUIRED COMPONENTS
Declarative
Package
)
find_package(KF6KirigamiAddons 0.6 REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GOBJECT gobject-2.0 REQUIRED IMPORTED_TARGET)
pkg_check_modules(GIO gio-2.0 REQUIRED IMPORTED_TARGET)
find_package(KF6KirigamiAddons 0.6 REQUIRED)
find_package(LibKWorkspace CONFIG REQUIRED)
find_package(KWinDBusInterface)

View file

@ -2,3 +2,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
add_subdirectory(mobileshell)
add_subdirectory(cellularnetwork)
add_subdirectory(info)
add_subdirectory(powermanagement)
add_subdirectory(time)
add_subdirectory(virtualkeyboard)

View file

@ -0,0 +1,26 @@
set (cellularnetworksettings_SRCS
cellularnetworksettings.cpp
modem.cpp
modemdetails.cpp
sim.cpp
mobileproviders.cpp
profilesettings.cpp
)
add_library(kcm_cellular_network MODULE ${cellularnetworksettings_SRCS})
target_link_libraries(kcm_cellular_network
Qt::DBus
Qt::Gui
Qt::Quick
Qt::Qml
Qt::Xml
KF6::Plasma
KF6::I18n
KF6::NetworkManagerQt
KF6::ModemManagerQt
KF6::QuickAddons
)
install(TARGETS kcm_cellular_network DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
kpackage_install_package(package kcm_cellular_network kcms)

View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kcm_cellular_network.pot

View file

@ -0,0 +1,157 @@
/*
SPDX-FileCopyrightText: 2018 Martin Kacej <m.kacej@atlas.sk>
SPDX-FileCopyrightText: 2020-2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "cellularnetworksettings.h"
#include <KLocalizedString>
#include <KPluginFactory>
#include <KUser>
#include <QQmlEngine>
K_PLUGIN_CLASS_WITH_JSON(CellularNetworkSettings, "cellularnetworksettings.json")
CellularNetworkSettings *CellularNetworkSettings::staticInst = nullptr;
CellularNetworkSettings::CellularNetworkSettings(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, metaData, args)
, m_modemList{}
, m_simList{}
{
CellularNetworkSettings::staticInst = this;
qmlRegisterType<ProfileSettings>("cellularnetworkkcm", 1, 0, "ProfileSettings");
qmlRegisterType<Modem>("cellularnetworkkcm", 1, 0, "Modem");
qmlRegisterType<ModemDetails>("cellularnetworkkcm", 1, 0, "ModemDetails");
qmlRegisterType<AvailableNetwork>("cellularnetworkkcm", 1, 0, "AvailableNetwork");
qmlRegisterType<Sim>("cellularnetworkkcm", 1, 0, "Sim");
qmlRegisterType<InlineMessage>("cellularnetworkkcm", 1, 0, "InlineMessage");
// find modems
updateModemList();
connect(ModemManager::notifier(), &ModemManager::Notifier::modemAdded, this, &CellularNetworkSettings::updateModemList);
connect(ModemManager::notifier(), &ModemManager::Notifier::modemRemoved, this, &CellularNetworkSettings::updateModemList);
}
CellularNetworkSettings *CellularNetworkSettings::instance()
{
return CellularNetworkSettings::staticInst;
}
Modem *CellularNetworkSettings::selectedModem()
{
// TODO: we are currently assuming there is a single modem
if (m_modemList.count() > 0) {
return m_modemList[0];
}
return nullptr;
}
QList<Modem *> CellularNetworkSettings::modems()
{
return m_modemList;
}
QList<Sim *> CellularNetworkSettings::sims()
{
return m_simList;
}
bool CellularNetworkSettings::modemFound()
{
return !m_modemList.empty();
}
void CellularNetworkSettings::updateModemList()
{
// find modems
ModemManager::scanDevices();
qDebug() << QStringLiteral("Scanning for modems...");
// loop over every modem
for (ModemManager::ModemDevice::Ptr device : ModemManager::modemDevices()) {
ModemManager::Modem::Ptr modem = device->modemInterface();
qDebug() << QStringLiteral("Found modem:") << device->uni();
m_modemList.push_back(new Modem(this, device, modem));
// update sims list if modem's list changes
connect(m_modemList[m_modemList.size() - 1], &Modem::simsChanged, this, [this]() -> void {
fillSims();
});
}
if (m_modemList.empty()) {
qDebug() << QStringLiteral("No modems found.");
}
// fill sim list
fillSims();
// update the currently selected modem
Q_EMIT selectedModemChanged();
}
void CellularNetworkSettings::fillSims()
{
for (auto p : m_simList) {
delete p;
}
m_simList.clear();
qDebug() << QStringLiteral("Scanning SIMs list...");
for (auto modem : m_modemList) {
auto sims = modem->sims();
for (auto sim : sims) {
qDebug() << QStringLiteral("Found SIM") << sim->uni() << sim->imsi();
m_simList.push_back(sim);
}
}
Q_EMIT simsChanged();
}
QList<InlineMessage *> CellularNetworkSettings::messages()
{
return m_messages;
}
void CellularNetworkSettings::addMessage(InlineMessage::Type type, QString msg)
{
m_messages.push_back(new InlineMessage{this, type, msg});
Q_EMIT messagesChanged();
}
void CellularNetworkSettings::removeMessage(int index)
{
if (index >= 0 && index < m_messages.size()) {
m_messages.removeAt(index);
Q_EMIT messagesChanged();
}
}
InlineMessage::InlineMessage(QObject *parent, Type type, QString message)
: QObject{parent}
, m_type{type}
, m_message{message}
{
}
InlineMessage::Type InlineMessage::type()
{
return m_type;
}
QString InlineMessage::message()
{
return m_message;
}
#include "cellularnetworksettings.moc"

View file

@ -0,0 +1,101 @@
/*
SPDX-FileCopyrightText: 2018 Martin Kacej <m.kacej@atlas.sk>
SPDX-FileCopyrightText: 2020-2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <QSharedPointer>
#include <KQuickAddons/ConfigModule>
#include "mobileproviders.h"
#include "modem.h"
#include "modemdetails.h"
#include "sim.h"
#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/ModemDevice>
class Sim;
class Modem;
class MobileProviders;
class InlineMessage : public QObject
{
Q_OBJECT
Q_PROPERTY(int type READ type NOTIFY typeChanged)
Q_PROPERTY(QString message READ message NOTIFY messageChanged)
public:
enum Type {
Information,
Positive,
Warning,
Error,
};
InlineMessage(QObject *parent = nullptr, Type type = Information, QString message = "");
Type type();
QString message();
Q_SIGNALS:
void typeChanged();
void messageChanged();
private:
Type m_type;
QString m_message;
};
class CellularNetworkSettings : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(bool modemFound READ modemFound NOTIFY modemFoundChanged)
Q_PROPERTY(Modem *selectedModem READ selectedModem NOTIFY selectedModemChanged)
Q_PROPERTY(QList<Sim *> sims READ sims NOTIFY simsChanged)
Q_PROPERTY(QList<InlineMessage *> messages READ messages NOTIFY messagesChanged)
public:
CellularNetworkSettings(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args);
static CellularNetworkSettings *instance();
Modem *selectedModem();
QList<Modem *> modems();
QList<Sim *> sims();
bool modemFound();
QList<InlineMessage *> messages();
void addMessage(InlineMessage::Type type, QString msg);
Q_INVOKABLE void removeMessage(int index);
Q_SIGNALS:
void modemFoundChanged();
void selectedModemChanged();
void simsChanged();
void messagesChanged();
private:
void updateModemList();
void fillSims();
QList<Modem *> m_modemList;
QList<Sim *> m_simList;
QList<InlineMessage *> m_messages;
static CellularNetworkSettings *staticInst;
};

View file

@ -0,0 +1,114 @@
{
"Categories": "Qt;KDE;X-KDE-settings-system;",
"KPlugin": {
"Description": "Management of cellular networks",
"Description[az]": "Mobil şəbəkələr idarəetməsi",
"Description[ca@valencia]": "Gestió de xarxes de telefonia mòbil",
"Description[ca]": "Gestió de xarxes de telefonia mòbil",
"Description[cs]": "Spravovat vaše mobilní sítě",
"Description[de]": "Verwaltung von Mobilfunknetzen",
"Description[en_GB]": "Management of cellular networks",
"Description[es]": "Gestión de redes móviles",
"Description[eu]": "Mugikor-sareen kudeaketa",
"Description[fi]": "Matkapuhelinverkkojen hallinta",
"Description[fr]": "Gestion de réseaux cellulaires",
"Description[hu]": "Mobilhálózatok kezelése",
"Description[ia]": "Gestion de retes cellular",
"Description[id]": "Pengelolaan jaringan seluler",
"Description[is]": "Umsjón farsímaneta",
"Description[it]": "Gestione delle reti cellulari",
"Description[ka]": "ფიჭური ქსელების მართვა",
"Description[ko]": "모바일 네트워크 관리",
"Description[lt]": "Korinių tinklų tvarkymas",
"Description[nl]": "Beheer van cellulaire netwerken",
"Description[nn]": "Handsaming av mobilnettverk",
"Description[pa]": "ਸੈਲੂਲਰ ਨੈੱਟਵਰਕਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰੋ",
"Description[pl]": "Zarządzanie sieciami komórkowymi",
"Description[pt]": "Gestão de redes móveis",
"Description[pt_BR]": "Gerenciamento de redes celulares",
"Description[ro]": "Gestiunea rețelelor celulare",
"Description[ru]": "Управление сотовыми сетями",
"Description[sl]": "Upravljanje mobilnih omrežij",
"Description[sv]": "Hantering av mobilnät",
"Description[tr]": "Hücresel ağların yönetimi",
"Description[uk]": "Керування стільниковими мережами",
"Description[vi]": "Quản lí các mạng di dộng",
"Description[x-test]": "xxManagement of cellular networksxx",
"Description[zh_CN]": "管理移动网络",
"Description[zh_TW]": "行動網路管理",
"FormFactors": [
"handset",
"tablet",
"mediacenter"
],
"Icon": "smartphone",
"Name": "Cellular Network",
"Name[az]": "Mobil Şəbəkə",
"Name[ca@valencia]": "Xarxa de telefonia mòbil",
"Name[ca]": "Xarxa de telefonia mòbil",
"Name[cs]": "Mobilní síť",
"Name[de]": "Mobilfunknetz",
"Name[en_GB]": "Cellular Network",
"Name[es]": "Red móvil",
"Name[eu]": "Mugikorrerako sareak",
"Name[fi]": "Matkapuhelinverkko",
"Name[fr]": "Réseau cellulaire",
"Name[hu]": "Mobilhálózatok",
"Name[ia]": "Rete Cellular",
"Name[id]": "Jaringan Seluler",
"Name[is]": "Farsímakerfi",
"Name[it]": "Rete cellulare",
"Name[ka]": "ფიჭური ქსელი",
"Name[ko]": "모바일 네트워크",
"Name[lt]": "Korinis tinklas",
"Name[nl]": "Cellulair netwerk",
"Name[nn]": "Mobilnettverk",
"Name[pa]": "ਸੈਲੂਲਰ ਨੈੱਟਵਰਕ",
"Name[pl]": "Sieci komórkowe",
"Name[pt]": "Rede Móvel",
"Name[pt_BR]": "Rede celular",
"Name[ro]": "Rețea celulară",
"Name[ru]": "Сотовая сеть",
"Name[sk]": "Mobilná sieť",
"Name[sl]": "Mobilno omrežje",
"Name[sv]": "Mobilnät",
"Name[tr]": "Hücresel Ağ",
"Name[uk]": "Стільникова мережа",
"Name[vi]": "Mạng di động",
"Name[x-test]": "xxCellular Networkxx",
"Name[zh_CN]": "移动网络",
"Name[zh_TW]": "行動網路"
},
"X-KDE-Keywords": "mobile,data,network",
"X-KDE-Keywords[az]": "mobile,data,network,verilənlər,şəbəkə,mobil",
"X-KDE-Keywords[ca@valencia]": "mòbil,dades,xarxa",
"X-KDE-Keywords[ca]": "mòbil,dades,xarxa",
"X-KDE-Keywords[cs]": "mobilní,data,síť",
"X-KDE-Keywords[de]": "mobil,daten,netzwerk,Mobiltelefon",
"X-KDE-Keywords[en_GB]": "mobile,data,network",
"X-KDE-Keywords[es]": "móvil,datos,red",
"X-KDE-Keywords[eu]": "mugikorra,datuak,sarea",
"X-KDE-Keywords[fi]": "mobiili,data,verkko",
"X-KDE-Keywords[fr]": "mobile, donnée, réseau",
"X-KDE-Keywords[hu]": "mobil,adat,hálózat",
"X-KDE-Keywords[ia]": "mobile,data,network",
"X-KDE-Keywords[it]": "mobile,dati,rete",
"X-KDE-Keywords[ko]": "mobile,data,network,모바일,데이터,네트워크",
"X-KDE-Keywords[lt]": "mobilusis,mobilieji,duomenys,tinklas",
"X-KDE-Keywords[nl]": "mobiel,gegevens,netwerk",
"X-KDE-Keywords[nn]": "mobil,mobilt,data,nettverk",
"X-KDE-Keywords[pa]": "ਮੋਬਾਈਲ,ਡਾਟਾ,ਨੈੱਟਵਰਕ",
"X-KDE-Keywords[pl]": "mobilne,komórkowe,przenośne,dane,sieć",
"X-KDE-Keywords[pt]": "móvel,dados,rede",
"X-KDE-Keywords[pt_BR]": "móvel,dados,rede",
"X-KDE-Keywords[ru]": "mobile,data,network,мобильная сеть,данные,сеть",
"X-KDE-Keywords[sk]": "mobilné,dáta,sieť",
"X-KDE-Keywords[sl]": "mobilno,podatki,omrežje",
"X-KDE-Keywords[sv]": "mobil,data,nätverk",
"X-KDE-Keywords[uk]": "mobile,data,network,мобільний,дані,мережа",
"X-KDE-Keywords[vi]": "mobile,data,network,di động,dữ liệu,mạng",
"X-KDE-Keywords[x-test]": "xxmobilexx,xxdataxx,xxnetworkxx",
"X-KDE-Keywords[zh_CN]": "mobile,data,network,移动,数据,网络,蜂窝,手机",
"X-KDE-System-Settings-Parent-Category": "network",
"X-KDE-Weight": 70
}

View file

@ -0,0 +1,336 @@
/*
SPDX-FileCopyrightText: 2010-2012 Lamarque Souza <lamarque@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "mobileproviders.h"
#include <QDebug>
#include <QFile>
#include <QLocale>
#include <QRegularExpression>
#include <QTextStream>
const QString MobileProviders::ProvidersFile = QStringLiteral("/usr/share/mobile-broadband-provider-info/serviceproviders.xml");
// adapted from https://invent.kde.org/plasma/plasma-nm/-/blob/master/libs/editor/mobileproviders.cpp
// we only use gsm, ignore cdma
bool localeAwareCompare(const QString &one, const QString &two)
{
return one.localeAwareCompare(two) < 0;
}
MobileProviders::MobileProviders()
{
for (int c = 1; c <= QLocale::LastCountry; c++) {
const auto country = static_cast<QLocale::Country>(c);
QLocale locale(QLocale::AnyLanguage, country);
if (locale.country() == country) {
const QString localeName = locale.name();
const auto idx = localeName.indexOf(QLatin1Char('_'));
if (idx != -1) {
const QString countryCode = localeName.mid(idx + 1);
QString countryName = locale.nativeCountryName();
if (countryName.isEmpty()) {
countryName = QLocale::countryToString(country);
}
mCountries.insert(countryCode, countryName);
}
}
}
mError = Success;
QFile file2(ProvidersFile);
if (file2.open(QIODevice::ReadOnly)) {
if (mDocProviders.setContent(&file2)) {
docElement = mDocProviders.documentElement();
if (docElement.isNull()) {
qWarning() << ProvidersFile << ": document is null";
mError = ProvidersIsNull;
} else {
if (docElement.isNull() || docElement.tagName() != "serviceproviders") {
qWarning() << ProvidersFile << ": wrong format";
mError = ProvidersWrongFormat;
} else {
if (docElement.attribute("format") != "2.0") {
qWarning() << ProvidersFile << ": mobile broadband provider database format '" << docElement.attribute("format") << "' not supported.";
mError = ProvidersFormatNotSupported;
} else {
// qCDebug(PLASMA_NM) << "Everything is alright so far";
}
}
}
}
file2.close();
} else {
qWarning() << "Error opening providers file" << ProvidersFile;
mError = ProvidersMissing;
}
}
MobileProviders::~MobileProviders()
{
}
QStringList MobileProviders::getCountryList() const
{
QStringList temp = mCountries.values();
std::sort(temp.begin(), temp.end(), localeAwareCompare);
return temp;
}
QString MobileProviders::countryFromLocale() const
{
const QString localeName = QLocale().name();
const auto idx = localeName.indexOf(QLatin1Char('_'));
if (idx != -1) {
return localeName.mid(idx + 1);
}
return QString();
}
QStringList MobileProviders::getApns(const QString &provider)
{
mApns.clear();
mNetworkIds.clear();
if (!mProvidersGsm.contains(provider)) {
return QStringList();
}
QDomNode n = mProvidersGsm[provider];
while (!n.isNull()) {
QDomElement e = n.toElement(); // <gsm | cdma>
if (!e.isNull() && e.tagName().toLower() == "gsm") {
QDomNode n2 = e.firstChild();
while (!n2.isNull()) {
QDomElement e2 = n2.toElement(); // <apn | network-id>
if (!e2.isNull() && e2.tagName().toLower() == "apn") {
bool isInternet = true;
QDomNode n3 = e2.firstChild();
while (!n3.isNull()) {
QDomElement e3 = n3.toElement(); // <usage>
if (!e3.isNull() && e3.tagName().toLower() == "usage" && !e3.attribute("type").isNull()
&& e3.attribute("type").toLower() != "internet") {
// qCDebug(PLASMA_NM) << "apn" << e2.attribute("value") << "ignored because of usage" << e3.attribute("type");
isInternet = false;
break;
}
n3 = n3.nextSibling();
}
if (isInternet) {
mApns.insert(e2.attribute("value"), e2.firstChild());
}
} else if (!e2.isNull() && e2.tagName().toLower() == "network-id") {
mNetworkIds.append(e2.attribute("mcc") + '-' + e2.attribute("mnc"));
}
n2 = n2.nextSibling();
}
}
n = n.nextSibling();
}
QStringList temp = mApns.keys();
temp.sort();
return temp;
}
ProviderData MobileProviders::parseProvider(const QDomNode &providerNode)
{
ProviderData result;
QMap<QString, QString> localizedProviderNames;
QDomNode c = providerNode.firstChild(); // <name | gsm | cdma>
bool hasGsm = false;
while (!c.isNull()) {
QDomElement ce = c.toElement();
if (ce.tagName().toLower() == QLatin1String("gsm")) {
QDomNode gsmNode = c.firstChild();
while (!gsmNode.isNull()) {
QDomElement gsmElement = gsmNode.toElement();
if (gsmElement.tagName().toLower() == QLatin1String("network-id")) {
result.mccmncs.append(gsmElement.attribute("mcc") + gsmElement.attribute("mnc"));
}
gsmNode = gsmNode.nextSibling();
}
hasGsm = true;
} else if (ce.tagName().toLower() == QLatin1String("name")) {
QString lang = ce.attribute("xml:lang");
if (lang.isEmpty()) {
lang = "en"; // English is default
} else {
lang = lang.toLower();
lang.remove(QRegularExpression(QStringLiteral("\\-.*$"))); // Remove everything after '-' in xml:lang attribute.
}
localizedProviderNames.insert(lang, ce.text());
}
c = c.nextSibling();
}
result.name = getNameByLocale(localizedProviderNames);
const QString name = result.name;
if (hasGsm) {
mProvidersGsm.insert(name, providerNode.firstChild());
}
return result;
}
QStringList MobileProviders::getProvidersFromMCCMNC(const QString &targetMccMnc)
{
QStringList result;
QDomNode n = docElement.firstChild();
while (!n.isNull()) {
QDomElement e = n.toElement(); // <country ...>
if (!e.isNull()) {
QDomNode n2 = e.firstChild();
while (!n2.isNull()) {
QDomElement e2 = n2.toElement(); // <provider ...>
if (!e2.isNull() && e2.tagName().toLower() == "provider") {
ProviderData data = parseProvider(e2);
if (data.mccmncs.contains(targetMccMnc)) {
result << data.name;
}
}
n2 = n2.nextSibling();
}
}
n = n.nextSibling();
}
return result;
}
QStringList MobileProviders::getNetworkIds(const QString &provider)
{
if (mNetworkIds.isEmpty()) {
getApns(provider);
}
return mNetworkIds;
}
QVariantMap MobileProviders::getApnInfo(const QString &apn)
{
QVariantMap temp;
QDomNode n = mApns[apn];
QStringList dnsList;
QMap<QString, QString> localizedPlanNames;
while (!n.isNull()) {
QDomElement e = n.toElement(); // <name|username|password|dns(*)>
if (!e.isNull()) {
if (e.tagName().toLower() == "name") {
QString lang = e.attribute("xml:lang");
if (lang.isEmpty()) {
lang = "en"; // English is default
} else {
lang = lang.toLower();
lang.remove(QRegularExpression(QStringLiteral("\\-.*$"))); // Remove everything after '-' in xml:lang attribute.
}
localizedPlanNames.insert(lang, e.text());
} else if (e.tagName().toLower() == "username") {
temp.insert("username", e.text());
} else if (e.tagName().toLower() == "password") {
temp.insert("password", e.text());
} else if (e.tagName().toLower() == "dns") {
dnsList.append(e.text());
} else if (e.tagName().toLower() == "usage") {
temp.insert("usageType", e.attribute("type"));
}
}
n = n.nextSibling();
}
QString name = getNameByLocale(localizedPlanNames);
if (!name.isEmpty()) {
temp.insert("name", QVariant::fromValue(name));
}
temp.insert("number", getGsmNumber());
temp.insert("apn", apn);
temp.insert("dnsList", dnsList);
return temp;
}
QVariantMap MobileProviders::getCdmaInfo(const QString &provider)
{
if (!mProvidersCdma.contains(provider)) {
return QVariantMap();
}
QVariantMap temp;
QDomNode n = mProvidersCdma[provider];
QStringList sidList;
while (!n.isNull()) {
QDomElement e = n.toElement(); // <gsm or cdma ...>
if (!e.isNull() && e.tagName().toLower() == "cdma") {
QDomNode n2 = e.firstChild();
while (!n2.isNull()) {
QDomElement e2 = n2.toElement(); // <name | username | password | sid>
if (!e2.isNull()) {
if (e2.tagName().toLower() == "username") {
temp.insert("username", e2.text());
} else if (e2.tagName().toLower() == "password") {
temp.insert("password", e2.text());
} else if (e2.tagName().toLower() == "sid") {
sidList.append(e2.text());
}
}
n2 = n2.nextSibling();
}
}
n = n.nextSibling();
}
temp.insert("number", getCdmaNumber());
temp.insert("sidList", sidList);
return temp;
}
QString MobileProviders::getNameByLocale(const QMap<QString, QString> &localizedNames) const
{
QString name;
const QStringList locales = QLocale().uiLanguages();
for (const QString &locale : locales) {
QString language = locale.split(QLatin1Char('-')).at(0);
if (localizedNames.contains(language)) {
return localizedNames[language];
}
}
name = localizedNames["en"];
// Use any language if no proper localized name were found.
if (name.isEmpty() && !localizedNames.isEmpty()) {
name = localizedNames.constBegin().value();
}
return name;
}

View file

@ -0,0 +1,81 @@
/*
SPDX-FileCopyrightText: 2010-2012 Lamarque Souza <lamarque@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef PLASMA_NM_MOBILE_PROVIDERS_H
#define PLASMA_NM_MOBILE_PROVIDERS_H
#include <QDomDocument>
#include <QHash>
#include <QStringList>
#include <QVariantMap>
#include <NetworkManagerQt/ConnectionSettings>
// adapted from https://invent.kde.org/plasma/plasma-nm/-/blob/master/libs/editor/mobileproviders.h
// we only use gsm, ignore cdma
struct ProviderData {
QStringList mccmncs;
QString name;
};
class Q_DECL_EXPORT MobileProviders
{
public:
static const QString ProvidersFile;
enum ErrorCodes {
Success,
CountryCodesMissing,
ProvidersMissing,
ProvidersIsNull,
ProvidersWrongFormat,
ProvidersFormatNotSupported,
};
MobileProviders();
~MobileProviders();
QStringList getCountryList() const;
QString countryFromLocale() const;
QString getCountryName(const QString &key) const
{
return mCountries.value(key);
}
QStringList getApns(const QString &provider);
QStringList getNetworkIds(const QString &provider);
QVariantMap getApnInfo(const QString &apn);
QVariantMap getCdmaInfo(const QString &provider);
QStringList getProvidersFromMCCMNC(const QString &mccmnc);
QString getGsmNumber() const
{
return QString("*99#");
}
QString getCdmaNumber() const
{
return QString("#777");
}
inline ErrorCodes getError()
{
return mError;
}
private:
ProviderData parseProvider(const QDomNode &providerNode);
QHash<QString, QString> mCountries;
QHash<QString, QString> mMccMncToName;
QMap<QString, QDomNode> mProvidersGsm;
QMap<QString, QDomNode> mProvidersCdma;
QMap<QString, QDomNode> mApns;
QStringList mNetworkIds;
QDomDocument mDocProviders;
QDomElement docElement;
ErrorCodes mError;
QString getNameByLocale(const QMap<QString, QString> &names) const;
};
#endif // PLASMA_NM_MOBILE_PROVIDERS_H

View file

@ -0,0 +1,527 @@
// SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "modem.h"
#include <utility>
#include <KLocalizedString>
#include <KUser>
Modem::Modem(QObject *parent)
: QObject{parent}
{
}
Modem::Modem(QObject *parent, ModemManager::ModemDevice::Ptr mmModem, ModemManager::Modem::Ptr mmInterface)
: QObject{parent}
, m_mmModem{mmModem}
, m_nmModem{nullptr}
, m_mmInterface{mmInterface}
{
// TODO multi-sim support
m_sims = {new Sim{this, this, m_mmModem->sim(), m_mmInterface, m_mm3gppDevice}};
connect(m_mmModem.data(), &ModemManager::ModemDevice::simAdded, this, &Modem::simsChanged);
connect(m_mmModem.data(), &ModemManager::ModemDevice::simAdded, this, &Modem::hasSimChanged);
connect(m_mmModem.data(), &ModemManager::ModemDevice::simRemoved, this, &Modem::simsChanged);
connect(m_mmModem.data(), &ModemManager::ModemDevice::simRemoved, this, &Modem::hasSimChanged);
if (m_mmModem->sim()) {
connect(m_mmModem->sim().get(), &ModemManager::Sim::simIdentifierChanged, this, &Modem::hasSimChanged);
}
connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionAdded, this, &Modem::mobileDataEnabledChanged);
connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionRemoved, this, &Modem::mobileDataEnabledChanged);
connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionAdded, this, &Modem::mobileDataEnabledChanged);
connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionRemoved, this, &Modem::mobileDataEnabledChanged);
connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceAdded, this, &Modem::findNetworkManagerDevice);
connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceRemoved, this, &Modem::findNetworkManagerDevice);
// this is guaranteed to be a GSM modem
m_mm3gppDevice = m_mmModem->interface(ModemManager::ModemDevice::GsmInterface).objectCast<ModemManager::Modem3gpp>();
// if no sim is inserted, m_mm3gppDevice is nullptr
if (m_mm3gppDevice) {
m_mm3gppDevice->setTimeout(60000); // scanning networks likely takes longer than the default timeout
}
// find networkmanager modem, if it exists
findNetworkManagerDevice();
// we need to initialize it after m_mm3gppDevice has been set
m_details = new ModemDetails(this, this);
}
void Modem::findNetworkManagerDevice()
{
m_nmModem = nullptr;
// find networkmanager modem device
for (NetworkManager::Device::Ptr nmDevice : NetworkManager::networkInterfaces()) {
if (nmDevice->udi() == m_mmModem->uni()) {
m_nmModem = nmDevice.objectCast<NetworkManager::ModemDevice>();
}
}
if (m_nmModem) {
connect(m_nmModem.data(), &NetworkManager::Device::autoconnectChanged, this, &Modem::mobileDataEnabledChanged);
connect(m_nmModem.data(), &NetworkManager::Device::stateChanged, this, &Modem::mobileDataEnabledChanged);
connect(m_nmModem.data(), &NetworkManager::Device::availableConnectionAppeared, this, &Modem::mobileDataEnabledChanged);
connect(m_nmModem.data(), &NetworkManager::Device::availableConnectionDisappeared, this, &Modem::mobileDataEnabledChanged);
connect(m_nmModem.data(), &NetworkManager::ModemDevice::availableConnectionChanged, this, [this]() -> void {
refreshProfiles();
});
connect(m_nmModem.data(), &NetworkManager::ModemDevice::activeConnectionChanged, this, [this]() -> void {
refreshProfiles();
Q_EMIT activeConnectionUniChanged();
});
connect(m_nmModem.data(), &NetworkManager::ModemDevice::stateChanged, this, [this](auto newstate, auto oldstate, auto reason) -> void {
qDebug() << QStringLiteral("Modem") << m_nmModem->uni() << QStringLiteral("changed state:") << nmDeviceStateStr(oldstate) << QStringLiteral("->")
<< nmDeviceStateStr(newstate) << QStringLiteral("due to:") << reason;
});
// add connection profiles
refreshProfiles();
}
Q_EMIT nmModemChanged();
Q_EMIT mobileDataEnabledChanged();
Q_EMIT mobileDataSupportedChanged();
}
ModemDetails *Modem::modemDetails() const
{
return m_details;
}
QString Modem::displayId() const
{
// in the form /org/freedesktop/ModemManager1/Modem/0
QStringList uniSplit = uni().split("/");
return uniSplit.count() == 0 ? QStringLiteral("(empty)") : QString(uniSplit[uniSplit.size() - 1]);
}
QString Modem::uni() const
{
return m_mmInterface->uni();
}
QString Modem::activeConnectionUni() const
{
if (m_nmModem && m_nmModem->activeConnection() && m_nmModem->activeConnection()->connection()) {
return m_nmModem->activeConnection()->connection()->uuid();
}
return QString();
}
void Modem::reset()
{
qDebug() << QStringLiteral("Resetting the modem...");
QDBusPendingReply<void> reply = m_mmInterface->reset();
reply.waitForFinished();
if (reply.isError()) {
qDebug() << QStringLiteral("Error resetting the modem:") << reply.error().message();
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error resetting the modem: %1", reply.error().message()));
}
}
bool Modem::mobileDataSupported() const
{
return m_nmModem && hasSim();
}
bool Modem::needsAPNAdded() const
{
return m_nmModem && mobileDataSupported() && m_nmModem->availableConnections().count() == 0;
}
bool Modem::mobileDataEnabled() const
{
// no modem -> no mobile data -> report disabled
if (!m_nmModem) {
return false;
}
// mobile data already activated -> report enabled
if (m_nmModem->state() == NetworkManager::Device::Activated) {
return true;
}
// autoconnect disabled on the entire modem -> report disabled
if (!m_nmModem->autoconnect()) {
return false;
}
// at least one connection set to autoconnect -> report enabled
for (NetworkManager::Connection::Ptr con : m_nmModem->availableConnections()) {
if (con->settings()->autoconnect()) {
return true;
}
}
// modem, but no connection, set to autoconnect -> report disabled
return false;
}
void Modem::setMobileDataEnabled(bool enabled)
{
if (!m_nmModem) {
return;
}
if (!enabled) {
m_nmModem->setAutoconnect(false);
// we need to also set all connections to not autoconnect (#182)
for (NetworkManager::Connection::Ptr con : m_nmModem->availableConnections()) {
con->settings()->setAutoconnect(false);
con->update(con->settings()->toMap());
}
m_nmModem->disconnectInterface();
} else {
m_nmModem->setAutoconnect(true);
// activate the connection that was last used
QDateTime latestTimestamp;
NetworkManager::Connection::Ptr latestCon;
for (NetworkManager::Connection::Ptr con : m_nmModem->availableConnections()) {
QDateTime timestamp = con->settings()->timestamp();
// if con was not used yet, skip it, otherwise:
// if we have no latestTimestamp yet, con is the latest
// otherwise, compare the timestamps
// in case of a tie, use the first connection that was found
if (!timestamp.isNull() && (latestTimestamp.isNull() || timestamp > latestTimestamp)) {
latestTimestamp = timestamp;
latestCon = con;
}
}
// if we found the last used connection
if (!latestCon.isNull()) {
// set it to autoconnect and connect it immediately
latestCon->settings()->setAutoconnect(true);
latestCon->update(latestCon->settings()->toMap());
NetworkManager::activateConnection(latestCon->path(), m_nmModem->uni(), "");
}
}
}
bool Modem::isRoaming() const
{
if (!m_nmModem) {
return false;
}
if (m_nmModem->activeConnection() && m_nmModem->activeConnection()->connection()) {
auto connection = m_nmModem->activeConnection()->connection();
NetworkManager::GsmSetting::Ptr gsmSetting = connection->settings()->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
if (gsmSetting) {
return !gsmSetting->homeOnly();
}
}
return false;
}
void Modem::setIsRoaming(bool roaming)
{
if (!m_nmModem) {
return;
}
if (m_nmModem->activeConnection() && m_nmModem->activeConnection()->connection()) {
auto connection = m_nmModem->activeConnection()->connection();
NetworkManager::GsmSetting::Ptr gsmSetting = connection->settings()->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
if (gsmSetting) {
gsmSetting->setHomeOnly(!roaming); // set roaming setting
QDBusPendingReply reply = connection->update(connection->settings()->toMap());
reply.waitForFinished();
if (reply.isError()) {
qWarning() << QStringLiteral("Error updating connection settings for") << connection->uuid() << QStringLiteral(":") << reply.error().message()
<< QStringLiteral(".");
CellularNetworkSettings::instance()->addMessage(
InlineMessage::Error,
i18n("Error updating connection settings for %1: %2.", connection->uuid(), reply.error().message()));
} else {
qDebug() << QStringLiteral("Successfully updated connection settings") << connection->uuid() << QStringLiteral(".");
}
}
// the connection uni has changed, refresh the profiles list
refreshProfiles();
Q_EMIT activeConnectionUniChanged();
}
}
bool Modem::hasSim() const
{
if (!m_mmModem) {
return false;
}
return m_mmModem && m_mmModem->sim() && m_mmModem->sim()->uni() != QStringLiteral("/");
}
QList<ProfileSettings *> &Modem::profileList()
{
return m_profileList;
}
void Modem::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 Modem::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 << ".";
// cache roaming setting
bool roaming = isRoaming();
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();
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error activating connection: %1", reply.error().message()));
return;
}
// set roaming settings separately (since it changes the uni)
setIsRoaming(roaming);
}
void Modem::addProfile(QString name, QString apn, QString username, QString password, 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->setHomeOnly(!isRoaming());
gsmSetting->setInitialized(true);
QDBusPendingReply<QDBusObjectPath> reply = NetworkManager::addAndActivateConnection(settings->toMap(), m_nmModem->uni(), "");
reply.waitForFinished();
if (reply.isError()) {
qWarning() << QStringLiteral("Error adding connection:") << reply.error().message();
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error adding connection: %1", reply.error().message()));
} else {
qDebug() << QStringLiteral("Successfully added a new connection") << name << QStringLiteral("with APN") << apn << ".";
}
}
void Modem::removeProfile(const QString &connectionUni)
{
NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni);
if (!con) {
qWarning() << QStringLiteral("Could not find connection") << connectionUni << QStringLiteral("to update!");
return;
}
QDBusPendingReply reply = con->remove();
reply.waitForFinished();
if (reply.isError()) {
qWarning() << QStringLiteral("Error removing connection") << reply.error().message();
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error removing connection: %1", reply.error().message()));
}
}
void Modem::updateProfile(QString connectionUni, QString name, QString apn, QString username, QString password, QString networkType)
{
NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni);
if (!con) {
qWarning() << QStringLiteral("Could not find connection") << connectionUni << QStringLiteral("to update!");
return;
}
NetworkManager::ConnectionSettings::Ptr conSettings = con->settings();
if (!conSettings) {
qWarning() << QStringLiteral("Could not find connection settings for") << connectionUni << QStringLiteral("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 == "" ? NetworkManager::Setting::NotRequired : NetworkManager::Setting::AgentOwned);
gsmSetting->setNetworkType(ProfileSettings::networkTypeFlag(networkType));
gsmSetting->setHomeOnly(!isRoaming());
gsmSetting->setInitialized(true);
QDBusPendingReply reply = con->update(conSettings->toMap());
reply.waitForFinished();
if (reply.isError()) {
qWarning() << QStringLiteral("Error updating connection settings for") << connectionUni << QStringLiteral(":") << reply.error().message()
<< QStringLiteral(".");
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error,
i18n("Error updating connection settings for %1: %2.", connectionUni, reply.error().message()));
} else {
qDebug() << QStringLiteral("Successfully updated connection settings") << connectionUni << QStringLiteral(".");
}
}
void Modem::addDetectedProfileSettings()
{
if (!m_mmModem) {
qWarning() << "ModemManager device missing, cannot detect profile settings";
return;
}
if (!hasSim()) {
qWarning() << "No SIM found, cannot detect profile settings";
return;
}
if (!m_mm3gppDevice) {
qWarning() << "3gpp object not found, cannot detect profile settings";
return;
}
bool found = false;
static MobileProviders mobileProviders{};
QString operatorCode = m_mm3gppDevice->operatorCode();
qWarning() << QStringLiteral("Detecting profile settings. Using MCCMNC:") << operatorCode;
// lookup apns with mccmnc codes
for (QString &provider : mobileProviders.getProvidersFromMCCMNC(operatorCode)) {
qWarning() << QStringLiteral("Provider:") << provider;
for (auto apn : mobileProviders.getApns(provider)) {
QVariantMap apnInfo = mobileProviders.getApnInfo(apn);
qWarning() << QStringLiteral("Found gsm profile settings. Type:") << apnInfo[QStringLiteral("usageType")];
// only add mobile data apns (not mms)
if (apnInfo[QStringLiteral("usageType")].toString() == QStringLiteral("internet")) {
found = true;
QString name = provider;
if (!apnInfo[QStringLiteral("name")].isNull()) {
name += " - " + apnInfo[QStringLiteral("name")].toString();
}
addProfile(name,
apn,
apnInfo[QStringLiteral("username")].toString(),
apnInfo[QStringLiteral("password")].toString(),
QStringLiteral("4G/3G/2G"));
}
// TODO in the future for MMS settings, add else if here for == "mms"
}
}
if (!found) {
qDebug() << QStringLiteral("No profiles were found.");
Q_EMIT couldNotAutodetectSettings();
}
}
QList<Sim *> Modem::sims()
{
return m_sims;
}
ModemManager::ModemDevice::Ptr Modem::mmModemDevice()
{
return m_mmModem;
}
NetworkManager::ModemDevice::Ptr Modem::nmModemDevice()
{
return m_nmModem;
}
ModemManager::Modem::Ptr Modem::mmModemInterface()
{
return m_mmInterface;
}
QString Modem::nmDeviceStateStr(NetworkManager::Device::State state)
{
if (state == NetworkManager::Device::State::UnknownState)
return i18n("Unknown");
else if (state == NetworkManager::Device::State::Unmanaged)
return i18n("Unmanaged");
else if (state == NetworkManager::Device::State::Unavailable)
return i18n("Unavailable");
else if (state == NetworkManager::Device::State::Disconnected)
return i18n("Disconnected");
else if (state == NetworkManager::Device::State::Preparing)
return i18n("Preparing");
else if (state == NetworkManager::Device::State::ConfiguringHardware)
return i18n("ConfiguringHardware");
else if (state == NetworkManager::Device::State::NeedAuth)
return i18n("NeedAuth");
else if (state == NetworkManager::Device::State::ConfiguringIp)
return i18n("ConfiguringIp");
else if (state == NetworkManager::Device::State::CheckingIp)
return i18n("CheckingIp");
else if (state == NetworkManager::Device::State::WaitingForSecondaries)
return i18n("WaitingForSecondaries");
else if (state == NetworkManager::Device::State::Activated)
return i18n("Activated");
else if (state == NetworkManager::Device::State::Deactivating)
return i18n("Deactivating");
else if (state == NetworkManager::Device::State::Failed)
return i18n("Failed");
else
return "";
}

View file

@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "cellularnetworksettings.h"
#include "modemdetails.h"
#include "profilesettings.h"
#include "sim.h"
#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;
class Sim;
class AvailableNetwork;
class ModemDetails;
class MobileProviders;
// only supports GSM/UMTS/LTE
class Modem : public QObject
{
Q_OBJECT
Q_PROPERTY(ModemDetails *details READ modemDetails NOTIFY modemDetailsChanged)
Q_PROPERTY(QString uni READ uni NOTIFY uniChanged)
Q_PROPERTY(QString displayId READ displayId NOTIFY displayIdChanged)
Q_PROPERTY(bool isRoaming READ isRoaming WRITE setIsRoaming NOTIFY isRoamingChanged)
Q_PROPERTY(bool hasSim READ hasSim NOTIFY hasSimChanged)
Q_PROPERTY(QList<ProfileSettings *> profiles READ profileList NOTIFY profileListChanged)
Q_PROPERTY(QString activeConnectionUni READ activeConnectionUni NOTIFY activeConnectionUniChanged)
Q_PROPERTY(bool mobileDataEnabled READ mobileDataEnabled WRITE setMobileDataEnabled NOTIFY mobileDataEnabledChanged)
Q_PROPERTY(bool mobileDataSupported READ mobileDataSupported NOTIFY mobileDataSupportedChanged)
Q_PROPERTY(bool needsAPNAdded READ needsAPNAdded NOTIFY mobileDataEnabledChanged)
public:
Modem(QObject *parent = nullptr);
Modem(QObject *parent, ModemManager::ModemDevice::Ptr mmModem, ModemManager::Modem::Ptr m_mmInterface);
ModemDetails *modemDetails() const;
QString displayId() const; // splits uni and obtains the number suffix
QString uni() const;
QString activeConnectionUni() const;
Q_INVOKABLE void reset();
bool mobileDataEnabled() const;
void setMobileDataEnabled(bool enabled);
bool mobileDataSupported() const;
bool needsAPNAdded() const;
bool isRoaming() const;
void setIsRoaming(bool roaming);
bool hasSim() const;
// connection profiles
QList<ProfileSettings *> &profileList();
void refreshProfiles();
Q_INVOKABLE void activateProfile(const QString &connectionUni);
Q_INVOKABLE void addProfile(QString name, QString apn, QString username, QString password, QString networkType);
Q_INVOKABLE void removeProfile(const QString &connectionUni);
Q_INVOKABLE void updateProfile(QString connectionUni, QString name, QString apn, QString username, QString password, QString networkType);
Q_INVOKABLE void addDetectedProfileSettings(); // detect modem connection settings (ex. apn) and add a new connection
QList<Sim *> sims();
ModemManager::ModemDevice::Ptr mmModemDevice();
NetworkManager::ModemDevice::Ptr nmModemDevice();
ModemManager::Modem::Ptr mmModemInterface();
Q_SIGNALS:
void modemDetailsChanged();
void uniChanged();
void displayIdChanged();
void activeConnectionUniChanged();
void nmModemChanged();
void mobileDataEnabledChanged();
void mobileDataSupportedChanged();
void isRoamingChanged();
void simsChanged();
void hasSimChanged();
void profileListChanged();
void couldNotAutodetectSettings();
private:
void findNetworkManagerDevice();
QString nmDeviceStateStr(NetworkManager::Device::State state);
ModemDetails *m_details;
ModemManager::ModemDevice::Ptr m_mmModem;
NetworkManager::ModemDevice::Ptr m_nmModem; // may be a nullptr if the nm modem hasn't been found yet
ModemManager::Modem::Ptr m_mmInterface = nullptr;
ModemManager::Modem3gpp::Ptr m_mm3gppDevice = nullptr; // this may be a nullptr if no sim is inserted
QList<Sim *> m_sims;
QList<ProfileSettings *> m_profileList;
friend class ModemDetails;
};

View file

@ -0,0 +1,547 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "modemdetails.h"
#include <KLocalizedString>
#include <QDBusPendingCallWatcher>
ModemDetails::ModemDetails(QObject *parent, Modem *modem)
: QObject{parent}
, m_modem{modem}
, m_scanNetworkWatcher{nullptr}
, m_isScanningNetworks{false}
, m_cachedScannedNetworks{}
{
auto mmInterfacePointer = m_modem->m_mmInterface.data();
connect(mmInterfacePointer, &ModemManager::Modem::accessTechnologiesChanged, this, [this]() -> void {
Q_EMIT accessTechnologiesChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::deviceChanged, this, [this]() -> void {
Q_EMIT deviceChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::deviceIdentifierChanged, this, [this]() -> void {
Q_EMIT deviceIdentifierChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::driversChanged, this, [this]() -> void {
Q_EMIT driversChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::equipmentIdentifierChanged, this, [this]() -> void {
Q_EMIT equipmentIdentifierChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::manufacturerChanged, this, [this]() -> void {
Q_EMIT manufacturerChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::modelChanged, this, [this]() -> void {
Q_EMIT modelChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::ownNumbersChanged, this, [this]() -> void {
Q_EMIT ownNumbersChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::pluginChanged, this, [this]() -> void {
Q_EMIT pluginChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::powerStateChanged, this, [this]() -> void {
Q_EMIT powerStateChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::revisionChanged, this, [this]() -> void {
Q_EMIT revisionChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::signalQualityChanged, this, [this]() -> void {
Q_EMIT signalQualityChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::simPathChanged, this, [this]() -> void {
Q_EMIT simPathChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::stateChanged, this, [this]() -> void {
Q_EMIT stateChanged();
});
connect(mmInterfacePointer, &ModemManager::Modem::stateFailedReasonChanged, this, [this]() -> void {
Q_EMIT stateFailedReasonChanged();
});
if (m_modem->m_mm3gppDevice) {
connect(m_modem->m_mm3gppDevice.data(), &ModemManager::Modem3gpp::operatorCodeChanged, this, [this]() -> void {
Q_EMIT operatorCodeChanged();
});
connect(m_modem->m_mm3gppDevice.data(), &ModemManager::Modem3gpp::operatorNameChanged, this, [this]() -> void {
Q_EMIT operatorNameChanged();
});
connect(m_modem->m_mm3gppDevice.data(), &ModemManager::Modem3gpp::registrationStateChanged, this, [this]() -> void {
Q_EMIT registrationStateChanged();
Q_EMIT m_modem->isRoamingChanged();
});
} else {
qWarning() << QStringLiteral("3gpp device not found!");
}
// m_modem->m_nmModem may be nullptr, listen for updates
connect(m_modem, &Modem::nmModemChanged, this, &ModemDetails::updateNMSignals);
updateNMSignals();
}
void ModemDetails::updateNMSignals()
{
if (m_modem->m_nmModem) {
connect(m_modem->m_nmModem.data(), &NetworkManager::ModemDevice::firmwareVersionChanged, this, [this]() -> void {
Q_EMIT firmwareVersionChanged();
});
connect(m_modem->m_nmModem.data(), &NetworkManager::ModemDevice::interfaceNameChanged, this, [this]() -> void {
Q_EMIT interfaceNameChanged();
});
connect(m_modem->m_nmModem.data(), &NetworkManager::ModemDevice::meteredChanged, this, [this]() -> void {
Q_EMIT meteredChanged();
});
}
}
ModemDetails &ModemDetails::operator=(ModemDetails &&other)
{
swap(other);
return *this;
}
void ModemDetails::swap(ModemDetails &other)
{
std::swap(m_modem, other.m_modem);
std::swap(m_cachedScannedNetworks, other.m_cachedScannedNetworks);
std::swap(m_isScanningNetworks, other.m_isScanningNetworks);
std::swap(m_scanNetworkWatcher, other.m_scanNetworkWatcher);
}
QStringList ModemDetails::accessTechnologies()
{
QStringList list;
auto flags = m_modem->m_mmInterface->accessTechnologies();
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN) {
list.push_back(i18n("Unknown"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_POTS) {
list.push_back(i18n("POTS"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_GSM) {
list.push_back(i18n("GSM"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT) {
list.push_back(i18n("GSM Compact"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_GPRS) {
list.push_back(i18n("GPRS"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_EDGE) {
list.push_back(i18n("EDGE"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_UMTS) {
list.push_back(i18n("UMTS"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_HSDPA) {
list.push_back(i18n("HSDPA"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_HSUPA) {
list.push_back(i18n("HSUPA"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_HSPA) {
list.push_back(i18n("HSPA"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS) {
list.push_back(i18n("HSPA+"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_1XRTT) {
list.push_back(i18n("CDMA2000 1xRTT"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_EVDO0) {
list.push_back(i18n("CDMA2000 EVDO-0"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_EVDOA) {
list.push_back(i18n("CDMA2000 EVDO-A"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_EVDOB) {
list.push_back(i18n("CDMA2000 EVDO-B"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_LTE) {
list.push_back(i18n("LTE"));
}
if (flags & MM_MODEM_ACCESS_TECHNOLOGY_5GNR) {
list.push_back(i18n("5GNR"));
}
return list;
}
QString ModemDetails::device()
{
return m_modem->m_mmInterface->device();
}
QString ModemDetails::deviceIdentifier()
{
return m_modem->m_mmInterface->deviceIdentifier();
}
QStringList ModemDetails::drivers()
{
return m_modem->m_mmInterface->drivers();
}
QString ModemDetails::equipmentIdentifier()
{
return m_modem->m_mmInterface->equipmentIdentifier();
}
bool ModemDetails::isEnabled()
{
return m_modem->m_mmInterface->isEnabled();
}
QString ModemDetails::manufacturer()
{
return m_modem->m_mmInterface->manufacturer();
}
QString ModemDetails::model()
{
return m_modem->m_mmInterface->model();
}
QStringList ModemDetails::ownNumbers()
{
return m_modem->m_mmInterface->ownNumbers();
}
QString ModemDetails::plugin()
{
return m_modem->m_mmInterface->plugin();
}
QString ModemDetails::powerState()
{
switch (m_modem->m_mmInterface->powerState()) {
case MM_MODEM_POWER_STATE_UNKNOWN:
return i18n("Unknown");
case MM_MODEM_POWER_STATE_OFF:
return i18n("Off");
case MM_MODEM_POWER_STATE_LOW:
return i18n("Low-power mode");
case MM_MODEM_POWER_STATE_ON:
return i18n("Full power mode");
}
return {};
}
QString ModemDetails::revision()
{
return m_modem->m_mmInterface->revision();
}
uint ModemDetails::signalQuality()
{
return m_modem->m_mmInterface->signalQuality().signal;
}
QString ModemDetails::simPath()
{
return m_modem->m_mmInterface->simPath();
}
QString ModemDetails::state()
{
switch (m_modem->m_mmInterface->state()) {
case MM_MODEM_STATE_FAILED:
return i18n("Failed");
case MM_MODEM_STATE_UNKNOWN:
return i18n("Unknown");
case MM_MODEM_STATE_INITIALIZING:
return i18n("Initializing");
case MM_MODEM_STATE_LOCKED:
return i18n("Locked");
case MM_MODEM_STATE_DISABLED:
return i18n("Disabled");
case MM_MODEM_STATE_DISABLING:
return i18n("Disabling");
case MM_MODEM_STATE_ENABLING:
return i18n("Enabling");
case MM_MODEM_STATE_ENABLED:
return i18n("Enabled");
case MM_MODEM_STATE_SEARCHING:
return i18n("Searching for network provider");
case MM_MODEM_STATE_REGISTERED:
return i18n("Registered with network provider");
case MM_MODEM_STATE_DISCONNECTING:
return i18n("Disconnecting");
case MM_MODEM_STATE_CONNECTING:
return i18n("Connecting");
case MM_MODEM_STATE_CONNECTED:
return i18n("Connected");
}
return {};
}
QString ModemDetails::stateFailedReason()
{
switch (m_modem->m_mmInterface->stateFailedReason()) {
case MM_MODEM_STATE_FAILED_REASON_NONE:
return i18n("No error.");
case MM_MODEM_STATE_FAILED_REASON_UNKNOWN:
return i18n("Unknown error.");
case MM_MODEM_STATE_FAILED_REASON_SIM_MISSING:
return i18n("SIM is required but missing.");
case MM_MODEM_STATE_FAILED_REASON_SIM_ERROR:
return i18n("SIM is available but unusable.");
}
return {};
}
QString ModemDetails::operatorCode()
{
return m_modem->m_mm3gppDevice ? m_modem->m_mm3gppDevice->operatorCode() : QString{};
}
QString ModemDetails::operatorName()
{
return m_modem->m_mm3gppDevice ? m_modem->m_mm3gppDevice->operatorName() : QString{};
}
QString ModemDetails::registrationState()
{
if (!m_modem->m_mm3gppDevice) {
return QString{};
}
switch (m_modem->m_mm3gppDevice->registrationState()) {
case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE:
return i18n("Not registered, not searching for new operator to register.");
case MM_MODEM_3GPP_REGISTRATION_STATE_HOME:
return i18n("Registered on home network.");
case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING:
return i18n("Not registered, searching for new operator to register with.");
case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED:
return i18n("Registration denied.");
case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN:
return i18n("Unknown registration status.");
case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
return i18n("Registered on a roaming network.");
case MM_MODEM_3GPP_REGISTRATION_STATE_HOME_SMS_ONLY:
return i18n("Registered for \"SMS only\", on home network.");
case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_SMS_ONLY:
return i18n("Registered for \"SMS only\", roaming network.");
case MM_MODEM_3GPP_REGISTRATION_STATE_EMERGENCY_ONLY:
return i18n("Emergency services only.");
case MM_MODEM_3GPP_REGISTRATION_STATE_HOME_CSFB_NOT_PREFERRED:
return i18n("Registered for \"CSFB not preferred\", home network.");
case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED:
return i18n("Registered for \"CSFB not preferred\", roaming network.");
case MM_MODEM_3GPP_REGISTRATION_STATE_ATTACHED_RLOS:
return i18n("Attached for access to Restricted Local Operator Services.");
}
return {};
}
Q_DECLARE_METATYPE(MMModem3gppNetworkAvailability)
Q_DECLARE_METATYPE(MMModemAccessTechnology)
QList<AvailableNetwork *> ModemDetails::networks()
{
return m_cachedScannedNetworks;
}
Q_INVOKABLE void ModemDetails::scanNetworks()
{
for (auto p : m_cachedScannedNetworks) {
p->deleteLater();
}
m_cachedScannedNetworks.clear();
if (m_modem->m_mm3gppDevice) {
qDebug() << QStringLiteral("Scanning for available networks...");
QDBusPendingReply<ModemManager::QVariantMapList> reply = m_modem->m_mm3gppDevice->scan();
m_isScanningNetworks = true;
Q_EMIT isScanningNetworksChanged();
m_scanNetworkWatcher = new QDBusPendingCallWatcher(reply, this);
connect(m_scanNetworkWatcher, &QDBusPendingCallWatcher::finished, this, &ModemDetails::scanNetworksFinished);
}
Q_EMIT networksChanged();
}
void ModemDetails::scanNetworksFinished(QDBusPendingCallWatcher *call)
{
QDBusPendingReply<ModemManager::QVariantMapList> reply = *call;
if (reply.isError()) {
qDebug() << QStringLiteral("Scanning failed:") << reply.error().message();
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Scanning networks failed: %1", reply.error().message()));
} else {
ModemManager::QVariantMapList list = reply.value();
for (auto &var : list) {
auto status = var[QStringLiteral("status")].value<MMModem3gppNetworkAvailability>();
if (status == MM_MODEM_3GPP_NETWORK_AVAILABILITY_CURRENT || status == MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE) {
auto network = new AvailableNetwork{this,
m_modem->m_mm3gppDevice,
status == MM_MODEM_3GPP_NETWORK_AVAILABILITY_CURRENT,
var[QStringLiteral("operator-long")].toString(),
var[QStringLiteral("operator-short")].toString(),
var[QStringLiteral("operator-code")].toString(),
var[QStringLiteral("access-technology")].value<MMModemAccessTechnology>()};
m_cachedScannedNetworks.push_back(network);
}
}
}
m_isScanningNetworks = false;
Q_EMIT networksChanged();
Q_EMIT isScanningNetworksChanged();
call->deleteLater();
}
bool ModemDetails::isScanningNetworks()
{
return m_isScanningNetworks;
}
QString ModemDetails::firmwareVersion()
{
if (!m_modem->m_nmModem) {
return QString{};
}
return m_modem->m_nmModem->firmwareVersion();
}
QString ModemDetails::interfaceName()
{
if (!m_modem->m_nmModem) {
return QString{};
}
return m_modem->m_nmModem->interfaceName();
}
QString ModemDetails::metered()
{
if (!m_modem->m_nmModem) {
return QString{};
}
switch (m_modem->m_nmModem->metered()) {
case NetworkManager::Device::MeteredStatus::UnknownStatus:
return i18n("Unknown");
case NetworkManager::Device::MeteredStatus::Yes:
return i18n("Yes");
case NetworkManager::Device::MeteredStatus::No:
return i18n("No");
case NetworkManager::Device::MeteredStatus::GuessYes:
return i18n("GuessYes");
case NetworkManager::Device::MeteredStatus::GuessNo:
return i18n("GuessNo");
}
return QString{};
}
AvailableNetwork::AvailableNetwork(QObject *parent,
ModemManager::Modem3gpp::Ptr mm3gppDevice,
bool isCurrentlyUsed,
QString operatorLong,
QString operatorShort,
QString operatorCode,
MMModemAccessTechnology accessTechnology)
: QObject{parent}
, m_isCurrentlyUsed{isCurrentlyUsed}
, m_operatorLong{operatorLong}
, m_operatorShort{operatorShort}
, m_operatorCode{operatorCode}
, m_accessTechnology{}
, m_mm3gppDevice{mm3gppDevice}
{
switch (accessTechnology) {
case MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN:
m_accessTechnology = i18n("Unknown");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_POTS:
m_accessTechnology = i18n("POTS");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_GSM:
m_accessTechnology = i18n("2G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT:
m_accessTechnology = i18n("2G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_GPRS:
m_accessTechnology = i18n("2G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_EDGE:
m_accessTechnology = i18n("2G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_UMTS:
m_accessTechnology = i18n("3G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_HSDPA:
m_accessTechnology = i18n("3G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_HSUPA:
m_accessTechnology = i18n("3G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_HSPA:
m_accessTechnology = i18n("3G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS:
m_accessTechnology = i18n("3G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_1XRTT:
m_accessTechnology = i18n("3G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_EVDO0:
m_accessTechnology = i18n("3G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_EVDOA:
m_accessTechnology = i18n("3G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_EVDOB:
m_accessTechnology = i18n("3G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_LTE:
m_accessTechnology = i18n("4G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_5GNR:
m_accessTechnology = i18n("5G");
break;
case MM_MODEM_ACCESS_TECHNOLOGY_ANY:
m_accessTechnology = i18n("Any");
break;
}
}
bool AvailableNetwork::isCurrentlyUsed()
{
return m_isCurrentlyUsed;
}
QString AvailableNetwork::operatorLong()
{
return m_operatorLong;
}
QString AvailableNetwork::operatorShort()
{
return m_operatorShort;
}
QString AvailableNetwork::operatorCode()
{
return m_operatorCode;
}
QString AvailableNetwork::accessTechnology()
{
return m_accessTechnology;
}
void AvailableNetwork::registerToNetwork()
{
if (!m_isCurrentlyUsed && m_mm3gppDevice) {
m_mm3gppDevice->registerToNetwork(m_operatorCode);
}
}

View file

@ -0,0 +1,181 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "modem.h"
#include "sim.h"
#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 Modem;
class AvailableNetwork;
class ModemDetails : public QObject
{
Q_OBJECT
// modemmanager device
Q_PROPERTY(QStringList accessTechnologies READ accessTechnologies NOTIFY accessTechnologiesChanged) // currently used tech
Q_PROPERTY(QString device READ device NOTIFY deviceChanged)
Q_PROPERTY(QString deviceIdentifier READ deviceIdentifier NOTIFY deviceIdentifierChanged)
Q_PROPERTY(QStringList drivers READ drivers NOTIFY driversChanged)
Q_PROPERTY(QString equipmentIdentifier READ equipmentIdentifier NOTIFY equipmentIdentifierChanged)
// TODO add bands
Q_PROPERTY(bool isEnabled READ isEnabled NOTIFY isEnabledChanged)
Q_PROPERTY(QString manufacturer READ manufacturer NOTIFY manufacturerChanged)
Q_PROPERTY(QString model READ model NOTIFY modelChanged)
Q_PROPERTY(QStringList ownNumbers READ ownNumbers NOTIFY ownNumbersChanged)
Q_PROPERTY(QString plugin READ plugin NOTIFY pluginChanged)
Q_PROPERTY(QString powerState READ powerState NOTIFY powerStateChanged)
Q_PROPERTY(QString revision READ revision NOTIFY revisionChanged)
Q_PROPERTY(uint signalQuality READ signalQuality NOTIFY signalQualityChanged)
Q_PROPERTY(QString simPath READ simPath NOTIFY simPathChanged)
Q_PROPERTY(QString state READ state NOTIFY stateChanged)
Q_PROPERTY(QString stateFailedReason READ stateFailedReason NOTIFY stateFailedReasonChanged)
// modemmanager 3gpp device
Q_PROPERTY(QString operatorCode READ operatorCode NOTIFY operatorCodeChanged)
Q_PROPERTY(QString operatorName READ operatorName NOTIFY operatorNameChanged)
Q_PROPERTY(QString registrationState READ registrationState NOTIFY registrationStateChanged)
Q_PROPERTY(QList<AvailableNetwork *> networks READ networks NOTIFY networksChanged)
Q_PROPERTY(bool isScanningNetworks READ isScanningNetworks NOTIFY isScanningNetworksChanged)
// networkmanager device
Q_PROPERTY(QString firmwareVersion READ firmwareVersion NOTIFY firmwareVersionChanged)
Q_PROPERTY(QString interfaceName READ interfaceName NOTIFY interfaceNameChanged)
Q_PROPERTY(QString metered READ metered NOTIFY meteredChanged)
public:
ModemDetails(QObject *parent = nullptr, Modem *modem = nullptr);
ModemDetails &operator=(ModemDetails &&other);
void swap(ModemDetails &other);
QStringList accessTechnologies();
QString device();
QString deviceIdentifier();
QStringList drivers();
QString equipmentIdentifier();
bool isEnabled();
QString manufacturer();
uint maxActiveBearers();
uint maxBearers();
QString model();
QStringList ownNumbers();
QString plugin();
QString powerState();
QString revision();
uint signalQuality();
QString simPath();
QString state();
QString stateFailedReason();
QString operatorCode();
QString operatorName();
QString registrationState();
Q_INVOKABLE void scanNetworks();
QList<AvailableNetwork *> networks();
bool isScanningNetworks();
void scanNetworksFinished(QDBusPendingCallWatcher *call);
QString firmwareVersion();
QString interfaceName();
QString metered();
Q_SIGNALS:
void accessTechnologiesChanged();
void deviceChanged();
void deviceIdentifierChanged();
void driversChanged();
void equipmentIdentifierChanged();
void isEnabledChanged();
void manufacturerChanged();
void modelChanged();
void ownNumbersChanged();
void pluginChanged();
void powerStateChanged();
void revisionChanged();
void signalQualityChanged();
void simPathChanged();
void stateChanged();
void stateFailedReasonChanged();
void supportedCapabilitiesChanged();
void operatorCodeChanged();
void operatorNameChanged();
void registrationStateChanged();
void networksChanged();
void isScanningNetworksChanged();
void firmwareVersionChanged();
void interfaceNameChanged();
void meteredChanged();
private:
void updateNMSignals();
Modem *m_modem;
QDBusPendingCallWatcher *m_scanNetworkWatcher;
bool m_isScanningNetworks;
QList<AvailableNetwork *> m_cachedScannedNetworks;
};
class AvailableNetwork : public QObject
{
Q_OBJECT
Q_PROPERTY(bool isCurrentlyUsed READ isCurrentlyUsed NOTIFY isCurrentlyUsedChanged)
Q_PROPERTY(QString operatorLong READ operatorLong NOTIFY operatorLongChanged)
Q_PROPERTY(QString operatorShort READ operatorShort NOTIFY operatorShortChanged)
Q_PROPERTY(QString operatorCode READ operatorCode NOTIFY operatorCodeChanged)
Q_PROPERTY(QString accessTechnology READ accessTechnology NOTIFY accessTechnologyChanged)
public:
AvailableNetwork(QObject *parent = nullptr,
ModemManager::Modem3gpp::Ptr mm3gppDevice = nullptr,
bool isCurrentlyUsed = false,
QString operatorLong = "",
QString operatorShort = "",
QString operatorCode = "",
MMModemAccessTechnology accessTechnology = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
bool isCurrentlyUsed();
QString operatorLong();
QString operatorShort();
QString operatorCode();
QString accessTechnology();
Q_INVOKABLE void registerToNetwork();
Q_SIGNALS:
void isCurrentlyUsedChanged();
void operatorLongChanged();
void operatorShortChanged();
void operatorCodeChanged();
void accessTechnologyChanged();
private:
bool m_isCurrentlyUsed;
QString m_operatorLong;
QString m_operatorShort;
QString m_operatorCode;
QString m_accessTechnology;
ModemManager::Modem3gpp::Ptr m_mm3gppDevice; // this may be a nullptr if no sim is inserted
};

View file

@ -0,0 +1,95 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
import QtQuick 2.12
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.12 as Controls
import org.kde.kirigami 2.12 as Kirigami
import org.kde.kcm 1.2
import cellularnetworkkcm 1.0
Kirigami.ScrollablePage {
id: root
title: i18n("Available Networks")
property Modem modem
property Sim sim
ListView {
id: listView
header: ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
MessagesList {
visible: count != 0
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
model: kcm.messages
}
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
visible: !modem.details.isScanningNetworks && listView.count == 0
icon.name: "network-mobile-100"
text: i18n("Current operator: %1", modem.details.operatorName ? modem.details.operatorName : i18n("none"))
helpfulAction: Kirigami.Action {
icon.name: "view-refresh"
text: i18n("Scan For Networks")
enabled: !modem.details.isScanningNetworks
onTriggered: modem.details.scanNetworks()
}
}
Controls.BusyIndicator {
anchors.centerIn: parent
visible: modem.details.isScanningNetworks
implicitWidth: Kirigami.Units.iconSizes.large
implicitHeight: implicitWidth
}
model: modem.details.networks
delegate: Kirigami.SwipeListItem {
onClicked: {
if (!modelData.isCurrentlyUsed) {
modelData.registerToNetwork();
modem.details.scanNetworks();
}
}
contentItem: RowLayout {
Layout.fillWidth: true
ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
level: 3
text: modelData.operatorLong + " | " + modelData.operatorShort + "(" + modelData.operatorCode + ")"
}
Controls.Label {
text: modelData.accessTechnology
}
}
Controls.RadioButton {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
checked: modelData.isCurrentlyUsed
onClicked: {
if (!modelData.isCurrentlyUsed) {
modelData.registerToNetwork();
modem.details.scanNetworks();
}
checked = modelData.isCurrentlyUsed;
}
}
}
}
}
}

View file

@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2020-2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: GPL-3.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 cellularnetworkkcm 1.0
Kirigami.Dialog {
id: dialog
title: i18n("Edit APN")
clip: true
property Modem modem
property ProfileSettings profile
property int pageWidth
standardButtons: Controls.Dialog.Ok | Controls.Dialog.Cancel
onAccepted: {
if (profile == null) { // create new profile
modem.addProfile(profileName.text, profileApn.text, profileUsername.text, profilePassword.text, profileNetworkType.value);
} else { // edit existing profile
modem.updateProfile(profile.connectionUni, profileName.text, profileApn.text, profileUsername.text, profilePassword.text, profileNetworkType.value);
}
}
preferredWidth: pageWidth - Kirigami.Units.gridUnit * 4
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)
}
}
}
}
}
}

View file

@ -0,0 +1,51 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
import QtQuick 2.12
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.12 as Controls
import org.kde.kirigami 2.12 as Kirigami
import cellularnetworkkcm 1.0
ColumnLayout {
id: root
property var model
property alias count: repeater.count
spacing: 0
visible: count > 0
Repeater {
id: repeater
model: root.model
delegate: Kirigami.InlineMessage {
Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
visible: true
text: modelData.message
type: {
switch (modelData.type) {
case InlineMessage.Information: return Kirigami.MessageType.Information;
case InlineMessage.Positive: return Kirigami.MessageType.Positive;
case InlineMessage.Warning: return Kirigami.MessageType.Warning;
case InlineMessage.Error: return Kirigami.MessageType.Error;
}
return Kirigami.MessageType.Error;
}
actions: [
Kirigami.Action {
icon.name: "dialog-close"
onTriggered: kcm.removeMessage(model.index)
}
]
}
}
}

View file

@ -0,0 +1,309 @@
// SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: GPL-3.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.kcm 1.2
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import cellularnetworkkcm 1.0
Kirigami.ScrollablePage {
id: modemPage
title: i18n("Modem %1", modem.displayId)
leftPadding: 0
rightPadding: 0
topPadding: Kirigami.Units.gridUnit
bottomPadding: Kirigami.Units.gridUnit
property Modem modem
property bool showExtra: false
ColumnLayout {
MessagesList {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.smallSpacing
visible: count != 0
model: kcm.messages
}
MobileForm.FormCard {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("Modem Control")
}
MobileForm.AbstractFormDelegate {
id: modemRestartButton
Layout.fillWidth: true
contentItem: RowLayout {
Controls.Label {
Layout.fillWidth: true
text: i18n("Modem Restart")
}
Controls.Button {
text: i18n("Force Modem Restart")
onClicked: modem.reset()
}
}
}
}
}
MobileForm.FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("Modem Details")
}
MobileForm.AbstractFormDelegate {
id: accessTechnologiesText
Layout.fillWidth: true
background: Item {}
contentItem: ColumnLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Controls.Label {
Layout.fillWidth: true
text: i18n("Access Technologies")
elide: Text.ElideRight
}
Repeater {
model: modem.details.accessTechnologies
Controls.Label {
Layout.fillWidth: true
text: modelData
color: Kirigami.Theme.disabledTextColor
font: Kirigami.Theme.smallFont
elide: Text.ElideRight
}
}
}
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: imeiText
text: i18n("IMEI")
description: modem.details.equipmentIdentifier
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: enabledText
text: i18n("Enabled")
description: modem.details.isEnabled
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: manufacturerText
text: i18n("Manufacturer")
description: modem.details.manufacturer
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: modelText
text: i18n("Model")
description: modem.details.model
}
MobileForm.FormDelegateSeparator {}
MobileForm.AbstractFormDelegate {
id: ownedNumbersText
Layout.fillWidth: true
background: Item {}
contentItem: ColumnLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Controls.Label {
Layout.fillWidth: true
text: i18n("Owned Numbers:")
elide: Text.ElideRight
}
Repeater {
model: modem.details.ownNumbers
Controls.Label {
Layout.fillWidth: true
text: modelData
color: Kirigami.Theme.disabledTextColor
font: Kirigami.Theme.smallFont
elide: Text.ElideRight
}
}
}
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: revisionText
text: i18n("Revision")
description: modem.details.revision
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: signalQualityText
text: i18n("Signal Quality")
description: modem.details.signalQuality
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: stateText
text: i18n("State")
description: modem.details.state
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: failureReasonText
text: i18n("Failure Reason")
description: modem.details.stateFailedReason
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: registrationStateText
text: i18n("Registration State")
description: modem.details.registrationState
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: roamingText
text: i18n("Roaming")
description: modem.isRoaming ? i18n("Yes") : i18n("No")
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: firmwareVersionText
text: i18n("Firmware Version")
description: modem.details.firmwareVersion
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: interfaceNameText
text: i18n("Interface Name")
description: modem.details.interfaceName
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: meteredText
text: i18n("Metered")
description: modem.details.metered
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: activeNMConnectionText
text: i18n("Active NetworkManager Connection")
description: modem.activeConnectionUni
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: deviceText
text: i18n("Device")
description: modem.details.device
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: deviceIdText
text: i18n("Device ID")
description: modem.details.deviceIdentifier
}
MobileForm.FormDelegateSeparator {}
MobileForm.AbstractFormDelegate {
id: driversText
Layout.fillWidth: true
background: Item {}
contentItem: ColumnLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Controls.Label {
Layout.fillWidth: true
text: i18n("Drivers:")
elide: Text.ElideRight
}
Repeater {
model: modem.details.drivers
Controls.Label {
Layout.fillWidth: true
text: modelData
color: Kirigami.Theme.disabledTextColor
font: Kirigami.Theme.smallFont
elide: Text.ElideRight
}
}
}
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: pluginText
text: i18n("Plugin")
description: modem.details.plugin
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: powerStateText
text: i18n("Power State")
description: modem.details.powerState
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: simPathText
text: i18n("SIM Path")
description: modem.details.simPath
}
}
}
}
}

View file

@ -0,0 +1,103 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
import QtQuick 2.15
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.15 as Controls
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.12 as Kirigami
Controls.Dialog {
id: dialog
anchors.centerIn: Controls.Overlay.overlay
modal: true
padding: Kirigami.Units.smallSpacing
closePolicy: Controls.Popup.CloseOnEscape | Controls.Popup.CloseOnReleaseOutside
property int translateY: (1 - opacity) * Kirigami.Units.gridUnit * 2
NumberAnimation on opacity {
from: 0; to: 1;
duration: Kirigami.Units.veryShortDuration
easing.type: Easing.InOutQuad
running: true
}
contentItem.transform: Translate { y: dialog.translateY }
footer.transform: Translate { y: dialog.translateY }
header: Item {
transform: Translate { y: dialog.translateY }
implicitHeight: heading.implicitHeight + Kirigami.Units.largeSpacing * 2
Kirigami.Heading {
id: heading
level: 2
text: dialog.title
elide: Text.ElideRight
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Kirigami.Units.largeSpacing
anchors.verticalCenter: parent.verticalCenter
// use tooltip for long text that is elided
Controls.ToolTip.visible: truncated && titleHoverHandler.hovered
Controls.ToolTip.text: dialog.title
HoverHandler {
id: titleHoverHandler
}
}
}
background: Item {
transform: Translate { y: dialog.translateY }
RectangularGlow {
anchors.fill: rect
anchors.topMargin: 1
cornerRadius: rect.radius * 2
glowRadius: 2
spread: 0.2
color: Qt.rgba(0, 0, 0, 0.3)
}
Rectangle {
id: rect
anchors.fill: parent
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing
Kirigami.Separator {
id: topSeparator
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: dialog.header.implicitHeight
}
Kirigami.Separator {
id: bottomSeparator
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: dialog.footer.implicitHeight
}
Rectangle {
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
color: Kirigami.Theme.backgroundColor
anchors.left: parent.left
anchors.right: parent.right
anchors.top: topSeparator.bottom
anchors.bottom: bottomSeparator.top
}
}
}
}

View file

@ -0,0 +1,183 @@
/*
SPDX-FileCopyrightText: 2020-2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
import QtQuick 2.12
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.12 as Controls
import org.kde.kirigami 2.12 as Kirigami
import cellularnetworkkcm 1.0
Kirigami.ScrollablePage {
id: apnlist
title: i18n("APNs")
property Modem modem
ListView {
id: profileListView
model: modem.profiles
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Kirigami.Units.largeSpacing
visible: profileListView.count === 0
text: i18n("No APNs configured")
icon.name: "globe"
helpfulAction: Kirigami.Action {
iconName: "list-add"
text: i18n("Add Connection")
onTriggered: {
profileDialog.profile = null;
profileDialog.open();
}
}
}
EditProfileDialog {
id: profileDialog
modem: apnlist.modem
profile: null
pageWidth: apnlist.width
}
header: ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
MessagesList {
id: messagesList
visible: count != 0
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
model: kcm.messages
}
Kirigami.InlineMessage {
id: cannotFindWarning
Layout.margins: visible ? Kirigami.Units.largeSpacing : 0
Layout.topMargin: visible && !messagesList.visible ? Kirigami.Units.largeSpacing : 0
Layout.fillWidth: true
visible: false
type: Kirigami.MessageType.Warning
showCloseButton: true
text: qsTr("Unable to autodetect connection settings for your carrier. Please find your carrier's APN settings by either contacting support or searching online.")
Connections {
target: modem
function onCouldNotAutodetectSettings() {
cannotFindWarning.visible = true;
}
}
}
Kirigami.SwipeListItem {
Layout.fillWidth: true
visible: profileListView.count !== 0
onClicked: {
profileDialog.profile = null;
profileDialog.open();
}
contentItem: Row {
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
anchors.verticalCenter: parent.verticalCenter
source: "list-add"
height: Kirigami.Units.gridUnit * 1.5
width: height
}
Kirigami.Heading {
level: 3
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignLeft
text: i18n("Add APN")
}
}
}
Kirigami.SwipeListItem {
Layout.fillWidth: true
onClicked: {
modem.addDetectedProfileSettings();
}
contentItem: Row {
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
anchors.verticalCenter: parent.verticalCenter
source: "list-add"
height: Kirigami.Units.gridUnit * 1.5
width: height
}
Kirigami.Heading {
level: 3
anchors.verticalCenter: parent.verticalCenter
Layout.alignment: Qt.AlignLeft
text: i18n("Autodetect APN")
}
}
}
}
delegate: Kirigami.SwipeListItem {
onClicked: modem.activateProfile(modelData.connectionUni)
actions: [
Kirigami.Action {
iconName: "entry-edit"
text: i18n("Edit")
onTriggered: {
profileDialog.profile = modelData;
profileDialog.open();
}
},
Kirigami.Action {
iconName: "delete"
text: i18n("Delete")
onTriggered: modem.removeProfile(modelData.connectionUni)
}
]
contentItem: RowLayout {
Layout.fillWidth: true
Controls.RadioButton {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
checked: modem.activeConnectionUni == modelData.connectionUni
onClicked: {
if (!checked) {
modem.activateProfile(modelData.connectionUni);
}
// reapply binding
checked = Qt.binding(() => { return modem.activeConnectionUni == modelData.connectionUni });
}
}
ColumnLayout {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
Layout.fillWidth: true
level: 3
text: modelData.name
}
Controls.Label {
Layout.fillWidth: true
text: modelData.apn
}
}
}
}
}
}

View file

@ -0,0 +1,249 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.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.kcm 1.2
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import cellularnetworkkcm 1.0
Kirigami.ScrollablePage {
id: root
title: i18n("SIM Lock")
leftPadding: 0
rightPadding: 0
topPadding: Kirigami.Units.gridUnit
bottomPadding: Kirigami.Units.gridUnit
property Sim sim
ColumnLayout {
spacing: 0
width: root.width
MessagesList {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
model: kcm.messages
}
ColumnLayout {
id: unlockSimPlaceholder
visible: sim.locked
Layout.fillWidth: true
// HACK: prevent infinite binding loop?
Component.onCompleted: unlockSimPlaceholder.Layout.preferredHeight = root.height
Kirigami.PlaceholderMessage {
Layout.margins: Kirigami.Units.gridUnit * 2
Layout.fillWidth: true
Layout.fillHeight: true
text: i18n("SIM is locked")
explanation: i18n("In order to use this SIM, you must first unlock it.")
icon.name: "lock"
helpfulAction: Kirigami.Action {
icon.name: "unlock"
text: "Unlock SIM"
onTriggered: unlockPinDialog.open() // TODO replace custom unlock pin dialog with just opening the system unlock PIN dialog
}
}
}
ColumnLayout {
id: notLockedSimPlaceholder
visible: !sim.pinEnabled && !unlockSimPlaceholder.visible
Layout.fillWidth: true
// HACK: prevent infinite binding loop?
Component.onCompleted: notLockedSimPlaceholder.Layout.preferredHeight = root.height
Kirigami.PlaceholderMessage {
Layout.margins: Kirigami.Units.gridUnit * 2
Layout.fillWidth: true
Layout.fillHeight: true
text: i18n("SIM is not locked")
explanation: i18n("You can lock your SIM to require a set PIN code for phone calls and mobile data.")
icon.name: "unlock"
helpfulAction: Kirigami.Action {
icon.name: "lock"
text: i18n("Lock SIM")
onTriggered: createPinDialog.open()
}
}
}
MobileForm.FormCard {
visible: !notLockedSimPlaceholder.visible && !unlockSimPlaceholder.visible
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormButtonDelegate {
id: disableSimLockButton
text: i18n("Disable SIM Lock")
description: i18n("Disable the SIM lock feature and remove the passcode on the SIM.")
onClicked: removePinDialog.open();
}
Kirigami.Separator {
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
opacity: (!disableSimLockButton.controlHovered && !changePinButton.controlHovered) ? 0.5 : 0
}
MobileForm.FormButtonDelegate {
id: changePinButton
text: i18n("Change PIN")
description: i18n("Change the passcode set on the SIM.")
onClicked: changePinDialog.open()
}
}
}
// dialogs
RegExpValidator {
id: pinValidator
regExp: /[0-9]+/
}
Kirigami.Dialog {
id: unlockPinDialog
title: i18n("Unlock SIM")
standardButtons: Controls.Dialog.Ok | Controls.Dialog.Cancel
padding: Kirigami.Units.largeSpacing
onAccepted: sim.sendPin(unlockPinCurPin.text)
ColumnLayout {
Controls.Label {
text: i18n("Attempts left: %1", sim.unlockRetriesLeft)
}
Kirigami.PasswordField {
id: unlockPinCurPin
placeholderText: i18n("Enter PIN")
validator: pinValidator
}
}
}
Kirigami.Dialog {
id: changePinDialog
title: i18n("Change SIM PIN")
standardButtons: isValid ? Controls.Dialog.Ok | Controls.Dialog.Cancel : Controls.Dialog.Cancel
padding: Kirigami.Units.largeSpacing
property bool isValid: changePinNewPin.text == changePinConfirmPin.text &&
changePinNewPin.text.length >= 4 && changePinNewPin.text.length <= 8 // SIM PINs are between 4-8 digits
onAccepted: {
if (isValid) {
sim.changePin(changePinCurPin.text, changePinNewPin.text);
}
}
ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.InlineMessage {
id: changePinMatch
Layout.fillWidth: true
visible: changePinNewPin.text != changePinConfirmPin.text
text: i18n("PINs don't match!")
type: Kirigami.MessageType.Error
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: !changePinMatch.visible && changePinNewPin.text.length != 0 && (changePinNewPin.text.length < 4 || changePinNewPin.text.length > 8)
text: i18n("PINs must be between 4 and 8 digits!")
type: Kirigami.MessageType.Error
}
Kirigami.PasswordField {
id: changePinCurPin
placeholderText: i18n("Current PIN")
validator: pinValidator
}
Kirigami.PasswordField {
id: changePinNewPin
placeholderText: i18n("New PIN")
validator: pinValidator
}
Kirigami.PasswordField {
id: changePinConfirmPin
placeholderText: i18n("Confirm PIN")
validator: pinValidator
}
}
}
Kirigami.Dialog {
id: removePinDialog
title: i18n("Remove SIM PIN")
standardButtons: Controls.Dialog.Ok | Controls.Dialog.Cancel
padding: Kirigami.Units.largeSpacing
onAccepted: sim.togglePinEnabled(removePinCurPin.text);
ColumnLayout {
Kirigami.PasswordField {
id: removePinCurPin
placeholderText: i18n("Current PIN")
validator: pinValidator
}
}
}
Kirigami.Dialog {
id: createPinDialog
title: i18n("Add SIM PIN")
standardButtons: isValid ? Controls.Dialog.Ok | Controls.Dialog.Cancel : Controls.Dialog.Cancel
padding: Kirigami.Units.largeSpacing
property bool isValid: createPinNewPin.text == createPinConfirmPin.text &&
createPinNewPin.text.length >= 4 && createPinNewPin.text.length <= 8 // SIM PINs are between 4-8 digits
onAccepted: {
if (isValid) {
sim.togglePinEnabled(createPinNewPin.text);
}
}
ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.InlineMessage {
id: createPinMatch
Layout.fillWidth: true
visible: createPinNewPin.text != createPinConfirmPin.text
text: i18n("PINs don't match!")
type: Kirigami.MessageType.Error
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: !createPinMatch.visible && createPinNewPin.text.length != 0 && (createPinNewPin.text.length < 4 || createPinNewPin.text.length > 8)
text: i18n("PINs must be between 4 and 8 digits!")
type: Kirigami.MessageType.Error
}
Kirigami.PasswordField {
id: createPinNewPin
placeholderText: i18n("New PIN")
validator: pinValidator
}
Kirigami.PasswordField {
id: createPinConfirmPin
placeholderText: i18n("Confirm PIN")
validator: pinValidator
}
}
}
}
}

View file

@ -0,0 +1,230 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.12 as Controls
import org.kde.kirigami 2.12 as Kirigami
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
import org.kde.kcm 1.2
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import cellularnetworkkcm 1.0
Kirigami.ScrollablePage {
id: simPage
title: i18n("SIM") + " " + displayId
property Sim sim: null
property string displayId: sim ? sim.displayId : ""
property bool simEnabled: sim ? sim.enabled : false
property bool isRoaming: sim ? (sim.modem ? sim.modem.isRoaming : false) : false
property bool simLocked: sim ? sim.locked : false
property string simImsi: sim ? sim.imsi : ""
property string simEid: sim ? sim.eid : ""
property string operatorCode: sim ? (sim.modem ? sim.modem.details.operatorCode : "") : ""
property string operatorName: sim ? (sim.modem ? sim.modem.details.operatorName : "") : ""
property string simOperatorIdentifier: sim ? sim.operatorIdentifier : ""
property string simOperatorName: sim ? sim.operatorName : ""
property string simIdentifier: sim ? sim.simIdentifier : ""
property var simEmergencyNumbers: sim ? sim.emergencyNumbers : []
leftPadding: 0
rightPadding: 0
topPadding: Kirigami.Units.gridUnit
bottomPadding: Kirigami.Units.gridUnit
PlasmaNM.EnabledConnections {
id: enabledConnections
}
ColumnLayout {
spacing: 0
width: simPage.width
Kirigami.InlineMessage {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Layout.bottomMargin: visible && !messagesList.visible ? Kirigami.Units.largeSpacing : 0
visible: !simEnabled
type: Kirigami.MessageType.Error
text: qsTr("This SIM slot is empty, a SIM card needs to be inserted in order for it to be used.")
}
MessagesList {
id: messagesList
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
model: kcm.messages
}
MobileForm.FormCard {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormSwitchDelegate {
id: dataRoamingCheckBox
text: i18n("Data Roaming")
description: i18n("Allow your device to use networks other than your carrier.")
enabled: simEnabled
checked: isRoaming
onCheckedChanged: sim.modem.isRoaming = checked
}
MobileForm.FormDelegateSeparator { above: dataRoamingCheckBox; below: apnButton }
MobileForm.FormButtonDelegate {
id: apnButton
icon.name: "globe"
text: i18n("Modify APNs")
description: i18n("Configure access point names for your carrier.")
enabled: simEnabled && enabledConnections.wwanEnabled
onClicked: kcm.push("ProfileList.qml", { "modem": sim.modem });
}
MobileForm.FormDelegateSeparator { above: apnButton; below: networksButton }
MobileForm.FormButtonDelegate {
id: networksButton
icon.name: "network-mobile-available"
text: i18n("Networks")
description: i18n("Select a network operator.")
enabled: simEnabled
onClicked: kcm.push("AvailableNetworks.qml", { "modem": sim.modem, "sim": sim });
}
MobileForm.FormDelegateSeparator { above: networksButton; below: simLockButton }
MobileForm.FormButtonDelegate {
id: simLockButton
icon.name: "unlock"
text: i18n("SIM Lock")
description: i18n("Modify SIM lock settings.")
enabled: simEnabled
onClicked: kcm.push("SimLockPage.qml", { "sim": sim });
}
MobileForm.FormDelegateSeparator { above: simLockButton; below: modemDetailsButton }
MobileForm.FormButtonDelegate {
id: modemDetailsButton
icon.name: "network-modem"
text: i18n("Modem Details")
description: i18n("View the details of the modem this SIM is connected to.")
onClicked: kcm.push("ModemPage.qml", { "modem": sim.modem })
}
}
}
MobileForm.FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("SIM Details")
}
MobileForm.FormTextDelegate {
id: lockedText
text: i18n("Locked")
description: simLocked ? i18n("Yes") : i18n("No")
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: imsiText
text: i18n("IMSI")
description: simImsi
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: eidText
text: i18n("EID")
description: simEid
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: opCodeModemText
text: i18n("Operator Code (modem)")
description: operatorCode
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: opNameModemText
text: i18n("Operator Name (modem)")
description: operatorName
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: opCodeSimText
text: i18n("Operator Code (provided by SIM)")
description: simOperatorIdentifier
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: opNameSimText
text: i18n("Operator Name (provided by SIM)")
description: simOperatorName
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: simIdText
text: i18n("SIM ID")
description: simIdentifier
}
MobileForm.FormDelegateSeparator {}
MobileForm.AbstractFormDelegate {
id: emergencyNumbersText
Layout.fillWidth: true
background: Item {}
contentItem: ColumnLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Controls.Label {
Layout.fillWidth: true
text: i18n("Emergency Numbers")
elide: Text.ElideRight
}
Repeater {
model: simEmergencyNumbers
Controls.Label {
Layout.fillWidth: true
text: modelData
color: Kirigami.Theme.disabledTextColor
font: Kirigami.Theme.smallFont
elide: Text.ElideRight
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,167 @@
// SPDX-FileCopyrightText: 2018 Martin Kacej <m.kacej@atlas.sk>
// SPDX-FileCopyrightText: 2020 Dimitris Kardarakos <dimkard@posteo.net>
// SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.12 as Controls
import org.kde.plasma.networkmanagement 0.2 as PlasmaNM
import org.kde.kirigami 2.19 as Kirigami
import org.kde.kcm 1.3 as KCM
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import cellularnetworkkcm 1.0
KCM.SimpleKCM {
id: root
objectName: "mobileDataMain"
leftPadding: 0
rightPadding: 0
topPadding: Kirigami.Units.gridUnit
bottomPadding: Kirigami.Units.gridUnit
Kirigami.PlaceholderMessage {
id: noModem
anchors.centerIn: parent
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Kirigami.Units.largeSpacing
visible: !enabledConnections.wwanHwEnabled || !availableDevices.modemDeviceAvailable || !kcm.modemFound
icon.name: "auth-sim-missing"
text: i18n("Modem not available")
}
ColumnLayout {
spacing: 0
width: root.width
visible: !noModem.visible
PlasmaNM.Handler {
id: nmHandler
}
PlasmaNM.AvailableDevices {
id: availableDevices
}
PlasmaNM.EnabledConnections {
id: enabledConnections
}
SimPage {
id: simPage
visible: false
}
MessagesList {
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
model: kcm.messages
}
MobileForm.FormCard {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormSwitchDelegate {
id: mobileDataSwitch
text: i18n("Mobile data")
description: {
if (!kcm.modemFound) {
return "";
} else if (!kcm.selectedModem.hasSim) {
return i18n("No SIM is inserted.")
} if (!kcm.selectedModem.mobileDataSupported) {
return i18n("Mobile data is not available with this modem.")
} else if (kcm.selectedModem.needsAPNAdded) {
return i18n("An APN needs to be configured to have mobile data.");
} else {
return i18n("Whether mobile data is enabled.");
}
}
property bool manuallySet: false
property bool shouldBeChecked: kcm.selectedModem && kcm.selectedModem.mobileDataEnabled
onShouldBeCheckedChanged: {
checked = shouldBeChecked;
}
enabled: kcm.selectedModem.mobileDataSupported && !kcm.selectedModem.needsAPNAdded
checked: shouldBeChecked
onCheckedChanged: {
// prevent binding loops
if (manuallySet) {
manuallySet = false;
return;
}
if (kcm.selectedModem.mobileDataEnabled != checked) {
manuallySet = true;
kcm.selectedModem.mobileDataEnabled = checked;
}
}
}
MobileForm.FormDelegateSeparator { above: mobileDataSwitch; below: dataUsageButton }
MobileForm.FormButtonDelegate {
id: dataUsageButton
text: i18n("Data Usage")
description: i18n("View data usage.")
icon.name: "office-chart-bar"
enabled: false
}
}
}
MobileForm.FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: kcm.sims.count == 1 ? i18n("SIM") : i18n("SIMs")
}
Repeater {
id: repeater
model: kcm.sims
delegate: ColumnLayout {
Layout.fillWidth: true
Kirigami.Separator {
visible: model.index !== 0
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
opacity: (!(model.index && repeater.itemAt(model.index - 1).controlHovered) && !simDelegate.controlHovered) ? 0.5 : 0
}
MobileForm.FormButtonDelegate {
id: simDelegate
text: i18n("SIM %1", modelData.displayId)
description: i18n("View SIM %1 details.", modelData.displayId)
icon.name: "auth-sim-symbolic"
onClicked: {
simPage.sim = modelData;
simPage.visible = true;
kcm.push(simPage);
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,141 @@
// SPDX-FileCopyrightText: 2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "profilesettings.h"
#include <KLocalizedString>
ProfileSettings::ProfileSettings(QObject *parent,
QString name,
QString apn,
QString user,
QString password,
NetworkManager::GsmSetting::NetworkType networkType,
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())
{
setParent(parent);
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()
{
return m_name;
}
QString ProfileSettings::apn()
{
return m_apn;
}
void ProfileSettings::setApn(QString apn)
{
if (apn != m_apn) {
m_apn = apn;
Q_EMIT apnChanged();
}
}
QString ProfileSettings::user()
{
return m_user;
}
void ProfileSettings::setUser(QString user)
{
if (user != m_user) {
m_user = user;
Q_EMIT userChanged();
}
}
QString ProfileSettings::password()
{
return m_password;
}
void ProfileSettings::setPassword(QString password)
{
if (password != m_password) {
m_password = password;
Q_EMIT passwordChanged();
}
}
QString ProfileSettings::networkType()
{
return m_networkType;
}
void ProfileSettings::setNetworkType(QString networkType)
{
if (networkType != m_networkType) {
m_networkType = networkType;
Q_EMIT networkTypeChanged();
}
}
QString ProfileSettings::connectionUni()
{
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;
}

View file

@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "cellularnetworksettings.h"
#include "modemdetails.h"
#include "sim.h"
#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,
QString name,
QString apn,
QString user,
QString password,
NetworkManager::GsmSetting::NetworkType networkType,
QString connectionUni);
ProfileSettings(QObject *parent, NetworkManager::Setting::Ptr settings, NetworkManager::Connection::Ptr connection);
QString name();
QString apn();
void setApn(QString apn);
QString user();
void setUser(QString user);
QString password();
void setPassword(QString password);
QString networkType();
void setNetworkType(QString ipType);
QString connectionUni();
// 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;
};

View file

@ -0,0 +1,194 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "sim.h"
#include <KLocalizedString>
Sim::Sim(QObject *parent, Modem *modem, ModemManager::Sim::Ptr mmSim, ModemManager::Modem::Ptr mmModem, ModemManager::Modem3gpp::Ptr mmModem3gpp)
: QObject{parent}
, m_modem{modem}
, m_mmSim{mmSim}
, m_mmModem{mmModem}
, m_mmModem3gpp{mmModem3gpp}
{
connect(m_mmSim.data(), &ModemManager::Sim::imsiChanged, this, [this]() -> void {
Q_EMIT imsiChanged();
});
connect(m_mmSim.data(), &ModemManager::Sim::operatorIdentifierChanged, this, [this]() -> void {
Q_EMIT operatorIdentifierChanged();
});
connect(m_mmSim.data(), &ModemManager::Sim::operatorNameChanged, this, [this]() -> void {
Q_EMIT operatorNameChanged();
});
connect(m_mmSim.data(), &ModemManager::Sim::simIdentifierChanged, this, [this]() -> void {
Q_EMIT simIdentifierChanged();
});
connect(m_mmModem.data(), &ModemManager::Modem::unlockRequiredChanged, this, [this]() -> void {
Q_EMIT lockedChanged();
Q_EMIT lockedReasonChanged();
});
if (m_mmModem3gpp) {
connect(m_mmModem3gpp.data(), &ModemManager::Modem3gpp::enabledFacilityLocksChanged, this, [this]() -> void {
Q_EMIT pinEnabledChanged();
});
}
}
bool Sim::enabled()
{
return uni() != QStringLiteral("/");
}
bool Sim::pinEnabled()
{
return m_mmModem3gpp && (m_mmModem3gpp->enabledFacilityLocks() & MM_MODEM_3GPP_FACILITY_SIM);
}
int Sim::unlockRetriesLeft()
{
return m_mmModem->unlockRetries()[MM_MODEM_LOCK_SIM_PIN];
}
bool Sim::locked()
{
return m_mmModem->unlockRequired() == MM_MODEM_LOCK_SIM_PIN;
}
QString Sim::lockedReason()
{
switch (m_mmModem->unlockRequired()) {
case MM_MODEM_LOCK_UNKNOWN:
return i18n("Lock reason unknown.");
case MM_MODEM_LOCK_NONE:
return i18n("Modem is unlocked.");
case MM_MODEM_LOCK_SIM_PIN:
return i18n("SIM requires the PIN code.");
case MM_MODEM_LOCK_SIM_PIN2:
return i18n("SIM requires the PIN2 code.");
case MM_MODEM_LOCK_SIM_PUK:
return i18n("SIM requires the PUK code.");
case MM_MODEM_LOCK_SIM_PUK2:
return i18n("SIM requires the PUK2 code.");
case MM_MODEM_LOCK_PH_SP_PIN:
return i18n("Modem requires the service provider PIN code.");
case MM_MODEM_LOCK_PH_SP_PUK:
return i18n("Modem requires the service provider PUK code.");
case MM_MODEM_LOCK_PH_NET_PIN:
return i18n("Modem requires the network PIN code.");
case MM_MODEM_LOCK_PH_NET_PUK:
return i18n("Modem requires the network PUK code.");
case MM_MODEM_LOCK_PH_SIM_PIN:
return i18n("Modem requires the PIN code.");
case MM_MODEM_LOCK_PH_CORP_PIN:
return i18n("Modem requires the corporate PIN code.");
case MM_MODEM_LOCK_PH_CORP_PUK:
return i18n("Modem requires the corporate PUK code.");
case MM_MODEM_LOCK_PH_FSIM_PIN:
return i18n("Modem requires the PH-FSIM PIN code.");
case MM_MODEM_LOCK_PH_FSIM_PUK:
return i18n("Modem requires the PH-FSIM PUK code.");
case MM_MODEM_LOCK_PH_NETSUB_PIN:
return i18n("Modem requires the network subset PIN code.");
case MM_MODEM_LOCK_PH_NETSUB_PUK:
return i18n("Modem requires the network subset PUK code.");
}
return QString{};
}
QString Sim::imsi()
{
return m_mmSim->imsi();
}
QString Sim::eid()
{
return ""; // TODO add in mm-qt
}
QString Sim::operatorIdentifier()
{
return m_mmSim->operatorIdentifier();
}
QString Sim::operatorName()
{
return m_mmSim->operatorName();
}
QString Sim::simIdentifier()
{
return m_mmSim->simIdentifier();
}
QStringList Sim::emergencyNumbers()
{
return {}; // TODO add in mm-qt
}
QString Sim::uni()
{
return m_mmSim->uni();
}
QString Sim::displayId()
{
// in the form /org/freedesktop/ModemManager1/Sim/0
QStringList uniSplit = uni().split(QStringLiteral("/"));
return (uniSplit.count() == 0 || uni() == "/") ? i18n("(empty)") : QString(uniSplit[uniSplit.size() - 1]);
}
Modem *Sim::modem()
{
return m_modem;
}
void Sim::togglePinEnabled(const QString &pin)
{
bool isPinEnabled = pinEnabled();
QDBusPendingReply reply = m_mmSim->enablePin(pin, !isPinEnabled);
reply.waitForFinished();
if (reply.isError()) {
qWarning() << QStringLiteral("Error toggling SIM lock to") << isPinEnabled << QStringLiteral(":") << reply.error().message();
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error toggling SIM lock: %1", reply.error().message()));
}
}
void Sim::changePin(const QString &oldPin, const QString &newPin)
{
QDBusPendingReply reply = m_mmSim->changePin(oldPin, newPin);
reply.waitForFinished();
if (reply.isError()) {
qWarning() << QStringLiteral("Error changing the PIN:") << reply.error().message();
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error changing the PIN: %1", reply.error().message()));
}
}
void Sim::sendPin(const QString &pin)
{
if (m_mmModem->unlockRequired() != MM_MODEM_LOCK_NONE) {
QDBusPendingReply reply = m_mmSim->sendPin(pin);
reply.waitForFinished();
if (reply.isError()) {
qWarning() << QStringLiteral("Error sending the PIN:") << reply.error().message();
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error sending the PIN: %1", reply.error().message()));
}
}
}
void Sim::sendPuk(const QString &pin, const QString &puk)
{
if (m_mmModem->unlockRequired() != MM_MODEM_LOCK_NONE) {
QDBusPendingReply reply = m_mmSim->sendPuk(pin, puk);
reply.waitForFinished();
if (reply.isError()) {
qWarning() << QStringLiteral("Error sending the PUK:") << reply.error().message();
CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error sending the PUK: %1", reply.error().message()));
}
}
}

View file

@ -0,0 +1,94 @@
/*
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "modem.h"
#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 Modem;
class Sim : public QObject
{
Q_OBJECT
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged)
Q_PROPERTY(bool pinEnabled READ pinEnabled NOTIFY pinEnabledChanged) // if there is a PIN set on the SIM
Q_PROPERTY(int unlockRetriesLeft READ unlockRetriesLeft NOTIFY unlockRetriesLeftChanged)
Q_PROPERTY(bool locked READ locked NOTIFY lockedChanged) // if the SIM is currently locked (requires entering PIN)
Q_PROPERTY(QString lockedReason READ lockedReason NOTIFY lockedReasonChanged)
Q_PROPERTY(QString imsi READ imsi NOTIFY imsiChanged)
Q_PROPERTY(QString eid READ eid NOTIFY eidChanged) // TODO (not in mm-qt)
Q_PROPERTY(QString operatorIdentifier READ operatorIdentifier NOTIFY operatorIdentifierChanged)
Q_PROPERTY(QString operatorName READ operatorName NOTIFY operatorNameChanged)
Q_PROPERTY(QString simIdentifier READ simIdentifier NOTIFY simIdentifierChanged)
Q_PROPERTY(QStringList emergencyNumbers READ emergencyNumbers NOTIFY emergencyNumbersChanged)
Q_PROPERTY(QString uni READ uni NOTIFY uniChanged)
Q_PROPERTY(QString displayId READ displayId NOTIFY displayIdChanged)
Q_PROPERTY(Modem *modem READ modem NOTIFY modemChanged)
public:
Sim(QObject *parent = nullptr,
Modem *modem = nullptr,
ModemManager::Sim::Ptr mmSim = ModemManager::Sim::Ptr{nullptr},
ModemManager::Modem::Ptr mmModem = ModemManager::Modem::Ptr{nullptr},
ModemManager::Modem3gpp::Ptr mmModem3gpp = ModemManager::Modem3gpp::Ptr{nullptr});
bool enabled();
bool pinEnabled();
int unlockRetriesLeft();
bool locked();
QString lockedReason();
QString imsi();
QString eid(); // TODO add in mm-qt
QString operatorIdentifier();
QString operatorName();
QString simIdentifier();
QStringList emergencyNumbers(); // TODO add in mm-qt
QString uni();
QString displayId();
Modem *modem();
Q_INVOKABLE void togglePinEnabled(const QString &pin);
Q_INVOKABLE void changePin(const QString &oldPin, const QString &newPin);
Q_INVOKABLE void sendPin(const QString &pin);
Q_INVOKABLE void sendPuk(const QString &pin, const QString &puk);
Q_SIGNALS:
void enabledChanged();
void pinEnabledChanged();
void unlockRetriesLeftChanged();
void lockedChanged();
void lockedReasonChanged();
void imsiChanged();
void eidChanged();
void operatorIdentifierChanged();
void operatorNameChanged();
void simIdentifierChanged();
void emergencyNumbersChanged();
void uniChanged();
void displayIdChanged();
void modemChanged();
private:
Modem *m_modem;
ModemManager::Sim::Ptr m_mmSim;
ModemManager::Modem::Ptr m_mmModem;
ModemManager::Modem3gpp::Ptr m_mmModem3gpp; // this may be a nullptr if no sim is inserted
};

22
kcms/info/CMakeLists.txt Normal file
View file

@ -0,0 +1,22 @@
set(info_SRCS # Specify source files for the library
info.cpp
distroinfo.cpp
softwareinfo.cpp
hardwareinfo.cpp
)
add_library(kcm_mobile_info MODULE ${info_SRCS})
target_link_libraries(kcm_mobile_info
Qt::Core
KF6::CoreAddons
KF6::I18n
KF6::QuickAddons
KF6::ConfigCore
KF6::Solid
)
install(TARGETS kcm_mobile_info DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) # Install the library to the kcm location
kpackage_install_package(package kcm_mobile_info kcms) # Finally install our QML kpackage.

1
kcms/info/Messages.sh Normal file
View file

@ -0,0 +1 @@
$XGETTEXT $(find . -name \*.cpp -o -name \*.h -o -name \*.qml) -o $podir/kcm_mobile_info.pot

12
kcms/info/distroinfo.cpp Normal file
View file

@ -0,0 +1,12 @@
/*
SPDX-FileCopyrightText: 2019 Jonah Brüchert <jbb@kaidan.im>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "distroinfo.h"
DistroInfo::DistroInfo(QObject *parent)
: QObject(parent)
{
}

69
kcms/info/distroinfo.h Normal file
View file

@ -0,0 +1,69 @@
/*
SPDX-FileCopyrightText: 2019 Jonah Brüchert <jbb@kaidan.im>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <KOSRelease>
#include <QObject>
// clang-format off
#define PROPERTY(type, name) \
type name() const { return m_osrelease.name(); }\
// clang-format off
#ifndef DISTROINFO_H
#define DISTROINFO_H
class DistroInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString version READ version CONSTANT)
Q_PROPERTY(QString id READ id CONSTANT)
Q_PROPERTY(QStringList idLike READ idLike CONSTANT)
Q_PROPERTY(QString versionCodename READ versionCodename CONSTANT)
Q_PROPERTY(QString versionId READ versionId CONSTANT)
Q_PROPERTY(QString prettyName READ prettyName CONSTANT)
Q_PROPERTY(QString ansiColor READ ansiColor CONSTANT)
Q_PROPERTY(QString cpeName READ cpeName CONSTANT)
Q_PROPERTY(QString homeUrl READ homeUrl CONSTANT)
Q_PROPERTY(QString documentationUrl READ documentationUrl CONSTANT)
Q_PROPERTY(QString supportUrl READ supportUrl CONSTANT)
Q_PROPERTY(QString bugReportUrl READ bugReportUrl CONSTANT)
Q_PROPERTY(QString privacyPolicyUrl READ privacyPolicyUrl CONSTANT)
Q_PROPERTY(QString buildId READ buildId CONSTANT)
Q_PROPERTY(QString variant READ variant CONSTANT)
Q_PROPERTY(QString variantId READ variantId CONSTANT)
Q_PROPERTY(QString logo READ logo CONSTANT)
public:
DistroInfo(QObject *parent = nullptr);
PROPERTY(QString, name)
PROPERTY(QString, version)
PROPERTY(QString, id)
PROPERTY(QStringList, idLike)
PROPERTY(QString, versionCodename)
PROPERTY(QString, versionId)
PROPERTY(QString, prettyName)
PROPERTY(QString, ansiColor)
PROPERTY(QString, cpeName)
PROPERTY(QString, homeUrl)
PROPERTY(QString, documentationUrl)
PROPERTY(QString, supportUrl)
PROPERTY(QString, bugReportUrl)
PROPERTY(QString, privacyPolicyUrl)
PROPERTY(QString, buildId)
PROPERTY(QString, variant)
PROPERTY(QString, variantId)
PROPERTY(QString, logo)
private:
KOSRelease m_osrelease;
};
#endif // DISTROINFO_H

View file

@ -0,0 +1,89 @@
/*
SPDX-FileCopyrightText: 2019 Jonah Brüchert <jbb@kaidan.im>
SPDX-FileCopyrightText: 2012-2019 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "hardwareinfo.h"
#include <KCoreAddons>
#include <KFormat>
#include <solid/device.h>
#include <solid/processor.h>
#include <KLocalizedString>
#ifdef Q_OS_LINUX
#include <sys/sysinfo.h>
#elif defined(Q_OS_FREEBSD)
#include <sys/sysctl.h>
#include <sys/types.h>
#endif
HardwareInfo::HardwareInfo(QObject *parent)
: QObject(parent)
{
}
int HardwareInfo::processorCount() const
{
return Solid::Device::listFromType(Solid::DeviceInterface::Processor).count();
}
QString HardwareInfo::processors() const
{
const auto list = Solid::Device::listFromType(Solid::DeviceInterface::Processor);
// Format processor string
// Group by processor name
QMap<QString, int> processorMap;
for (const auto &device : list) {
const QString name = device.product();
auto it = processorMap.find(name);
if (it == processorMap.end()) {
processorMap.insert(name, 1);
} else {
++it.value();
}
}
// Create a formatted list of grouped processors
QStringList names;
names.reserve(processorMap.count());
for (auto it = processorMap.constBegin(); it != processorMap.constEnd(); ++it) {
const int count = it.value();
QString name = it.key();
name.replace(QStringLiteral("(TM)"), QChar(8482));
name.replace(QStringLiteral("(R)"), QChar(174));
name = name.simplified();
names.append(QStringLiteral("%1 × %2").arg(count).arg(name));
}
const QString processorLabel = names.join(QLatin1String(", "));
return processorLabel;
}
QString HardwareInfo::memory() const
{
qlonglong totalRam = -1;
#ifdef Q_OS_LINUX
struct sysinfo info {
};
if (sysinfo(&info) == 0)
// manpage "sizes are given as multiples of mem_unit bytes"
totalRam = qlonglong(info.totalram) * info.mem_unit;
#elif defined(Q_OS_FREEBSD)
/* Stuff for sysctl */
size_t len;
unsigned long memory;
len = sizeof(memory);
sysctlbyname("hw.physmem", &memory, &len, NULL, 0);
totalRam = memory;
#endif
return KFormat().formatByteSize(totalRam);
}

28
kcms/info/hardwareinfo.h Normal file
View file

@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2019 Jonah Brüchert <jbb@kaidan.im>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <QObject>
#ifndef HARDWAREINFO_H
#define HARDWAREINFO_H
class HardwareInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(QString processors READ processors CONSTANT)
Q_PROPERTY(int processorCount READ processorCount CONSTANT)
Q_PROPERTY(QString memory READ memory CONSTANT)
public:
HardwareInfo(QObject *parent = nullptr);
QString processors() const;
int processorCount() const;
QString memory() const;
};
#endif // HARDWAREINFO_H

65
kcms/info/info.cpp Normal file
View file

@ -0,0 +1,65 @@
/*
SPDX-FileCopyrightText: 2019 Jonah Brüchert <jbb@kaidan.im>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "info.h"
#include <KLocalizedString>
#include <KPluginFactory>
#include <QClipboard>
#include <QGuiApplication>
K_PLUGIN_CLASS_WITH_JSON(Info, "info.json")
Info::Info(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, metaData, args)
, m_distroInfo(new DistroInfo(this))
, m_softwareInfo(new SoftwareInfo(this))
, m_hardwareInfo(new HardwareInfo(this))
{
setButtons(Apply | Default);
qDebug() << "Info module loaded.";
}
void Info::copyInfoToClipboard() const
{
const QString clipboardText = QStringLiteral(
"Operating System: %1\n"
"KDE Plasma Version: %2\n"
"KDE Frameworks Version: %3\n"
"Qt Version: %4\n"
"Kernel Version: %5\n"
"OS-Type: %6\n"
"Processor: %7\n"
"Memory: %8\n")
.arg(distroInfo()->name(),
softwareInfo()->plasmaVersion(),
softwareInfo()->frameworksVersion(),
softwareInfo()->qtVersion(),
softwareInfo()->kernelRelease(),
softwareInfo()->osType(),
hardwareInfo()->processors(),
hardwareInfo()->memory());
QGuiApplication::clipboard()->setText(clipboardText);
}
DistroInfo *Info::distroInfo() const
{
return m_distroInfo;
}
SoftwareInfo *Info::softwareInfo() const
{
return m_softwareInfo;
}
HardwareInfo *Info::hardwareInfo() const
{
return m_hardwareInfo;
}
#include "info.moc"

42
kcms/info/info.h Normal file
View file

@ -0,0 +1,42 @@
/*
SPDX-FileCopyrightText: 2019 Jonah Brüchert <jbb@kaidan.im>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "distroinfo.h"
#include "hardwareinfo.h"
#include "softwareinfo.h"
#include <KQuickAddons/ConfigModule>
#ifndef INFO_H
#define INFO_H
class Info : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(DistroInfo *distroInfo READ distroInfo NOTIFY distroInfoChanged)
Q_PROPERTY(SoftwareInfo *softwareInfo READ softwareInfo NOTIFY softwareInfoChanged)
Q_PROPERTY(HardwareInfo *hardwareInfo READ hardwareInfo NOTIFY hardwareInfoChanged)
DistroInfo *distroInfo() const;
SoftwareInfo *softwareInfo() const;
HardwareInfo *hardwareInfo() const;
public:
Info(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args);
Q_INVOKABLE void copyInfoToClipboard() const;
Q_SIGNALS:
void distroInfoChanged();
void softwareInfoChanged();
void hardwareInfoChanged();
private:
DistroInfo *m_distroInfo;
SoftwareInfo *m_softwareInfo;
HardwareInfo *m_hardwareInfo;
};
#endif // INFO_H

123
kcms/info/info.json Normal file
View file

@ -0,0 +1,123 @@
{
"Categories": "Qt;KDE;X-KDE-settings-system;",
"KPlugin": {
"Description": "Software Versions",
"Description[az]": "Proqram təminatı versiyası",
"Description[ca@valencia]": "Versions de programari",
"Description[ca]": "Versions de programari",
"Description[cs]": "Verze softwaru",
"Description[de]": "Software-Versionen",
"Description[en_GB]": "Software Versions",
"Description[es]": "Versiones de software",
"Description[eu]": "Softwarearen bertsioak",
"Description[fi]": "Ohjelmaversiot",
"Description[fr]": "Versions des logiciels",
"Description[hu]": "Szoftververziók",
"Description[ia]": "Versiones de software",
"Description[id]": "Versi Perangkat Lunak",
"Description[is]": "Útgáfur hugbúnaðar",
"Description[it]": "Versioni software",
"Description[ka]": "პროგრამების ვერსიები",
"Description[ko]": "소프트웨어 버전",
"Description[lt]": "Programinės įrangos versijos",
"Description[nl]": "Softwareversies",
"Description[nn]": "Programversjonar",
"Description[pa]": "ਸਾਫਟਵੇਅਰ ਵਰਜ਼ਨ",
"Description[pl]": "Wersje oprogramowania",
"Description[pt]": "Versões do 'Software'",
"Description[pt_BR]": "Versões do software",
"Description[ro]": "Versiuni de programe",
"Description[ru]": "Версии приложений",
"Description[sl]": "Verzije programja",
"Description[sv]": "Programvaruversioner",
"Description[tr]": "Yazılım Sürümleri",
"Description[uk]": "Версії програмного забезпечення",
"Description[vi]": "Các phiên bản phần mềm",
"Description[x-test]": "xxSoftware Versionsxx",
"Description[zh_CN]": "软件版本",
"Description[zh_TW]": "軟體版本",
"FormFactors": [
"handset",
"tablet",
"mediacenter"
],
"Icon": "dialog-information",
"Name": "Information",
"Name[az]": "Məlumat",
"Name[ca@valencia]": "Informació",
"Name[ca]": "Informació",
"Name[cs]": "Informace",
"Name[da]": "Information",
"Name[de]": "Informationen",
"Name[en_GB]": "Information",
"Name[es]": "Información",
"Name[et]": "Teave",
"Name[eu]": "Informazioa",
"Name[fi]": "Tiedot",
"Name[fr]": "Informations",
"Name[gl]": "Información",
"Name[hu]": "Információk",
"Name[ia]": "Information",
"Name[id]": "Informasi",
"Name[is]": "Upplýsingar",
"Name[it]": "Informazioni",
"Name[ka]": "ინფორმაცია",
"Name[ko]": "정보",
"Name[lt]": "Informacija",
"Name[nl]": "Informatie",
"Name[nn]": "Informasjon",
"Name[pa]": "ਜਾਣਕਾਰੀ",
"Name[pl]": "Informacje",
"Name[pt]": "Informação",
"Name[pt_BR]": "Informações",
"Name[ro]": "Informații",
"Name[ru]": "Сведения",
"Name[sk]": "Informácie",
"Name[sl]": "Informacije",
"Name[sv]": "Information",
"Name[ta]": "விவரங்கள்",
"Name[tr]": "Bilgi",
"Name[uk]": "Інформація",
"Name[vi]": "Thông tin",
"Name[x-test]": "xxInformationxx",
"Name[zh_CN]": "信息",
"Name[zh_TW]": "資訊"
},
"X-KDE-Keywords": "info, distro, system",
"X-KDE-Keywords[ast]": "info, información, systema",
"X-KDE-Keywords[az]": "info, distro, system,məlumat,distribütor,sistem",
"X-KDE-Keywords[ca@valencia]": "informació, distribució, sistema",
"X-KDE-Keywords[ca]": "informació, distribució, sistema",
"X-KDE-Keywords[cs]": "informace, distribuce, systém",
"X-KDE-Keywords[da]": "info, distribution, system",
"X-KDE-Keywords[de]": "information,distribution,system",
"X-KDE-Keywords[en_GB]": "info, distro, system",
"X-KDE-Keywords[es]": "información, distro, sistema",
"X-KDE-Keywords[et]": "teave, info, süsteem",
"X-KDE-Keywords[eu]": "informazioa, banaketa, sistema",
"X-KDE-Keywords[fi]": "tietoa, jakelu, järjestelmä",
"X-KDE-Keywords[fr]": "info, distrib, système",
"X-KDE-Keywords[gl]": "info, información, distro, distribución, sistema",
"X-KDE-Keywords[hu]": "információ, disztró, rendszer",
"X-KDE-Keywords[ia]": "info, distro, system",
"X-KDE-Keywords[it]": "informazioni, distribuzione, sistema",
"X-KDE-Keywords[ko]": "info, distro, system, 정보, 배포판, 시스템",
"X-KDE-Keywords[lt]": "informacija, platinamasis paketas, platinimas, distribucija, sistema, distributyvas",
"X-KDE-Keywords[nl]": "informatie, distributie, systeem",
"X-KDE-Keywords[nn]": "info, distro, distribusjon, system",
"X-KDE-Keywords[pa]": "ਜਾਣਕਾਰੀ, ਡਿਸਟਰੋ, ਸਿਸਟਮ",
"X-KDE-Keywords[pl]": "info, distro, system",
"X-KDE-Keywords[pt]": "informação, distribuição, sistema",
"X-KDE-Keywords[pt_BR]": "informação, info, distro, distribuição, sistema",
"X-KDE-Keywords[ro]": "informații, info, distribuție, sistem",
"X-KDE-Keywords[ru]": "info,distro,system,информация,дистрибутив,сведения,система",
"X-KDE-Keywords[sk]": "info, distro, systém",
"X-KDE-Keywords[sl]": "info, distro, system,sistem",
"X-KDE-Keywords[sv]": "information, distribution, system",
"X-KDE-Keywords[tr]": "bilgi, dağıtım, sistem",
"X-KDE-Keywords[uk]": "info, distro, system, інфо, відомості, дистрибутив, система",
"X-KDE-Keywords[vi]": "info,distro,system,thông tin,bản phân phối,hệ thống",
"X-KDE-Keywords[x-test]": "xxinfoxx,xx distroxx,xx systemxx",
"X-KDE-Keywords[zh_CN]": "info, distro, system, 信息, 发行版, 系统",
"X-KDE-Keywords[zh_TW]": "info, distro, system, 資訊, 發行版, 系統, 信息"
}

View file

@ -0,0 +1,148 @@
/*
SPDX-FileCopyrightText: 2019 Jonah Brüchert <jbb@kaidan.im>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick.Layouts 1.2
import QtQuick 2.7
import QtQuick.Controls 2.2 as Controls
import org.kde.kcm 1.2 as KCM
import org.kde.kirigami 2.10 as Kirigami
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
KCM.SimpleKCM {
title: i18n("System Information")
leftPadding: 0
rightPadding: 0
Kirigami.Theme.colorSet: Kirigami.Theme.Window
ColumnLayout {
width: parent.width
spacing: 0
Kirigami.Icon {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Kirigami.Units.gridUnit
Layout.bottomMargin: Kirigami.Units.gridUnit
implicitWidth: Kirigami.Units.iconSizes.huge
implicitHeight: width
source: kcm.distroInfo.logo ? kcm.distroInfo.logo : "kde"
}
MobileForm.FormCard {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormTextDelegate {
text: i18n("Operating System")
description: kcm.distroInfo.name
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormButtonDelegate {
text: i18n("Webpage")
description: kcm.distroInfo.homeUrl
onClicked: {
Qt.openUrlExternally(kcm.distroInfo.homeUrl)
}
}
}
}
MobileForm.FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: "Software"
}
MobileForm.FormTextDelegate {
text: i18n("KDE Plasma Version")
description: kcm.softwareInfo.plasmaVersion
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
text: i18n("KDE Frameworks Version")
description: kcm.softwareInfo.frameworksVersion
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
text: i18n("Qt Version")
description: kcm.softwareInfo.qtVersion
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
text: i18n("Kernel Version")
description: kcm.softwareInfo.kernelRelease
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
text: i18n("OS Type")
description: i18nc("@label %1 is the CPU bit width (e.g. 32 or 64)", "%1-bit", kcm.softwareInfo.osType)
}
}
}
MobileForm.FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: "Hardware"
}
MobileForm.FormTextDelegate {
text: i18np("Processor", "Processors", kcm.hardwareInfo.processorCount);
description: kcm.hardwareInfo.processors
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
text: i18n("Memory")
description: {
if (kcm.hardwareInfo.memory !== "0 B") {
return i18nc("@label %1 is the formatted amount of system memory (e.g. 7,7 GiB)",
"%1 of RAM", kcm.hardwareInfo.memory)
} else {
return i18nc("Unknown amount of RAM", "Unknown")
}
}
}
}
}
}
footer: RowLayout {
Item {
Layout.fillWidth: true
}
Controls.Button {
text: i18n("Copy to clipboard")
icon.name: "edit-copy"
onClicked: kcm.copyInfoToClipboard()
}
}
}

View file

@ -0,0 +1,63 @@
/*
SPDX-FileCopyrightText: 2019 Jonah Brüchert <jbb@kaidan.im>
SPDX-FileCopyrightText: 2012-2019 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "softwareinfo.h"
#include <sys/utsname.h>
#include <KConfigGroup>
#include <KCoreAddons>
#include <KDesktopFile>
#include <KLocalizedString>
#include <QDebug>
#include <QStandardPaths>
SoftwareInfo::SoftwareInfo(QObject *parent)
: QObject(parent)
{
}
QString SoftwareInfo::kernelRelease() const
{
struct utsname utsName {
};
uname(&utsName);
return QString::fromLatin1(utsName.release);
}
QString SoftwareInfo::frameworksVersion() const
{
return KCoreAddons::versionString();
}
QString SoftwareInfo::qtVersion() const
{
return QString::fromLatin1(qVersion());
}
QString SoftwareInfo::plasmaVersion() const
{
const QStringList &filePaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("wayland-sessions/plasma.desktop"));
if (filePaths.length() < 1) {
return QString();
}
// Despite the fact that there can be multiple desktop files we simply take
// the first one as users usually don't have xsessions/ in their $HOME
// data location, so the first match should (usually) be the only one and
// reflect the plasma session run.
KDesktopFile desktopFile(filePaths.first());
return desktopFile.desktopGroup().readEntry("X-KDE-PluginInfo-Version", QString());
}
QString SoftwareInfo::osType() const
{
const int bits = QT_POINTER_SIZE == 8 ? 64 : 32;
return QString::number(bits);
}

31
kcms/info/softwareinfo.h Normal file
View file

@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2019 Jonah Brüchert <jbb@kaidan.im>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <QObject>
#ifndef SOFTWAREINFO_H
#define SOFTWAREINFO_H
class SoftwareInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(QString kernelRelease READ kernelRelease CONSTANT)
Q_PROPERTY(QString frameworksVersion READ frameworksVersion CONSTANT)
Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT)
Q_PROPERTY(QString plasmaVersion READ plasmaVersion CONSTANT)
Q_PROPERTY(QString osType READ osType CONSTANT)
public:
SoftwareInfo(QObject *parent = nullptr);
QString kernelRelease() const;
QString frameworksVersion() const;
QString qtVersion() const;
QString plasmaVersion() const;
QString osType() const;
};
#endif // SOFTWAREINFO_H

View file

@ -0,0 +1,24 @@
# SPDX-License-Identifier: BSD-3-Clause
# SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@kde.org>
add_definitions(-DTRANSLATION_DOMAIN=\"kcm_mobile_power\")
add_library(kcm_mobile_power MODULE
mobilepower.cpp
batterymodel.cpp
statisticsprovider.cpp
)
target_link_libraries(kcm_mobile_power
Qt::DBus
Qt::Core
KF6::CoreAddons
KF6::I18n
KF6::QuickAddons
KF6::ConfigCore
KF6::Solid
)
install(TARGETS kcm_mobile_power DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
kpackage_install_package(package kcm_mobile_power kcms)

View file

@ -0,0 +1 @@
$XGETTEXT $(find . -name \*.cpp -o -name \*.h -o -name \*.qml) -o $podir/kcm_mobile_powermanagement.pot

View file

@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
* SPD
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "batterymodel.h"
#include <Solid/DeviceNotifier>
#include <QQmlEngine>
#include <QtQml>
BatteryModel::BatteryModel(QObject *parent)
: QAbstractListModel(parent)
{
qmlRegisterUncreatableType<Solid::Battery>("org.kde.kinfocenter.energy.private", 1, 0, "Battery", QStringLiteral("Use Solid::Battery"));
m_batteries = Solid::Device::listFromType(Solid::DeviceInterface::Battery);
connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, [this](const QString &udi) {
auto it = std::find_if(m_batteries.constBegin(), m_batteries.constEnd(), [&udi](const Solid::Device &dev) {
return dev.udi() == udi;
});
if (it != m_batteries.constEnd()) {
return;
}
const Solid::Device device(udi);
if (device.isValid() && device.is<Solid::Battery>()) {
beginInsertRows(QModelIndex(), m_batteries.count(), m_batteries.count());
m_batteries.append(device);
endInsertRows();
Q_EMIT countChanged();
}
});
connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, [this](const QString &udi) {
auto it = std::find_if(m_batteries.constBegin(), m_batteries.constEnd(), [&udi](const Solid::Device &dev) {
return dev.udi() == udi;
});
if (it == m_batteries.constEnd()) {
return;
}
int index = std::distance(m_batteries.constBegin(), it);
beginRemoveRows(QModelIndex(), index, index);
m_batteries.removeAt(index);
endRemoveRows();
Q_EMIT countChanged();
});
}
QVariant BatteryModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_batteries.count()) {
return QVariant();
}
if (role == BatteryRole) {
// .as returns a pointer to a casted DeviceInterface. This pointer must
// not, under any circumstances, be deleted outside Solid!
// https://bugs.kde.org/show_bug.cgi?id=413003
const auto battery = m_batteries.value(index.row()).as<Solid::Battery>();
QQmlEngine::setObjectOwnership(battery, QQmlEngine::CppOwnership);
return QVariant::fromValue(battery);
} else if (role == ProductRole) {
const Solid::Device device = m_batteries.value(index.row());
return device.product();
} else if (role == VendorRole) {
const Solid::Device device = m_batteries.value(index.row());
return device.vendor();
} else if (role == UdiRole) {
return m_batteries.at(index.row()).udi();
}
return QVariant();
}
int BatteryModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_batteries.count();
}
QHash<int, QByteArray> BatteryModel::roleNames() const
{
return {{BatteryRole, "battery"}, {VendorRole, "vendor"}, {ProductRole, "product"}, {UdiRole, "udi"}};
}

View file

@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QAbstractListModel>
#include <QList>
#include <Solid/Battery>
#include <Solid/Device>
class BatteryModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
explicit BatteryModel(QObject *parent);
~BatteryModel() override = default;
enum Roles {
BatteryRole = Qt::UserRole,
UdiRole,
VendorRole,
ProductRole,
};
Q_ENUM(Roles)
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
signals:
void countChanged();
private:
QList<Solid::Device> m_batteries;
};

View file

@ -0,0 +1,285 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
// SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@kde.org>
#include "mobilepower.h"
#include "statisticsprovider.h"
#include <KConfigGroup>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KSharedConfig>
#include <Solid/Battery>
K_PLUGIN_CLASS_WITH_JSON(MobilePower, "powermanagement.json")
enum {
THIRTY_SECONDS,
ONE_MINUTE,
TWO_MINUTES,
FIVE_MINUTES,
TEN_MINUTES,
FIFTEEN_MINUTES,
NEVER,
};
const QStringList timeValues = {
i18n("30 sec"),
i18n("1 min"),
i18n("2 min"),
i18n("5 min"),
i18n("10 min"),
i18n("15 min"),
i18n("Never"),
};
// Maps the indices of the timeValues indexes
// to minutes.
const QMap<int, qreal> idxToMinutes = {
{THIRTY_SECONDS, 0.5},
{ONE_MINUTE, 1},
{TWO_MINUTES, 2},
{FIVE_MINUTES, 5},
{TEN_MINUTES, 10},
{FIFTEEN_MINUTES, 15},
{NEVER, 0},
};
MobilePower::MobilePower(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, metaData, args)
, m_batteries{new BatteryModel(this)}
, m_profilesConfig{KSharedConfig::openConfig("powermanagementprofilesrc", KConfig::SimpleConfig | KConfig::CascadeConfig)}
{
qmlRegisterUncreatableType<BatteryModel>("org.kde.kcm.power.mobile.private", 1, 0, "BatteryModel", QStringLiteral("Use BatteryModel"));
qmlRegisterUncreatableType<Solid::Battery>("org.kde.kcm.power.mobile.private", 1, 0, "Battery", "");
qmlRegisterType<StatisticsProvider>("org.kde.kcm.power.mobile.private", 1, 0, "HistoryModel");
setButtons(KQuickAddons::ConfigModule::NoAdditionalButton);
load();
}
MobilePower::~MobilePower() = default;
// contents of powermanagementprofilesrc
//
// [Battery][SuspendSession] // our LockScreen
// idleTime=600000
// suspendThenHibernate = enabled / disabled.
// suspendType=1
// type 1 = sleep
// type 8 = shutdown
// type 32 = lock screen
//
// [Battery][DimDisplay] // our "Sleep Screen"
// idleTime=300000
// Aparently KDE removes this group when it's false.
//
void MobilePower::load()
{
// we assume that the [AC], [Battery], and [LowBattery] groups have the same value
// (which is done by this kcm)
KConfigGroup batteryGroup = m_profilesConfig->group("Battery");
if (batteryGroup.hasGroup("DimDisplay")) {
qDebug() << "[Battery][DimDisplay] group is listed";
KConfigGroup dimSettings = batteryGroup.group("DimDisplay");
// powerdevil/dimdisplayconfig.cpp - here we load time / 60 / 1000
// We should really, really, stop doing that.
m_dimScreenTime = (dimSettings.readEntry("idleTime").toDouble() / 60) / 1000;
} else {
qDebug() << "[Battery][DimDisplay] Group is not listed";
m_dimScreenTime = 0;
}
if (batteryGroup.hasGroup("DPMSControl")) {
qDebug() << "[Battery][DPMSControl] group is listed";
KConfigGroup dpmsSettings = batteryGroup.group("DPMSControl");
m_screenOffTime = dpmsSettings.readEntry("idleTime").toDouble() / 60 / 1000;
} else {
qDebug() << "[Battery][DPMSControl] is not listed";
m_screenOffTime = 0;
}
if (batteryGroup.hasGroup("SuspendSession")) {
qDebug() << "[Battery][SuspendSession] group is listed";
KConfigGroup suspendSessionGroup = batteryGroup.group("SuspendSession");
m_suspendSessionTime = suspendSessionGroup.readEntry("idleTime").toDouble() / 60 / 1000;
} else {
qDebug() << "[Battery][SuspendSession] is not listed";
m_suspendSessionTime = 0;
}
}
void MobilePower::save()
{
// we set all profiles at the same time, since our UI is quite a simple global toggle
KConfigGroup acGroup = m_profilesConfig->group("AC");
KConfigGroup batteryGroup = m_profilesConfig->group("Battery");
KConfigGroup lowBatteryGroup = m_profilesConfig->group("LowBattery");
if (m_dimScreenTime == 0) {
qDebug() << "Deleting the group DimDisplay";
acGroup.deleteGroup("DimDisplay", KConfigGroup::Notify);
batteryGroup.deleteGroup("DimDisplay", KConfigGroup::Notify);
lowBatteryGroup.deleteGroup("DimDisplay", KConfigGroup::Notify);
} else {
// powerdevil/dimdisplayconfig.cpp - here we store time * 60 * 1000
// We should really, really, stop doing that.
acGroup.group("DimDisplay").writeEntry("idleTime", m_dimScreenTime * 60 * 1000, KConfigGroup::Notify);
batteryGroup.group("DimDisplay").writeEntry("idleTime", m_dimScreenTime * 60 * 1000, KConfigGroup::Notify);
lowBatteryGroup.group("DimDisplay").writeEntry("idleTime", m_dimScreenTime * 60 * 1000, KConfigGroup::Notify);
}
if (m_screenOffTime == 0) {
qDebug() << "Deleting the group DPMSControl";
acGroup.deleteGroup("DPMSControl", KConfigGroup::Notify);
batteryGroup.deleteGroup("DPMSControl", KConfigGroup::Notify);
lowBatteryGroup.deleteGroup("DPMSControl", KConfigGroup::Notify);
} else {
acGroup.group("DPMSControl").writeEntry("idleTime", m_screenOffTime * 60 * 1000, KConfigGroup::Notify);
batteryGroup.group("DPMSControl").writeEntry("idleTime", m_screenOffTime * 60 * 1000, KConfigGroup::Notify);
lowBatteryGroup.group("DPMSControl").writeEntry("idleTime", m_screenOffTime * 60 * 1000, KConfigGroup::Notify);
}
// ensure the system is locked when the screen is turned off
acGroup.group("DPMSControl").writeEntry("lockBeforeTurnOff", 1, KConfigGroup::Notify);
batteryGroup.group("DPMSControl").writeEntry("lockBeforeTurnOff", 1, KConfigGroup::Notify);
lowBatteryGroup.group("DPMSControl").writeEntry("lockBeforeTurnOff", 1, KConfigGroup::Notify);
if (m_suspendSessionTime == 0) {
qDebug() << "Deleting the group SuspendDisplay";
acGroup.deleteGroup("SuspendSession", KConfigGroup::Notify);
batteryGroup.deleteGroup("SuspendSession", KConfigGroup::Notify);
lowBatteryGroup.deleteGroup("SuspendSession", KConfigGroup::Notify);
} else {
acGroup.group("SuspendSession").writeEntry("idleTime", m_suspendSessionTime * 60 * 1000, KConfigGroup::Notify);
acGroup.group("SuspendSession").writeEntry("suspendType", 1, KConfigGroup::Notify);
batteryGroup.group("SuspendSession").writeEntry("idleTime", m_suspendSessionTime * 60 * 1000, KConfigGroup::Notify);
batteryGroup.group("SuspendSession").writeEntry("suspendType", 1, KConfigGroup::Notify);
lowBatteryGroup.group("SuspendSession").writeEntry("idleTime", m_suspendSessionTime * 60 * 1000, KConfigGroup::Notify);
lowBatteryGroup.group("SuspendSession").writeEntry("suspendType", 1, KConfigGroup::Notify);
}
m_profilesConfig->sync();
// Do not mess with Suspend Type
// suspendSessionGroup.writeEntry("suspendType", 32); // always lock screen.
}
QStringList MobilePower::timeOptions() const
{
return timeValues;
}
void MobilePower::setDimScreenIdx(int idx)
{
qreal value = idxToMinutes.value(idx);
qDebug() << "Got the value" << value;
if (m_dimScreenTime == value) {
return;
}
if (value == 0) {
qDebug() << "Setting to never dim";
} else {
qDebug() << "Setting to dim in " << value << "Minutes";
}
m_dimScreenTime = value;
Q_EMIT dimScreenIdxChanged();
save();
}
void MobilePower::setScreenOffIdx(int idx)
{
qreal value = idxToMinutes.value(idx);
qDebug() << "Got the value" << value;
if (m_screenOffTime == value) {
return;
}
if (value == 0) {
qDebug() << "Setting to never screen off";
} else {
qDebug() << "Setting to screen off in " << value << "Minutes";
}
m_screenOffTime = value;
Q_EMIT screenOffIdxChanged();
save();
}
void MobilePower::setSuspendSessionIdx(int idx)
{
qreal value = idxToMinutes.value(idx);
qDebug() << "Got the value" << value;
if (m_suspendSessionTime == value) {
return;
}
if (value == 0) {
qDebug() << "Setting to never suspend";
} else {
qDebug() << "Setting to suspend in " << value << "Minutes";
}
m_suspendSessionTime = value;
Q_EMIT suspendSessionIdxChanged();
save();
}
int MobilePower::suspendSessionIdx()
{
if (m_suspendSessionTime == 0) {
return NEVER;
} else if (qFuzzyIsNull(m_suspendSessionTime)) {
return NEVER;
} else if (qFuzzyCompare(m_suspendSessionTime, 0.5)) {
return THIRTY_SECONDS;
}
return idxToMinutes.key(std::round(m_suspendSessionTime));
}
int MobilePower::dimScreenIdx()
{
if (m_dimScreenTime == 0) {
return NEVER;
} else if (qFuzzyIsNull(m_dimScreenTime)) {
return NEVER;
} else if (qFuzzyCompare(m_dimScreenTime, 0.5)) {
return THIRTY_SECONDS;
}
return idxToMinutes.key(std::round(m_dimScreenTime));
}
int MobilePower::screenOffIdx()
{
if (m_screenOffTime == 0) {
return NEVER;
} else if (qFuzzyIsNull(m_screenOffTime)) {
return NEVER;
} else if (qFuzzyCompare(m_screenOffTime, 0.5)) {
return THIRTY_SECONDS;
}
return idxToMinutes.key(std::round(m_screenOffTime));
}
BatteryModel *MobilePower::batteries()
{
return m_batteries;
}
#include "mobilepower.moc"

View file

@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
// SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@kde.org>
#pragma once
#include "batterymodel.h"
#include <KQuickAddons/ConfigModule>
#include <KSharedConfig>
#include <memory>
class MobilePower : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(BatteryModel *batteries READ batteries CONSTANT)
Q_PROPERTY(int dimScreenIdx READ dimScreenIdx WRITE setDimScreenIdx NOTIFY dimScreenIdxChanged)
Q_PROPERTY(int screenOffIdx READ screenOffIdx WRITE setScreenOffIdx NOTIFY screenOffIdxChanged)
Q_PROPERTY(int suspendSessionIdx READ suspendSessionIdx WRITE setSuspendSessionIdx NOTIFY suspendSessionIdxChanged)
public:
MobilePower(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args);
~MobilePower();
Q_INVOKABLE QStringList timeOptions() const;
void setDimScreenIdx(int idx);
void setScreenOffIdx(int idx);
void setSuspendSessionIdx(int idx);
int dimScreenIdx();
int screenOffIdx();
int suspendSessionIdx();
BatteryModel *batteries();
Q_SIGNAL void dimScreenIdxChanged();
Q_SIGNAL void screenOffIdxChanged();
Q_SIGNAL void suspendSessionIdxChanged();
QString stringForValue(int value);
void load() override;
void save() override;
private:
BatteryModel *m_batteries;
KSharedConfig::Ptr m_profilesConfig;
qreal m_suspendSessionTime;
qreal m_dimScreenTime;
qreal m_screenOffTime;
};

View file

@ -0,0 +1,185 @@
/*
SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.2
import QtQuick.Controls 2.10 as QQC2
import QtQuick.Layouts 1.11
import org.kde.kirigami 2.10 as Kirigami
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.kcm 1.2
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import org.kde.kcm.power.mobile.private 1.0
Kirigami.ScrollablePage {
id: root
property QtObject battery
property string vendor
property string product
property string currentUdi
title: i18n("Battery Information")
leftPadding: 0
rightPadding: 0
topPadding: Kirigami.Units.gridUnit
bottomPadding: Kirigami.Units.gridUnit
HistoryModel {
id: history
duration: 86400 // last 24 hours
device: currentUdi
type: HistoryModel.ChargeType
}
ColumnLayout {
width: parent.width
spacing: 0
MobileForm.FormCard {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("Usage Graph")
}
MobileForm.AbstractFormDelegate {
Layout.fillWidth: true
background: Item {}
clip: true
contentItem: Flickable {
implicitWidth: 500
implicitHeight: 200
contentWidth: 500
contentHeight: 200
Graph {
id: graph
width: 500
height: 200
implicitWidth: 500
implicitHeight: 200
data: history.points
// Set grid lines distances which directly correspondent to the xTicksAt variables
readonly property var xDivisionWidths: [1000 * 60 * 10, 1000 * 60 * 60 * 12, 1000 * 60 * 60, 1000 * 60 * 30, 1000 * 60 * 60 * 2, 1000 * 60 * 10]
xTicksAt: graph.xTicksAtFullSecondHour
xDivisionWidth: xDivisionWidths[xTicksAt]
xMin: history.firstDataPointTime
xMax: history.lastDataPointTime
xDuration: history.duration
yUnits: i18nc("literal percent sign","%")
yMax: 100
yStep: 20
visible: history.count > 1
}
}
}
}
}
MobileForm.FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("Information")
}
MobileForm.FormTextDelegate {
id: isRechargeableDelegate
text: i18n("Is Rechargeable")
description: battery.rechargeable ? i18n("Yes") : i18n("No")
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: chargeStateDelegate
text: i18n("Charge State")
description: {
switch (battery.chargeState) {
case Battery.NoCharge: return i18n("Not charging")
case Battery.Charging: return i18n("Charging")
case Battery.Discharging: return i18n("Discharging")
case Battery.FullyCharged: return i18n("Fully charged")
default: return i18n("Unknown")
}
}
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: currentChargeDelegate
text: i18n("Current Charge")
description: i18nc("%1 is value, %2 is unit", "%1 %2", Number(battery.chargePercent).toLocaleString(Qt.locale(), "f", 0), i18n("%"))
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: healthDelegate
text: i18n("Health")
description: i18nc("%1 is value, %2 is unit", "%1 %2", Number(battery.capacity).toLocaleString(Qt.locale(), "f", 0), i18n("%"))
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: vendorDelegate
text: i18n("Vendor")
description: root.vendor
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: modelDelegate
text: i18n("Model")
description: root.product
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: serialDelegate
text: i18n("Serial Number")
description: battery.serial
}
MobileForm.FormDelegateSeparator {}
MobileForm.FormTextDelegate {
id: technologyDelegate
text: i18n("Technology")
description: {
switch (battery.technology) {
case Battery.LithiumIon: return i18n("Lithium ion")
case Battery.LithiumPolymer: return i18n("Lithium polymer")
case Battery.LithiumIronPhosphate: return i18n("Lithium iron phosphate")
case Battery.LeadAcid: return i18n("Lead acid")
case Battery.NickelCadmium: return i18n("Nickel cadmium")
case Battery.NickelMetalHydride: return i18n("Nickel metal hydride")
default: return i18n("Unknown technology")
}
}
}
}
}
}
}

View file

@ -0,0 +1,243 @@
/*
* SPDX-FileCopyrightText: 2015 David Edmundson <david@davidedmundson.co.uk>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
import QtQuick 2.3
/**
* We need to draw a graph, all other libs are not suitable as we are basically
* a connected scatter plot with non linear X spacing.
* Currently this is not available in kdeclarative nor kqtquickcharts
*
* We only paint once, so canvas is fast enough for our purposes.
* It is designed to look identical to those in ksysguard.
*/
Canvas
{
id: canvas
antialiasing: true
readonly property real xTicksAtDontCare: 0
readonly property real xTicksAtTwelveOClock: 1
readonly property real xTicksAtFullHour: 2
readonly property real xTicksAtHalfHour: 3
readonly property real xTicksAtFullSecondHour: 4
readonly property real xTicksAtTenMinutes: 5
readonly property real xTicksAtFullTwoHours: 6
property int xPadding: 45
property int yPadding: 40
property var data //expect an array of QPointF
property real yMax: 100
property real xMax: 100
property real yMin: 0
property real xMin: 0
property real yStep: 20
property string yUnits: ""
property string xUnits: ""
property real xDuration: 3600
property real xDivisions: 6
property real xDivisionWidth: 600000
property real xTicksAt: xTicksAtDontCare
//internal
property real plotWidth: width - xPadding * 1.5
property real plotHeight: height - yPadding * 2
onDataChanged: {
canvas.requestPaint();
}
//take a QPointF
function scalePoint(plot, currentUnixTime) {
var scaledX = (plot.x - (currentUnixTime / 1000 - xDuration)) / xDuration * plotWidth;
var scaledY = (plot.y - yMin) * plotHeight / (yMax - yMin);
return Qt.point(xPadding + scaledX,
height - yPadding - scaledY);
}
SystemPalette {
id: palette;
colorGroup: SystemPalette.Active
}
onPaint: {
var c = canvas.getContext('2d');
c.clearRect(0,0, width, height)
//draw the background
c.fillStyle = palette.base
c.fillRect(xPadding, yPadding, plotWidth, plotHeight);
//reset for fonts and stuff
c.fillStyle = palette.text
//Draw the lines
c.lineWidth = 1;
c.lineJoin = 'round';
c.lineCap = 'round';
c.strokeStyle = 'rgba(255, 0, 0, 1)';
var gradient = c.createLinearGradient(0,0,0,height);
gradient.addColorStop(0, 'rgba(255, 0, 0, 0.2)');
gradient.addColorStop(1, 'rgba(255, 0, 0, 0.05)');
c.fillStyle = gradient;
// For scaling
var currentUnixTime = Date.now()
var xMinUnixTime = currentUnixTime - xDuration * 1000
// Draw the line graph
c.beginPath();
var index = 0
while ((index < data.length - 1) && (data[index].x < (xMinUnixTime / 1000))) {
index++
}
var firstPoint = scalePoint(data[index], currentUnixTime)
c.moveTo(firstPoint.x, firstPoint.y)
var point
for (var i = index + 1; i < data.length; i++) {
if (data[i].x > (xMinUnixTime / 1000)) {
point = scalePoint(data[i], currentUnixTime)
c.lineTo(point.x, point.y)
}
}
c.stroke();
c.strokeStyle = 'rgba(0, 0, 0, 0)';
c.lineTo(point.x, height - yPadding);
c.lineTo(firstPoint.x, height - yPadding);
c.fill();
c.closePath()
// Draw the frame on top
//draw an outline
c.strokeStyle = 'rgba(0,50,0,0.02)';
c.lineWidth = 1;
c.rect(xPadding - 1, yPadding - 1, plotWidth + 2, plotHeight + 2);
// Draw the Y value texts
c.fillStyle = palette.text;
c.textAlign = "right"
c.textBaseline = "middle";
for(var i = 0; i <= yMax; i += yStep) {
var y = scalePoint(Qt.point(0,i)).y;
c.fillText(i + canvas.yUnits, xPadding - 10, y);
//grid line
c.moveTo(xPadding, y)
c.lineTo(plotWidth + xPadding, y)
}
c.stroke()
// Draw the X value texts
c.textAlign = "center"
c.lineWidth = 1
c.strokeStyle = 'rgba(0, 0, 0, 0.15)'
var xDivisions = xDuration / xDivisionWidth * 1000
var xGridDistance = plotWidth / xDivisions
var xTickPos
var xTickDateTime
var xTickDateStr
var xTickTimeStr
var currentDateTime = new Date()
var lastDateStr = currentDateTime.toLocaleDateString(Qt.locale(), Locale.ShortFormat)
var hours = currentDateTime.getHours()
var minutes = currentDateTime.getMinutes()
var seconds = currentDateTime.getSeconds()
var diff
switch (xTicksAt) {
case xTicksAtTwelveOClock:
diff = ((hours - 12) * 60 * 60 + minutes * 60 + seconds)
break
case xTicksAtFullHour:
diff = (minutes * 60 + seconds)
break
case xTicksAtFullSecondHour:
diff = (minutes * 60 + seconds)
break
case xTicksAtHalfHour:
diff = ((minutes - 30) * 60 + seconds)
break
case xTicksAtTenMinutes:
diff = ((minutes % 10) * 60 + seconds)
break
default:
diff = 0
}
var xGridOffset = plotWidth * (diff / xDuration)
var dateChanged = false
var dashedLines = 50
var dashedLineLength = plotHeight / dashedLines
var dashedLineDutyCycle
for (var i = xDivisions; i >= -1; i--) {
xTickPos = i * xGridDistance + xPadding - xGridOffset
if ((xTickPos > xPadding) && (xTickPos < plotWidth + xPadding))
{
xTickDateTime = new Date(currentUnixTime - (xDivisions - i) * xDivisionWidth - diff * 1000)
xTickDateStr = xTickDateTime.toLocaleDateString(Qt.locale(), Locale.ShortFormat)
xTickTimeStr = xTickDateTime.toLocaleTimeString(Qt.locale(), Locale.ShortFormat)
if (lastDateStr != xTickDateStr) {
dateChanged = true
}
if ((i % 2 == 0) || (xDivisions < 10))
{
// Display the time
c.fillText(xTickTimeStr, xTickPos, canvas.height - yPadding / 2)
// If the date has changed and is not the current day in a <= 24h graph, display it
// Always display the date for 48h and 1 week graphs
if (dateChanged || (xDuration > (60*60*48))) {
c.fillText(xTickDateStr, xTickPos, canvas.height - yPadding / 4)
dateChanged = false
}
// Tick markers
c.moveTo(xTickPos, canvas.height - yPadding)
c.lineTo(xTickPos, canvas.height - (yPadding * 4) / 5)
dashedLineDutyCycle = 0.5
} else {
dashedLineDutyCycle = 0.1
}
for (var j = 0; j < dashedLines; j++) {
c.moveTo(xTickPos, yPadding + j * dashedLineLength)
c.lineTo(xTickPos, yPadding + j * dashedLineLength + dashedLineDutyCycle * dashedLineLength)
}
lastDateStr = xTickDateStr
}
}
c.stroke()
}
}

View file

@ -0,0 +1,165 @@
/*
SPDX-FileCopyrightText: 2011 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.2
import QtQuick.Controls 2.10 as QQC2
import QtQuick.Layouts 1.11
import org.kde.kirigami 2.10 as Kirigami
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.kcm 1.2
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import org.kde.kcm.power.mobile.private 1.0
SimpleKCM {
id: powermanagementModule
leftPadding: 0
rightPadding: 0
topPadding: Kirigami.Units.gridUnit
bottomPadding: Kirigami.Units.gridUnit
ColumnLayout {
width: parent.width
spacing: 0
MobileForm.FormCard {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("Devices")
}
Repeater {
model: kcm.batteries
delegate: MobileForm.AbstractFormDelegate {
Layout.fillWidth: true
onClicked: kcm.push("BatteryPage.qml", { "battery": model.battery, "vendor": model.vendor, "product": model.product, "currentUdi": model.udi })
contentItem: RowLayout {
spacing: Kirigami.Units.gridUnit
Kirigami.Icon {
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
Layout.rightMargin: Kirigami.Units.largeSpacing
source: {
switch (model.battery.type) {
case 3: return model.battery.chargeState === 1 ? "battery-full-charging" : "battery-full"
case 2: return "battery-ups"
case 9: return "monitor"
case 4: return "input-mouse"
case 5: return "input-keyboard"
case 1: return "phone"
case 7: return "smartphone"
default: return "paint-unknown"
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
QQC2.Label {
Layout.fillWidth: true
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
color: Kirigami.Theme.textColor
text: {
let batteryType;
switch (model.battery.type) {
case 3: batteryType = i18n("Internal battery"); break;
case 2: batteryType = i18n("UPS battery"); break;
case 9: batteryType = i18n("Monitor battery"); break;
case 4: batteryType = i18n("Mouse battery"); break;
case 5: batteryType = i18n("Keyboard battery"); break;
case 1: batteryType = i18n("PDA battery"); break;
case 7: batteryType = i18n("Phone battery"); break;
default: batteryType = i18n("Unknown battery"); break;
}
const chargePercent = i18nc("%1 is value, %2 is unit", "%1%2", Number(battery.chargePercent).toLocaleString(Qt.locale(), "f", 0), i18n("%"));
return (model.battery.chargeState === Battery.Charging) ? i18nc("%1 is battery type, %2 is charge percent", "%1 %2 (Charging)", batteryType, chargePercent) : i18nc("%1 is battery type, %2 is charge percent", "%1 %2", batteryType, chargePercent);
}
}
QQC2.ProgressBar {
Layout.fillWidth: true
from: 0
to: 100
value: model.battery.chargePercent
}
}
Kirigami.Icon {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
source: "arrow-right"
implicitWidth: Math.round(Kirigami.Units.iconSizes.small * 0.75)
implicitHeight: Math.round(Kirigami.Units.iconSizes.small * 0.75)
}
}
}
}
}
}
MobileForm.FormCard {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.largeSpacing
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("Screen")
}
MobileForm.FormComboBoxDelegate {
id: dimScreenCombo
text: i18nc("Part of a sentence like 'Dim screen after 5 minutes'", "Dim screen after")
model: kcm.timeOptions()
currentIndex: kcm.dimScreenIdx
Component.onCompleted: dialog.parent = powermanagementModule
onCurrentIndexChanged: kcm.dimScreenIdx = currentIndex
}
MobileForm.FormDelegateSeparator { above: dimScreenCombo; below: screenOffCombo }
MobileForm.FormComboBoxDelegate {
id: screenOffCombo
text: i18nc("Part of a sentence like 'Turn off screen after 5 minutes'", "Turn off screen after")
model: kcm.timeOptions()
currentIndex: kcm.screenOffIdx
Component.onCompleted: dialog.parent = powermanagementModule
onCurrentIndexChanged: kcm.screenOffIdx = currentIndex
}
MobileForm.FormDelegateSeparator { above: screenOffCombo; below: suspendCombo }
MobileForm.FormComboBoxDelegate {
id: suspendCombo
text: i18nc("Part of a sentence like 'Suspend device after 5 minutes'", "Suspend device after")
model: kcm.timeOptions()
currentIndex: kcm.suspendSessionIdx
Component.onCompleted: dialog.parent = powermanagementModule
onCurrentIndexChanged: kcm.suspendSessionIdx = currentIndex
}
}
}
}
}

View file

@ -0,0 +1,111 @@
{
"Categories": "Qt;KDE;X-KDE-settings-system;",
"KPlugin": {
"Description": "Configure power management settings",
"Description[az]": "Enerji sərfiyyatı ayarlarını tənzimlə",
"Description[ca@valencia]": "Configureu els paràmetres de gestió de l'energia",
"Description[ca]": "Configuració dels paràmetres de gestió d'energia",
"Description[cs]": "Konfigurace nastavení správy napájení",
"Description[en_GB]": "Configure power management settings",
"Description[es]": "Configurar las preferencias de la gestión de energía",
"Description[eu]": "Konfiguratu energia-kudeaketa ezarpenak",
"Description[fi]": "Aseta virranhallinta",
"Description[fr]": "Configurer les paramètres de gestion de l'énergie ",
"Description[ia]": "Configura preferentias de gestion de energia",
"Description[is]": "Grunnstilla orkunotkun",
"Description[it]": "Configura le impostazioni di gestione energetica",
"Description[ka]": "ენერგიის მართვის პარამეტრების კონფიგურაცია",
"Description[ko]": "전력 관리 설정",
"Description[lt]": "Konfigūruoti energijos valdymo nuostatas",
"Description[nl]": "Energie-instellingen configureren",
"Description[nn]": "Set opp straumstyring",
"Description[pa]": "ਪਾਵਰ ਇੰਤਜ਼ਾਮ ਸੈਟਿੰਗਾਂ ਦੀ ਸੰਰਚਨਾ",
"Description[pl]": "Ustawienia zarządzania energią",
"Description[pt]": "Configurar as definições de gestão de energia",
"Description[pt_BR]": "Configurar as opções do gerenciamento de energia",
"Description[ru]": "Настройка параметров энергопотребления",
"Description[sl]": "Konfiguriraj nastavitve upravljanja energijo",
"Description[sv]": "Anpassa inställningar av strömhantering",
"Description[tr]": "Güç yönetimi ayarlarını yapılandır",
"Description[uk]": "Налаштовування керування живленням",
"Description[vi]": "Cấu hình thiết lập quản lí nguồn điện",
"Description[x-test]": "xxConfigure power management settingsxx",
"Description[zh_CN]": "配置电源管理设置。",
"FormFactors": [
"handset",
"tablet",
"mediacenter"
],
"Icon": "preferences-system-power-management",
"Name": "Energy",
"Name[az]": "Enerji sərfiyyatı",
"Name[ca@valencia]": "Energia",
"Name[ca]": "Energia",
"Name[cs]": "Energie",
"Name[en_GB]": "Energy",
"Name[es]": "Energía",
"Name[eu]": "Energia",
"Name[fi]": "Virta",
"Name[fr]": "Énergie",
"Name[ia]": "Energia",
"Name[is]": "Orka",
"Name[it]": "Energia",
"Name[ka]": "ენერგია",
"Name[ko]": "에너지",
"Name[lt]": "Energija",
"Name[nl]": "Energie",
"Name[nn]": "Energibruk",
"Name[pa]": "ਊਰਜਾ",
"Name[pl]": "Energia",
"Name[pt]": "Energia",
"Name[pt_BR]": "Energia",
"Name[ru]": "Энергопотребление",
"Name[sl]": "Energija",
"Name[sv]": "Energi",
"Name[tr]": "Enerji",
"Name[uk]": "Живлення",
"Name[vi]": "Năng lượng",
"Name[x-test]": "xxEnergyxx",
"Name[zh_CN]": "电量"
},
"X-KDE-Keywords": "video, monitor, graphics, timeout, sleep, lock, screenlocker, screensaver",
"X-KDE-Keywords[ast]": "videu,monitor,gráficos,dormir,dormición,bloquiar,bloquéu,curiapantalles,bloquiador de pantalla",
"X-KDE-Keywords[az]": "video, monitor, graphics, timeout, sleep, lock, screenlocker, screensaver,qrafiklər,boşdayanma,yuxu,kilid,ekran kilidləyici,ekran qoruyucu",
"X-KDE-Keywords[ca@valencia]": "vídeo, monitor, gràfics, temps d'espera, adorm, bloqueja, bloqueig de la pantalla, estalvi de pantalla",
"X-KDE-Keywords[ca]": "vídeo, monitor, gràfics, temps d'espera, adorm, bloqueja, bloqueig de la pantalla, estalvi de pantalla",
"X-KDE-Keywords[cs]": "video, monitor, grafika, časový limit, spánek, zámek, zamčení obrazovky, spořič obrazovky",
"X-KDE-Keywords[da]": "video, skærm, grafik, tidsudløb, slumre, lås, skærmlåsning, pauseskærm",
"X-KDE-Keywords[de]": "Video,Monitor,Grafik,Zeitüberschreitung,Standby,Sperren, Bildschirmsperre,Bildschirmschoner",
"X-KDE-Keywords[el]": "βίντεο, οθόνη, γραφικά, χρονικό όριο, ύπνωση, κλείδωμα, κλείδωμα οθόνης, προστασία οθόνης",
"X-KDE-Keywords[en_GB]": "video, monitor, graphics, timeout, sleep, lock, screenlocker, screensaver",
"X-KDE-Keywords[es]": "vídeo, monitor, gráficos, tiempo de espera, dormir, bloquear, bloqueo de pantalla, salvapantallas",
"X-KDE-Keywords[et]": "video, monitor, graafika, aegumine, uni, lukustus, ekraanisäästja",
"X-KDE-Keywords[eu]": "bideoa, monitorea, grafikoak, denbora-muga, egin lo, giltzatu, pantaila-giltzatzailea, pantaila-babeslea",
"X-KDE-Keywords[fi]": "video, näyttö, grafiikka, aikakatkaisu, keskeytystila, lukitus, näyttölukko, näytönsäästäjä",
"X-KDE-Keywords[fr]": "vidéo, écran, graphique, mise en veille, sommeil, verrouillage, verrouillage d'écran, écran de veille",
"X-KDE-Keywords[gl]": "vídeo, monitor, gráficos, tempo límite, hibernar, bloquear, bloqueador de pantalla, salvapantallas",
"X-KDE-Keywords[hu]": "videó, monitor, grafika, időkorlát, alvás, zárolás, képernyőzár, képernyővédő",
"X-KDE-Keywords[ia]": "video, monitor, graphics, timeout, sleep, lock, screenlocker, screensaver",
"X-KDE-Keywords[it]": "video, monitor, grafica, tempo massimo, attendi, blocca, bloccaschermo, salvaschermo",
"X-KDE-Keywords[ko]": "video, monitor, graphics, timeout, sleep, lock, screenlocker, screensaver, 비디오, 모니터, 그래픽, 시간 제한, 절전, 대기, 잠금, 화면 보호기, 화면 잠금",
"X-KDE-Keywords[lt]": "vaizdas, monitorius, vaizduoklis, grafika, laiko limitas, miegas, miegoti, užraktas, uzraktas, užrakinti, uzrakinti, ekrano užraktas, ekrano uzraktas, ekrano užsklanda, ekrano uzsklanda",
"X-KDE-Keywords[nl]": "video, monitor, grafisch, tijdslimiet, onderbreken, vergrendelen, schermvergrendelaar, schermbeveiliging",
"X-KDE-Keywords[nn]": "video, skjerm, grafikk, tidsavbrot, timeout, sove, lås, låsing, skjermlåsar, pauseskjerm, skjermsparar",
"X-KDE-Keywords[pa]": "ਵੀਡੀਓ, ਮਾਨੀਟਰ, ਗਰਾਫਿਕਸ, ਟਾਈਮ ਆਉਟ, ਸਲੀਪ, ਲਾਕ, ਸਕਰੀਨ-ਲਾਕਰ, ਸਕਰੀਨ-ਸੇਵਰ",
"X-KDE-Keywords[pl]": "video, monitor, grafika, timeout, uśpij, zablokuj, blokada ekranu, wygaszacz ekranu",
"X-KDE-Keywords[pt]": "vídeo, monitor, gráfica, tempo-limite, suspender, bloquear, bloqueio de ecrã, protector de ecrã",
"X-KDE-Keywords[pt_BR]": "vídeo, monitor, gráfico, tempo limite, suspender, bloquear, bloqueio de tela, protetor de tela",
"X-KDE-Keywords[ro]": "video, monitor, grafică, expirare, somn, adormire, blocare, blocare ecran, protecție ecran",
"X-KDE-Keywords[ru]": "video, monitor, graphics, timeout, sleep, lock, screenlocker, screensaver, видео, монитор, экран, графика, блокировка",
"X-KDE-Keywords[sk]": "video, obrazovka, grafika, prestávka, spánok, zámok, zámok obrazovky, šetrič obrazovky",
"X-KDE-Keywords[sl]": "video, zaslon, grafika, časovna omejitev, spanje, zaklep, zaklep zaslona, ohranjevalnik zaslona",
"X-KDE-Keywords[sv]": "video, bildskärm, grafik, tidsgräns, viloläge, låsning, skärmlåsning, skärmsläckare",
"X-KDE-Keywords[tr]": "video, monitör, grafikler, zaman aşımı, uyku, kilit, ekran kilitleyici, ekran koruyucu",
"X-KDE-Keywords[uk]": "video, monitor, graphics, timeout, sleep, lock, screenlocker, screensaver, відео, монітор, графіка, час очікування, присипляння, блокування, збереження, екран",
"X-KDE-Keywords[vi]": "video,monitor,graphics,timeout,sleep,lock,screenlocker,screensaver,phim,màn hình,đồ hoạ,tắt màn hình,ngủ,khoá,khoá màn hình,bảo vệ màn hình",
"X-KDE-Keywords[x-test]": "xxvideoxx,xx monitorxx,xx graphicsxx,xx timeoutxx,xx sleepxx,xx lockxx,xx screenlockerxx,xx screensaverxx",
"X-KDE-Keywords[zh_CN]": "video, monitor, graphics, timeout, sleep, lock, screenlocker, screensaver, 视频, 显示器, 监视器, 图形, 图像, 超时, 睡眠, 锁屏, 屏幕保护, 屏幕锁定, 屏保, 锁定",
"X-KDE-Keywords[zh_TW]": "video, monitor, graphics, timeout, sleep, lock, screenlocker, screensaver, 影片, 顯示器, 圖形, 顯示卡, 逾時, 休眠, 鎖定, 螢幕鎖定, 螢幕保護程式",
"X-KDE-System-Settings-Parent-Category": "display",
"X-KDE-Weight": 70
}

View file

@ -0,0 +1,188 @@
/*
* SPDX-FileCopyrightText: 2015 David Edmundson <david@davidedmundson.co.uk>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include "statisticsprovider.h"
#include <QDBusArgument>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#include <QDBusMetaType> // qDBusRegisterMetaType
#include <QDBusPendingReply>
#include <QDebug>
const QDBusArgument &operator<<(QDBusArgument &argument, const HistoryReply &data)
{
argument.beginStructure();
argument << data.time << data.value << data.charging;
argument.endStructure();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &arg, HistoryReply &attrs)
{
arg.beginStructure();
arg >> attrs.time >> attrs.value >> attrs.charging;
arg.endStructure();
return arg;
}
StatisticsProvider::StatisticsProvider(QObject *parent)
: QObject(parent)
{
m_type = StatisticsProvider::ChargeType;
m_duration = 120;
qDBusRegisterMetaType<HistoryReply>();
qDBusRegisterMetaType<QList<HistoryReply>>();
}
void StatisticsProvider::setDevice(const QString &device)
{
if (device == m_device) {
return;
}
m_device = device;
Q_EMIT deviceChanged();
load();
}
void StatisticsProvider::setDuration(uint duration)
{
if (duration == m_duration) {
return;
}
m_duration = duration;
Q_EMIT durationChanged();
load();
}
void StatisticsProvider::setType(StatisticsProvider::HistoryType type)
{
if (m_type == type) {
return;
}
m_type = type;
Q_EMIT typeChanged();
load();
}
void StatisticsProvider::classBegin()
{
}
void StatisticsProvider::componentComplete()
{
m_isComplete = true;
load();
}
QVariantList StatisticsProvider::asPoints() const
{
QVariantList points;
points.reserve(m_values.count());
foreach (const HistoryReply &h, m_values) {
points.append(QPointF(h.time, h.value));
}
if (!points.isEmpty()) {
points.takeLast();
}
return points;
}
int StatisticsProvider::count() const
{
return m_values.count();
}
int StatisticsProvider::firstDataPointTime() const
{
if (m_values.isEmpty()) {
return 0;
}
return m_values.first().time;
}
int StatisticsProvider::lastDataPointTime() const
{
if (m_values.isEmpty()) {
return 0;
}
return m_values.last().time;
}
int StatisticsProvider::largestValue() const
{
if (m_values.isEmpty()) {
return 0;
}
int max = 0; // TODO std::max or something?
for (auto it = m_values.constBegin(), end = m_values.constEnd(); it != end; ++it) {
if ((*it).value > max) {
max = (*it).value;
}
}
return max;
}
void StatisticsProvider::refresh()
{
load();
}
void StatisticsProvider::load()
{
if (!m_isComplete || m_device.isEmpty()) {
return;
}
auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.UPower"),
m_device,
QStringLiteral("org.freedesktop.UPower.Device"),
QStringLiteral("GetHistory"));
if (m_type == RateType) {
msg << QLatin1String("rate");
} else { // m_type must = ChargeType
msg << QLatin1String("charge");
}
uint resolution = 100;
msg << m_duration << resolution;
QDBusPendingReply<QList<HistoryReply>> reply = QDBusConnection::systemBus().asyncCall(msg);
auto *watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<QList<HistoryReply>> reply = *watcher;
watcher->deleteLater();
m_values.clear();
if (reply.isError()) {
qWarning() << "Failed to get device history from UPower" << reply.error().message();
return;
}
foreach (const HistoryReply &r, reply.value()) {
if (r.value > 0) { // we get back some values which contain no value, possibly to indicate if charging changes, ignore them
m_values.prepend(r);
}
}
Q_EMIT dataChanged();
});
}

View file

@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2015 David Edmundson <david@davidedmundson.co.uk>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#ifndef STATISTICSPROVIDER_H
#define STATISTICSPROVIDER_H
#include <QObject>
#include <QPointF>
#include <QQmlParserStatus>
struct HistoryReply {
public:
uint time = 0;
double value = 0.0;
uint charging = 0;
};
Q_DECLARE_METATYPE(HistoryReply)
class StatisticsProvider : public QObject, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QString device MEMBER m_device WRITE setDevice NOTIFY deviceChanged)
Q_PROPERTY(uint duration MEMBER m_duration WRITE setDuration NOTIFY durationChanged)
Q_PROPERTY(HistoryType type MEMBER m_type WRITE setType NOTIFY typeChanged)
Q_PROPERTY(QVariantList points READ asPoints NOTIFY dataChanged)
Q_PROPERTY(int count READ count NOTIFY dataChanged)
Q_PROPERTY(int firstDataPointTime READ firstDataPointTime NOTIFY dataChanged)
Q_PROPERTY(int lastDataPointTime READ lastDataPointTime NOTIFY dataChanged)
Q_PROPERTY(int largestValue READ largestValue NOTIFY dataChanged)
public:
enum HistoryType {
RateType,
ChargeType,
};
Q_ENUM(HistoryType)
enum HistoryRoles {
TimeRole = Qt::UserRole + 1,
ValueRole,
ChargingRole,
};
explicit StatisticsProvider(QObject *parent = nullptr);
void setDevice(const QString &device);
void setDuration(uint duration);
void setType(HistoryType type);
void load();
void classBegin() override;
void componentComplete() override;
QVariantList asPoints() const;
int count() const;
int firstDataPointTime() const;
int lastDataPointTime() const;
int largestValue() const;
Q_SIGNALS:
void deviceChanged();
void typeChanged();
void durationChanged();
void dataChanged();
public Q_SLOTS:
void refresh();
private:
QString m_device;
HistoryType m_type;
uint m_duration; // in seconds
QList<HistoryReply> m_values;
bool m_isComplete = false;
};
#endif // STATISTICSPROVIDER_H

23
kcms/time/CMakeLists.txt Normal file
View file

@ -0,0 +1,23 @@
set(timesettings_SRCS
timesettings.cpp
timezonemodel.cpp
timezonesi18n.cpp
)
qt_add_dbus_interface(timesettings_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/timedated1.xml timedated_interface)
add_library(kcm_mobile_time MODULE ${timesettings_SRCS})
target_link_libraries(kcm_mobile_time
Qt::Quick
Qt::Qml
Qt::DBus
KF6::QuickAddons
KF6::ConfigCore
KF6::I18n
)
# Time and Date
install(TARGETS kcm_mobile_time DESTINATION ${KDE_INSTALL_QTPLUGINDIR}/kcms)
kpackage_install_package(package kcm_mobile_time kcms)

1
kcms/time/Messages.sh Normal file
View file

@ -0,0 +1 @@
$XGETTEXT $(find . -name \*.cpp -o -name \*.h -o -name \*.qml) -o $podir/kcm_mobile_time.pot

View file

@ -0,0 +1,128 @@
[Domain]
Name=Date and Time Control Module
Name[az]=Tarix və Vaxta Nəzarət Modulu
Name[ca]=Mòdul de control de la data i hora
Name[ca@valencia]=Mòdul de control de la data i hora
Name[cs]=Ovládací modul data a času
Name[da]=Dato og klokkeslæt-kontrolmodul
Name[de]=Kontrollmodul für Datum und Zeit
Name[el]=Άρθρωμα ελέγχου ημερομηνίας και ώρας
Name[en_GB]=Date and Time Control Module
Name[es]=Módulo de control de fecha y hora
Name[et]=Kuupäeva ja kellaaja juhtimismoodul
Name[eu]=Data eta ordua aginte modulua
Name[fi]=Aika-asetukset
Name[fr]=Module de contrôle de date et d'heure
Name[gl]=Módulo de control da data e hora
Name[hu]=Dátum és idő beállítómodul
Name[ia]=Modulo de controlo de data e tempore
Name[id]=Modul Kontrol Waktu dan Tanggal
Name[is]=Stjórneining fyrir dagsetningu og tíma
Name[it]=Modulo di controllo Data e ora
Name[ka]=თარიღისა და დროის კონტროლის მოდული
Name[ko]=날짜와 시간 제어 모듈
Name[lt]=Datos ir laiko valdymo modulis
Name[nl]=Controlemodule van datum en tijd
Name[nn]=Kontrollmodul for dato og klokkeslett
Name[pa]=ਮਿਤੀ ਅਤੇ ਟਾਈਮ ਕੰਟਰੋਲ ਮੋਡੀਊਲ
Name[pl]=Moduł sterowania datą i czasem
Name[pt]=Módulo de Controlo da Data e Hora
Name[pt_BR]=Módulo de controle da data e hora
Name[ro]=Modul de control pentru dată și oră
Name[ru]=Модуль настройки даты и времени
Name[sk]=Modul pre nastavenie dátumu a času
Name[sl]=Nadzorni modul za datum in čas
Name[sv]=Inställningsmodul för datum och tid
Name[ta]=தேதி மற்றும் நேரத்தை கட்டுப்படுத்தும் கூறு
Name[tr]=Tarih ve Zaman Denetim Modülü
Name[uk]=Модуль керування датою і часом
Name[vi]=Khối điều khiển ngày giờ
Name[x-test]=xxDate and Time Control Modulexx
Name[zh_CN]=日期和时间控制模块
Name[zh_TW]=日期與時間控制模組
Icon=preferences-system-time
[org.kde.active.clockconfig.save]
Name=Save the date/time settings
Name[az]=Tarix/Vaxt ayarlarını saxlamaq
Name[ca]=Desa la configuració de la data i hora
Name[ca@valencia]=Guarda la configuració de la data i hora
Name[cs]=Uložit nastavení data a času
Name[da]=Gem dato-/klokkeslætsindstillingerne
Name[de]=Einstellungen für Datum und Zeit speichern
Name[el]=Αποθήκευση των ρυθμίσεων ημερομηνίας/ώρας
Name[en_GB]=Save the date/time settings
Name[es]=Guardar las preferencias de fecha y hora
Name[et]=Kuupäeva/kellaaja seadistuste salvestamine
Name[eu]=Gorde data/ordu ezarpenak
Name[fi]=Tallenna päiväyksen ja ajan asetukset
Name[fr]=Enregistrement des paramètres de date / d'heure
Name[gl]=Garda a configuración da data e hora
Name[hu]=A dátum- és időbeállítások mentése
Name[ia]=Salveguarda le preferentias de data/ tempore
Name[id]=Simpan pengaturan waktu/tanggal
Name[is]=Vista stillingar dagsetningar og tíma
Name[it]=Salva le impostazioni di data e ora
Name[ka]=თარიღის/დროის პარამეტრების შენახვა
Name[ko]=날짜와 시간 설정 저장
Name[lt]=Įrašyti datos/laiko nuostatas
Name[nl]=Datum- en tijdinstellingen opslaan
Name[nn]=Lagra innstillingane for dato og klokkeslett
Name[pa]=ਮਿਤੀ/ਸਮਾਂ ਸੈਟਿੰਗਾਂ ਸੰਭਾਲੋ
Name[pl]=Zapisz ustawienia daty/czasu
Name[pt]=Mudar a configuração da data/hora
Name[pt_BR]=Salvar as configurações de data e hora
Name[ro]=Salvează configurările datei și orei
Name[ru]=Сохранить параметры даты и времени
Name[sk]=Uložiť nastavenia dátumu a času
Name[sl]=Shranjevanje nastavitev datuma in časa
Name[sv]=Spara inställningar av datum och tid
Name[ta]=தேதி மற்றும் நேர அமைப்புகளை சேமியுங்கள்
Name[tr]=Tarih/zaman ayarlarını kaydet
Name[uk]=Зберегти параметри дати/часу
Name[vi]=Lưu thiết lập ngày/giờ
Name[x-test]=xxSave the date/time settingsxx
Name[zh_CN]=保存日期/时间设置
Name[zh_TW]=儲存日期與時間設定
Description=System policies prevent you from saving the date/time settings.
Description[az]=Təhlükəsizlik qaydaları sizə tarix və vaxtı idarə etməyə mane olur.
Description[ca]=Les polítiques del sistema impedeixen que deseu la configuració de la data i hora.
Description[ca@valencia]=Les polítiques del sistema impedixen que guardeu la configuració de la data i hora.
Description[cs]=Nastavení systému vám znemožňuje uložit datum/čas.
Description[da]=Systempolitikker forhindrer dig i at gemme dato-/klokkeslætsindstillinger.
Description[de]=Die Einstellungen für Datum und Zeit können aufgrund einer Systemrichtlinie nicht gespeichert werden.
Description[el]=Οι πολιτικές του συστήματος σάς εμποδίζουν να αποθηκεύσετε τις ρυθμίσεις ημερομηνίας/ώρας.
Description[en_GB]=System policies prevent you from saving the date/time settings.
Description[es]=La política del sistema le impide que pueda guardar las preferencias de fecha y hora.
Description[et]=Süsteemi reeglid takistavad sul kuupäeva/kellaaja seadistusi salvestamast.
Description[eu]=Sistemako gidalerroek data/ordu ezarpenak gordetzea eragozten dizute.
Description[fi]=Järjestelmäkäytäntö estää sinua tallentamasta aika-asetuksia.
Description[fr]=Les stratégies système vous empêchent d'enregistrer les paramètres de date / d'heure.
Description[gl]=As políticas do sistema non permiten que garde a configuración da data e hora.
Description[hu]=A rendszer házirendjei nem engedik Önnek a dátum- és időbeállítások mentését.
Description[ia]=Le politicas de systema preveni te ex salveguardar le preferentias de tempore/data.
Description[id]=Kebijakan sistem menghalangi anda untuk menyimpan pengaturan waktu/tanggal.
Description[is]=Öryggisreglur kerfisins koma í veg fyrir að þú getir vistað stillingar fyrir tíma/dagsetningu.
Description[it]=Le politiche di sistema ti impediscono di salvare le impostazioni di data e ora.
Description[ka]=სისტემის წესები გიკრძალავთ დროის/თარიღის პარამეტრების შენახვას.
Description[ko]=시스템 정책 때문에 날짜와 시간 설정을 저장할 수 없습니다.
Description[lt]=Sistemos politika neleidžia jums įrašyti datos/laiko nustatymų.
Description[nl]=Systeembeleid voorkwam dat u de datum- en tijdinstellingen kon opslaan.
Description[nn]=Systemreglane hindrar deg i å lagra innstillingane for dato og klokkeslett.
Description[pa]=ਸਿਸਟਮ ਪਾਲਸੀ ਤੁਹਾਨੂੰ ਮਿਤੀ/ਸਮਾਂ ਸੈਟਿੰਗ ਬਦਲਣ ਤੋਂ ਤੁਹਾਨੂੰ ਰੋਕਦੀ ਹੈ।
Description[pl]=Polityka systemu nie pozwala ci na zapisanie ustawień daty/czasu.
Description[pt]=As políticas do sistema proíbem a modificação da configuração de data/hora.
Description[pt_BR]=As políticas do sistema não permitem a modificação das configurações de data e hora.
Description[ro]=Politicile sistemului vă interzic salvarea configurărilor de dată și oră.
Description[ru]=Правила безопасности запрещают вам управлять системной датой и временем.
Description[sk]=Systémové politiky vám zabránili uložiť nastavenie dátumu a času.
Description[sl]=Sistemski pravilniki vam onemogočajo, da bi shranili nastavitve datuma in časa.
Description[sv]=Systemets policy förhindrar att du sparar inställningar av datum och tid.
Description[ta]=தேதி மற்றும் நேர அமைப்புகளை நீங்கள் சேமிப்பதை கணினியின் கொள்கைகள் தடுக்கின்றன.
Description[tr]=Sistem politikaları, tarih/saat ayarlarını kaydetmenizi engeller.
Description[uk]=Відповідно до загальносистемних правил, ви не можете зберігати параметри дати/часу.
Description[vi]=Các chính sách của hệ thống không cho phép bạn lưu thiết lập ngày/giờ.
Description[x-test]=xxSystem policies prevent you from saving the date/time settings.xx
Description[zh_CN]=系统安全策略不允许您保存日期/时间设置。
Description[zh_TW]=系統政策拒絕讓您儲存日期與時間設定。
Policy=yes

9
kcms/time/config.h.cmake Normal file
View file

@ -0,0 +1,9 @@
/*
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
/* KDE's configuration directory */
#define KDE_CONFDIR "${CONFIG_INSTALL_DIR}"

View file

@ -0,0 +1,160 @@
/*
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.1
import org.kde.kirigami 2.5 as Kirigami
//import "private"
//FIXME: shouldn't be a FrameSvgItem
Item {
id: root
clip: true
//////// API
property int day
property int month
property int year
property bool userConfiguring: false
property string isoDate: year + "-" + clockRow.twoDigitString(month) + "-" + clockRow.twoDigitString(day)
property int fontSize: 14
property int _margin: Kirigami.Units.gridUnit
opacity: enabled ? 1.0 : 0.5
Rectangle {
color: "transparent"
border.width: 1
border.color: Kirigami.Theme.textColor
anchors.fill: parent
opacity: 0.3
}
/////// Implementation
Connections {
target: root
onDayChanged: clockRow.day = root.day
onMonthChanged: clockRow.month = root.month
onYearChanged: clockRow.year = root.year
}
//imagePath: "widgets/picker"
width: clockRow.width + root._margin * 2
height: clockRow.height + root._margin * 2
Timer {
id: userConfiguringTimer
repeat: false
interval: 1500
running: false
onTriggered: {
root.day = clockRow.day
root.month = clockRow.month
root.year = clockRow.year
userConfiguring = false
}
}
Row {
id: clockRow
spacing: 3
x: root._margin
y: root._margin
property int day
property int month
property int year
function twoDigitString(number)
{
return number < 10 ? "0"+number : number
}
Digit {
id: dayDigit
model: {
var dd = new Date(year, month, 0);
return dd.getDate()
}
currentIndex: ((day - 1) < model) ? day-1 : 1
onSelectedIndexChanged: {
if (selectedIndex > -1) {
day = selectedIndex+1
}
}
delegate: Text {
horizontalAlignment: Text.AlignHCenter
width: dayDigit.width
property int ownIndex: index
text: index+1
color: Kirigami.Theme.textColor
font.pointSize: root.fontSize
opacity: PathView.itemOpacity
}
}
Kirigami.Separator {
anchors {
top: parent.top
bottom: parent.bottom
}
}
Digit {
id: monthDigit
model: 12
currentIndex: month -1
onSelectedIndexChanged: {
if (selectedIndex > -1) {
month = selectedIndex + 1
}
}
delegate: Text {
horizontalAlignment: Text.AlignHCenter
width: monthDigit.width
property int ownIndex: index
property variant months: Array(i18n("Jan"), i18n("Feb"), i18n("Mar"), i18n("Apr"), i18n("May"), i18n("Jun"), i18n("Jul"), i18n("Aug"), i18n("Sep"), i18n("Oct"), i18n("Nov"), i18n("Dec"))
text: months[index]
font.pointSize: root.fontSize
color: Kirigami.Theme.textColor
opacity: PathView.itemOpacity
}
width: monthPlaceHolder.width
Text {
id: monthPlaceHolder
visible: false
font.pointSize: root.fontSize
text: "0000"
}
}
Kirigami.Separator {
anchors {
top: parent.top
bottom: parent.bottom
}
}
Digit {
id: yearDigit
//FIXME: yes, this is a tad lame ;)
model: 3000
currentIndex: year
onSelectedIndexChanged: {
if (selectedIndex > -1) {
year = selectedIndex
}
}
width: yearPlaceHolder.width*1.3
Text {
id: yearPlaceHolder
visible: false
font.pointSize: root.fontSize
text: "0000"
}
}
}
}

View file

@ -0,0 +1,75 @@
/*
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.7
import org.kde.kirigami 2.4 as Kirigami
Item {
id: root
property alias model: spinnerView.model
property alias currentIndex: spinnerView.currentIndex
property alias delegate: spinnerView.delegate
property alias moving: spinnerView.moving
property int selectedIndex: -1
property int fontSize: 14
width: placeHolder.width*1.3
height: placeHolder.height*3
Text {
id: placeHolder
visible: false
font.pointSize: root.fontSize
text: "00"
}
PathView {
id: spinnerView
anchors.fill: parent
model: 60
clip: true
pathItemCount: 5
dragMargin: 800
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
delegate: Text {
horizontalAlignment: Text.AlignHCenter
width: spinnerView.width
property int ownIndex: index
text: index < 10 ? "0"+index : index
color: Kirigami.Theme.textColor
font.pointSize: root.fontSize
opacity: PathView.itemOpacity
}
onMovingChanged: {
userConfiguring = true
if (!moving) {
userConfiguringTimer.restart()
selectedIndex = childAt(width/2, height/2).ownIndex
}
}
path: Path {
startX: spinnerView.width/2
startY: spinnerView.height + 1.5*placeHolder.height
PathAttribute { name: "itemOpacity"; value: 0 }
PathLine {
x: spinnerView.width/2
y: spinnerView.height/2
}
PathAttribute { name: "itemOpacity"; value: 1 }
PathLine {
x: spinnerView.width/2
y: -1.5*placeHolder.height
}
PathAttribute { name: "itemOpacity"; value: 0 }
}
}
}

View file

@ -0,0 +1,216 @@
/*
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.1
import org.kde.kirigami 2.4 as Kirigami
//FIXME: shouldn't be a FrameSvgItem
Item {
id: root
clip: true
//////// API
property alias hours: clockRow.hours
property alias minutes: clockRow.minutes
property alias seconds: clockRow.seconds
property bool userConfiguring: false
property bool twentyFour: true
property int fontSize: 14
property int _margin: Kirigami.Units.gridUnit
property string timeString: clockRow.twoDigitString(hours) + ":" + clockRow.twoDigitString(minutes) + ":" + clockRow.twoDigitString(seconds)
opacity: enabled ? 1.0 : 0.5
Connections {
target: root
// onHoursChanged: print("H : " + root.hours)
// onMinutesChanged: print("M : " + root.minutes)
// onSecondsChanged: print("S : " + root.seconds)
}
Behavior on width {
SequentialAnimation {
PauseAnimation {
duration: 250
}
NumberAnimation {
//duration: PlasmaCore.Units.longDuration
duration: 250
easing.type: Easing.InOutQuad
}
}
}
// KLocale.Locale {
// id: locale
// }
//imagePath: "widgets/picker"
width: clockRow.width + root._margin
height: clockRow.height + root._margin * 2
Timer {
id: userConfiguringTimer
repeat: false
interval: 1500
running: false
onTriggered: {
root.hours = clockRow.hours
root.minutes = clockRow.minutes
root.seconds = clockRow.seconds
userConfiguring = false
}
}
Rectangle {
color: "transparent"
opacity: 0.3
border.color: Kirigami.Theme.textColor
border.width: 1
anchors.fill: parent
}
Row {
id: clockRow
spacing: Kirigami.Units.gridUnit
x: root._margin
y: root._margin
property int hours
property int minutes
property int seconds
function twoDigitString(number)
{
return number < 10 ? "0"+number : number
}
Digit {
id: hoursDigit
model: root.twentyFour ? 24 : 12
currentIndex: root.twentyFour || hours < 12 ? hours : hours - 12
delegate: Text {
horizontalAlignment: Text.AlignHCenter
width: hoursDigit.width
property int ownIndex: index
text: (!root.twentyFour && index == 0) ? "12" : clockRow.twoDigitString(index)
font.pointSize: root.fontSize
color: Kirigami.Theme.textColor
opacity: PathView.itemOpacity
}
onSelectedIndexChanged: {
print("Bah");
if (selectedIndex > -1) {
if (root.twentyFour ||
meridiaeDigit.isAm) {
hours = selectedIndex
} else {
hours = selectedIndex + 12
}
}
}
}
Kirigami.Separator {
anchors {
top: parent.top
bottom: parent.bottom
}
}
Digit {
id: minutesDigit
model: 60
currentIndex: minutes
onSelectedIndexChanged: {
if (selectedIndex > -1) {
minutes = selectedIndex
}
}
}
Kirigami.Separator {
anchors {
top: parent.top
bottom: parent.bottom
}
}
Digit {
id: secondsDigit
model: 60
currentIndex: seconds
onSelectedIndexChanged: {
if (selectedIndex > -1) {
seconds = selectedIndex
}
}
}
Kirigami.Separator {
opacity: meridiaeDigit.opacity == 0 ? 0 : 1
anchors {
top: parent.top
bottom: parent.bottom
}
Behavior on opacity {
NumberAnimation {
duration: 250
easing.type: Easing.InOutQuad
}
}
}
Digit {
id: meridiaeDigit
visible: opacity != 0
opacity: root.twentyFour ? 0 : 1
property bool isAm: (selectedIndex > -1) ? (selectedIndex < 1) : (currentIndex < 1)
model: ListModel {
ListElement {
meridiae: "AM"
}
ListElement {
meridiae: "PM"
}
}
delegate: Text {
width: meridiaeDigit.width
horizontalAlignment: Text.AlignLeft
property int ownIndex: index
text: meridiae
color: Kirigami.Theme.textColor
font.pointSize: root.fontSize
//opacity: PathView.itemOpacity
}
currentIndex: hours > 12 ? 1 : 0
onSelectedIndexChanged: {
if (selectedIndex > -1) {
//AM
if (selectedIndex == 0) {
hours -= 12
//PM
} else {
hours += 12
}
}
}
width: meridiaePlaceHolder.width + root._margin
Text {
id: meridiaePlaceHolder
visible: false
font.pointSize: root.fontSize
text: "00"
}
Behavior on opacity {
NumberAnimation {
duration: 250
easing.type: Easing.InOutQuad
}
}
}
}
}

View file

@ -0,0 +1,257 @@
// -*- coding: iso-8859-1 -*-
/*
* SPDX-FileCopyrightText: 2011 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3 as Controls
import org.kde.kirigami 2.10 as Kirigami
import org.kde.kcm 1.2
import org.kde.timesettings 1.0
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
SimpleKCM {
id: timeModule
leftPadding: 0
rightPadding: 0
topPadding: Kirigami.Units.gridUnit
bottomPadding: Kirigami.Units.gridUnit
Component {
id: listDelegateComponent
Kirigami.BasicListItem {
text: {
if (model) {
if (model.region) {
return "%1 / %2".arg(model.region).arg(model.city)
} else {
return model.city
}
}
return ""
}
onClicked: {
timeZonePickerSheet.close()
kcm.saveTimeZone(model.timeZoneId)
}
}
}
ColumnLayout {
spacing: 0
width: parent.width
MobileForm.FormCard {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("Display")
}
MobileForm.FormSwitchDelegate {
id: hourFormatSwitch
text: i18n("24-Hour Format")
description: i18n("Whether to use a 24-hour format for clocks.")
checked: kcm.twentyFour
onCheckedChanged: {
kcm.twentyFour = checked
}
}
MobileForm.FormDelegateSeparator { above: hourFormatSwitch; below: timeZoneSelect }
MobileForm.FormButtonDelegate {
id: timeZoneSelect
text: i18n("Timezone")
description: kcm.timeZone
onClicked: timeZonePickerSheet.open()
}
}
}
MobileForm.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("Time and Date")
}
MobileForm.FormSwitchDelegate {
id: ntpCheckBox
text: i18n("Automatic Time Synchronization")
description: i18n("Whether to set the time automatically.")
checked: kcm.useNtp
onCheckedChanged: {
kcm.useNtp = checked
if (!checked) {
kcm.ntpServer = ""
kcm.saveTime()
}
}
}
MobileForm.FormDelegateSeparator { above: ntpCheckBox; below: timeSelect }
MobileForm.FormButtonDelegate {
id: timeSelect
enabled: !ntpCheckBox.checked
icon.name: "clock"
text: i18n("Current Time")
description: Qt.formatTime(kcm.currentTime, Locale.LongFormat)
onClicked: timePickerSheet.open()
}
MobileForm.FormDelegateSeparator { above: timeSelect; below: dateSelect }
MobileForm.FormButtonDelegate {
id: dateSelect
enabled: !ntpCheckBox.checked
icon.name: "view-calendar"
text: i18n("Date")
description: Qt.formatDate(kcm.currentDate, Locale.LongFormat)
onClicked: datePickerSheet.open()
}
}
}
}
Kirigami.OverlaySheet {
id: timeZonePickerSheet
header: ColumnLayout {
Kirigami.Heading {
text: i18nc("@title:window", "Pick Timezone")
}
Kirigami.SearchField {
Layout.fillWidth: true
width: parent.width
onTextChanged: {
kcm.timeZonesModel.filterString = text
}
}
}
footer: RowLayout {
Controls.Button {
Layout.alignment: Qt.AlignHCenter
text: i18nc("@action:button", "Close")
onClicked: timeZonePickerSheet.close()
}
}
ListView {
clip: true
anchors.fill: parent
implicitWidth: 18 * Kirigami.Units.gridUnit
model: kcm.timeZonesModel
delegate: Kirigami.DelegateRecycler {
width: parent.width
sourceComponent: listDelegateComponent
}
}
}
Kirigami.OverlaySheet {
id: timePickerSheet
header: Kirigami.Heading { text: i18n("Pick Time") }
TimePicker {
id: timePicker
enabled: !ntpCheckBox.checked
twentyFour: twentyFourSwitch.checked
implicitWidth: width > Kirigami.Units.gridUnit * 15 ? width : Kirigami.Units.gridUnit * 15
Component.onCompleted: {
var date = new Date(kcm.currentTime);
timePicker.hours = date.getHours();
timePicker.minutes = date.getMinutes();
timePicker.seconds = date.getSeconds();
}
Connections {
target: kcm
onCurrentTimeChanged: {
if (timePicker.userConfiguring) {
return;
}
var date = new Date(kcm.currentTime);
timePicker.hours = date.getHours();
timePicker.minutes = date.getMinutes();
timePicker.seconds = date.getSeconds();
}
}
onUserConfiguringChanged: {
kcm.currentTime = timeString
kcm.saveTime()
}
}
footer: RowLayout {
Controls.Button {
Layout.alignment: Qt.AlignRight
text: i18nc("@action:button", "Close")
onClicked: timePickerSheet.close()
}
}
}
Kirigami.OverlaySheet {
id: datePickerSheet
header: Kirigami.Heading { text: i18n("Pick Date") }
DatePicker {
id: datePicker
enabled: !ntpCheckBox.checked
implicitWidth: width > Kirigami.Units.gridUnit * 15 ? width : Kirigami.Units.gridUnit * 15
Component.onCompleted: {
var date = new Date(kcm.currentDate)
datePicker.day = date.getDate()
datePicker.month = date.getMonth()+1
datePicker.year = date.getFullYear()
}
Connections {
target: kcm
onCurrentDateChanged: {
if (datePicker.userConfiguring) {
return
}
var date = new Date(kcm.currentDate)
datePicker.day = date.getDate()
datePicker.month = date.getMonth()+1
datePicker.year = date.getFullYear()
}
}
onUserConfiguringChanged: {
kcm.currentDate = isoDate
kcm.saveTime()
}
}
footer: RowLayout {
Controls.Button {
Layout.alignment: Qt.AlignRight
text: i18nc("@action:button", "Close")
onClicked: datePickerSheet.close()
}
}
}
}

43
kcms/time/timedated1.xml Normal file
View file

@ -0,0 +1,43 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.timedate1">
<property name="Timezone" type="s" access="read">
</property>
<property name="LocalRTC" type="b" access="read">
</property>
<property name="CanNTP" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
</property>
<property name="NTP" type="b" access="read">
</property>
<property name="NTPSynchronized" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
</property>
<property name="TimeUSec" type="t" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
</property>
<property name="RTCTimeUSec" type="t" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
</property>
<method name="SetTime">
<arg type="x" direction="in"/>
<arg type="b" direction="in"/>
<arg type="b" direction="in"/>
</method>
<method name="SetTimezone">
<arg type="s" direction="in"/>
<arg type="b" direction="in"/>
</method>
<method name="SetLocalRTC">
<arg type="b" direction="in"/>
<arg type="b" direction="in"/>
<arg type="b" direction="in"/>
</method>
<method name="SetNTP">
<arg type="b" direction="in"/>
<arg type="b" direction="in"/>
</method>
</interface>
</node>

278
kcms/time/timesettings.cpp Normal file
View file

@ -0,0 +1,278 @@
/*
SPDX-FileCopyrightText: 2005 S.R.Haque <srhaque@iee.org>.
SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2011-2015 Sebastian Kügler <sebas@kde.org>
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "timesettings.h"
#include "timezonemodel.h"
#include <QDebug>
#include <QtCore/QDate>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QStandardItemModel>
#include <QTimer>
#include <QVariant>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KSharedConfig>
#include <utility>
#include "timedated_interface.h"
#define FORMAT24H "HH:mm:ss"
#define FORMAT12H "h:mm:ss ap"
K_PLUGIN_FACTORY_WITH_JSON(TimeSettingsFactory, "timesettings.json", registerPlugin<TimeSettings>();)
TimeSettings::TimeSettings(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, metaData, args)
, m_useNtp(true)
{
qDebug() << "time settings init";
m_timeZonesModel = nullptr;
setTimeZone(QTimeZone::systemTimeZone().id());
setButtons(Apply | Default);
qmlRegisterAnonymousType<TimeZoneModel>("org.kde.timesettings", 1);
qmlRegisterAnonymousType<TimeZoneFilterProxy>("org.kde.timesettings", 1);
initSettings();
initTimeZones();
qDebug() << "TimeSettings module loaded.";
}
TimeSettings::~TimeSettings()
{
}
void TimeSettings::initTimeZones()
{
auto *filterModel = new TimeZoneFilterProxy(this);
filterModel->setSourceModel(new TimeZoneModel(filterModel));
setTimeZonesModel(filterModel);
}
void TimeSettings::initSettings()
{
m_localeConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig);
m_localeSettings = KConfigGroup(m_localeConfig, "Locale");
setTimeFormat(m_localeSettings.readEntry("TimeFormat", QStringLiteral(FORMAT24H))); // FIXME?!
OrgFreedesktopTimedate1Interface timeDatedIface(QStringLiteral("org.freedesktop.timedate1"),
QStringLiteral("/org/freedesktop/timedate1"),
QDBusConnection::systemBus());
// the server list is not relevant for timesyncd, it fetches it from the network
m_useNtp = timeDatedIface.nTP();
}
void TimeSettings::timeout()
{
setCurrentTime(QTime::currentTime());
setCurrentDate(QDate::currentDate());
notify();
}
QString TimeSettings::currentTimeText()
{
return m_currentTimeText;
}
QTime TimeSettings::currentTime() const
{
return m_currentTime;
}
void TimeSettings::setCurrentTime(const QTime &currentTime)
{
if (m_currentTime != currentTime) {
m_currentTime = currentTime;
m_currentTimeText = QLocale().toString(QTime::currentTime(), m_timeFormat);
emit currentTimeChanged();
}
}
QDate TimeSettings::currentDate() const
{
return m_currentDate;
}
void TimeSettings::setCurrentDate(const QDate &currentDate)
{
if (m_currentDate != currentDate) {
m_currentDate = currentDate;
emit currentDateChanged();
}
}
bool TimeSettings::useNtp() const
{
return m_useNtp;
}
void TimeSettings::setUseNtp(bool ntp)
{
if (m_useNtp != ntp) {
m_useNtp = ntp;
saveTime();
emit useNtpChanged();
}
}
bool TimeSettings::saveTime()
{
OrgFreedesktopTimedate1Interface timedateIface(QStringLiteral("org.freedesktop.timedate1"),
QStringLiteral("/org/freedesktop/timedate1"),
QDBusConnection::systemBus());
bool rc = true;
// final arg in each method is "user-interaction" i.e whether it's OK for polkit to ask for auth
// we cannot send requests up front then block for all replies as we need NTP to be disabled before we can make a call to SetTime
// timedated processes these in parallel and will return an error otherwise
auto reply = timedateIface.SetNTP(m_useNtp, true);
reply.waitForFinished();
if (reply.isError()) {
m_errorString = i18n("Unable to change NTP settings");
emit errorStringChanged();
qWarning() << "Failed to enable NTP" << reply.error().name() << reply.error().message();
rc = false;
}
if (!useNtp()) {
QDateTime userTime;
userTime.setTime(currentTime());
userTime.setDate(currentDate());
qDebug() << "Setting userTime: " << userTime;
qint64 timeDiff = userTime.toMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch();
//*1000 for milliseconds -> microseconds
auto reply = timedateIface.SetTime(timeDiff * 1000, true, true);
reply.waitForFinished();
if (reply.isError()) {
m_errorString = i18n("Unable to set current time");
emit errorStringChanged();
qWarning() << "Failed to set current time" << reply.error().name() << reply.error().message();
rc = false;
}
}
saveTimeZone(m_timezone);
return rc;
}
void TimeSettings::saveTimeZone(const QString &newtimezone)
{
qDebug() << "Saving timezone to config: " << newtimezone;
OrgFreedesktopTimedate1Interface timedateIface(QStringLiteral("org.freedesktop.timedate1"),
QStringLiteral("/org/freedesktop/timedate1"),
QDBusConnection::systemBus());
if (!newtimezone.isEmpty()) {
qDebug() << "Setting timezone: " << newtimezone;
auto reply = timedateIface.SetTimezone(newtimezone, true);
reply.waitForFinished();
if (reply.isError()) {
m_errorString = i18n("Unable to set timezone");
emit errorStringChanged();
qWarning() << "Failed to set timezone" << reply.error().name() << reply.error().message();
}
}
setTimeZone(newtimezone);
emit timeZoneChanged();
notify();
}
QString TimeSettings::timeFormat()
{
return m_timeFormat;
}
void TimeSettings::setTimeFormat(const QString &timeFormat)
{
if (m_timeFormat != timeFormat) {
m_timeFormat = timeFormat;
m_localeSettings.writeEntry("TimeFormat", timeFormat, KConfigGroup::Notify);
m_localeConfig->sync();
QDBusMessage msg =
QDBusMessage::createSignal(QStringLiteral("/org/kde/kcmshell_clock"), QStringLiteral("org.kde.kcmshell_clock"), QStringLiteral("clockUpdated"));
QDBusConnection::sessionBus().send(msg);
qDebug() << "time format is now: " << QLocale().toString(QTime::currentTime(), m_timeFormat);
emit timeFormatChanged();
timeout();
}
}
QString TimeSettings::timeZone()
{
return m_timezone;
}
void TimeSettings::setTimeZone(const QString &timezone)
{
if (m_timezone != timezone) {
m_timezone = timezone;
qDebug() << "timezone changed to: " << timezone;
emit timeZoneChanged();
timeout();
}
}
TimeZoneFilterProxy *TimeSettings::timeZonesModel()
{
return m_timeZonesModel;
}
void TimeSettings::setTimeZonesModel(TimeZoneFilterProxy *timezones)
{
m_timeZonesModel = timezones;
emit timeZonesModelChanged();
}
bool TimeSettings::twentyFour()
{
return timeFormat() == QStringLiteral(FORMAT24H);
}
void TimeSettings::setTwentyFour(bool t)
{
if (twentyFour() != t) {
if (t) {
setTimeFormat(FORMAT24H);
} else {
setTimeFormat(FORMAT12H);
}
qDebug() << "T24 toggled: " << t << m_timeFormat;
emit twentyFourChanged();
emit currentTimeChanged();
timeout();
}
}
QString TimeSettings::errorString()
{
return m_errorString;
}
void TimeSettings::notify()
{
const QDBusMessage msg =
QDBusMessage::createSignal(QStringLiteral("/org/kde/kcmshell_clock"), QStringLiteral("org.kde.kcmshell_clock"), QStringLiteral("clockUpdated"));
QDBusConnection::sessionBus().send(msg);
}
#include "timesettings.moc"

120
kcms/time/timesettings.h Normal file
View file

@ -0,0 +1,120 @@
/*
SPDX-FileCopyrightText: 2011-2015 Sebastian Kügler <sebas@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef TIMESETTINGS_H
#define TIMESETTINGS_H
#include <QDate>
#include <QIcon>
#include <QObject>
#include <QStringListModel>
#include <QTime>
#include <QVariant>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KQuickAddons/ConfigModule>
#include "timezonemodel.h"
// #include "settingsmodule.h"
class TimeSettingsPrivate;
/**
* @class TimeSettings A class to manage time and date related settings. This class serves two functions:
* - Provide a plugin implementation
* - Provide a settings module
* This is done from one class in order to simplify the code. You can export any QObject-based
* class through qmlRegisterType(), however.
*/
class TimeSettings : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(QString timeFormat READ timeFormat WRITE setTimeFormat NOTIFY timeFormatChanged)
Q_PROPERTY(bool twentyFour READ twentyFour WRITE setTwentyFour NOTIFY twentyFourChanged)
Q_PROPERTY(QString timeZone READ timeZone WRITE setTimeZone NOTIFY timeZoneChanged)
Q_PROPERTY(TimeZoneFilterProxy *timeZonesModel READ timeZonesModel WRITE setTimeZonesModel NOTIFY timeZonesModelChanged)
Q_PROPERTY(QTime currentTime READ currentTime WRITE setCurrentTime NOTIFY currentTimeChanged)
Q_PROPERTY(QDate currentDate READ currentDate WRITE setCurrentDate NOTIFY currentDateChanged)
Q_PROPERTY(bool useNtp READ useNtp WRITE setUseNtp NOTIFY useNtpChanged)
Q_PROPERTY(QString currentTimeText READ currentTimeText NOTIFY currentTimeTextChanged)
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
public:
/**
* @name Settings Module Constructor
*
* @arg parent The parent object
* @arg list Arguments, currently unused
*/
TimeSettings(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args);
~TimeSettings() override;
QString currentTimeText();
QTime currentTime() const;
void setCurrentTime(const QTime &time);
QDate currentDate() const;
void setCurrentDate(const QDate &date);
bool useNtp() const;
void setUseNtp(bool ntp);
QString timeFormat();
QString timeZone();
TimeZoneFilterProxy *timeZonesModel();
bool twentyFour();
QString errorString();
public Q_SLOTS:
void setTimeZone(const QString &timezone);
void setTimeZonesModel(TimeZoneFilterProxy *timezones);
void setTimeFormat(const QString &timeFormat);
void setTwentyFour(bool t);
void timeout();
bool saveTime();
void notify();
Q_INVOKABLE void saveTimeZone(const QString &newtimezone);
Q_SIGNALS:
void currentTimeTextChanged();
void currentTimeChanged();
void currentDateChanged();
void twentyFourChanged();
void timeFormatChanged();
void timeZoneChanged();
void timeZonesChanged();
void timeZonesModelChanged();
void useNtpChanged();
void errorStringChanged();
protected:
QString findNtpUtility();
private:
QString m_timeFormat;
QString m_timezone;
TimeZoneFilterProxy *m_timeZonesModel;
QString m_timeZoneFilter;
QString m_currentTimeText;
QTime m_currentTime;
QDate m_currentDate;
bool m_useNtp;
QString m_errorString;
void initSettings();
void initTimeZones();
KSharedConfig::Ptr m_localeConfig;
KConfigGroup m_localeSettings;
};
#endif // TIMESETTINGS_H

128
kcms/time/timesettings.json Normal file
View file

@ -0,0 +1,128 @@
{
"Categories": "Qt;KDE;X-KDE-settings-system;",
"KPlugin": {
"Description": "Timezone, Date Display",
"Description[az]": "Saat Qurşağı, Tarix göstərişi",
"Description[ca@valencia]": "Zona horària, Mostra la data",
"Description[ca]": "Zona horària, Mostra la data",
"Description[cs]": "Zobrazení data a časového pásma",
"Description[de]": "Zeitzone, Datumsanzeige",
"Description[en_GB]": "Timezone, Date Display",
"Description[es]": "Zona horaria, visor de la fecha",
"Description[eu]": "Ordu-eremua, data azaltzea",
"Description[fi]": "Aikavyöhyke, Päivämääränäyttö",
"Description[fr]": "Fuseau horaire, affichage de la date",
"Description[hu]": "Időzóna, dátummegjelenítés",
"Description[ia]": "Fuso horari, Monstrator de Data",
"Description[id]": "Zona Waktu, Tampilan Tanggal",
"Description[is]": "Tímabelti, birting dagsetninga",
"Description[it]": "Fuso orario, visualizzazione della data",
"Description[ka]": "დროის სარტყელი, თარიღის ჩვენება",
"Description[ko]": "시간대, 날짜 표시",
"Description[lt]": "Laiko juosta, datos atvaizdavimas",
"Description[nl]": "Tijdzone, datumweergave",
"Description[nn]": "Tidssone, datovising",
"Description[pa]": "ਸਮਾਂ-ਖੇਤਰ, ਤਾਰੀਖ ਦਿਖਾਓ",
"Description[pl]": "Strefa czasowa, wyświetlanie daty",
"Description[pt]": "Fuso-Horário, Visualização das Datas",
"Description[pt_BR]": "Fuso horário, exibição de datas",
"Description[ro]": "Fus orar, afișare dată",
"Description[ru]": "Часовой пояс, формат вывода даты",
"Description[sl]": "Časovni pas, prikaz datuma",
"Description[sv]": "Tidszon, datumvisning",
"Description[tr]": "Zaman Dilimi, Tarih Gösterimi",
"Description[uk]": "Часовий пояс, показ дати",
"Description[vi]": "Hiển thị múi giờ, ngày",
"Description[x-test]": "xxTimezone, Date Displayxx",
"Description[zh_CN]": "时区、日期显示",
"Description[zh_TW]": "時區、日期顯示",
"FormFactors": [
"handset",
"tablet",
"mediacenter"
],
"Icon": "preferences-system-time",
"Name": "Time and Date",
"Name[az]": "Vaxt və Tarixi ayarlamaq",
"Name[ca@valencia]": "Hora i data",
"Name[ca]": "Hora i data",
"Name[cs]": "Datum a čas",
"Name[da]": "Klokkeslæt og dato",
"Name[de]": "Zeit und Datum",
"Name[el]": "Ώρα και ημερομηνία",
"Name[en_GB]": "Time and Date",
"Name[es]": "Fecha y hora",
"Name[et]": "Kellaaeg ja kuupäev",
"Name[eu]": "Ordua eta data",
"Name[fi]": "Aika ja päivämäärä",
"Name[fr]": "Heure et Date",
"Name[gl]": "Hora e data",
"Name[hu]": "Dátum és idő",
"Name[ia]": "Hora e data",
"Name[id]": "Waktu dan Tanggal",
"Name[is]": "Dagsetning og tími",
"Name[it]": "Ora e data",
"Name[ka]": "თარიღი და დრო",
"Name[ko]": "날짜와 시간",
"Name[lt]": "Laikas ir data",
"Name[nl]": "Datum en tijd",
"Name[nn]": "Dato og klokkeslett",
"Name[pa]": "ਸਮਾਂ ਅਤੇ ਮਿਤੀ",
"Name[pl]": "Czas i data",
"Name[pt]": "Hora e Data",
"Name[pt_BR]": "Data e hora",
"Name[ro]": "Oră și dată",
"Name[ru]": "Дата и время",
"Name[sk]": "Čas a dátum",
"Name[sl]": "Čas in datum",
"Name[sv]": "Tid och datum",
"Name[ta]": "நேரமும் தேதியும்",
"Name[tr]": "Zaman ve Tarih",
"Name[uk]": "Час і дата",
"Name[vi]": "Ngày và giờ",
"Name[x-test]": "xxTime and Datexx",
"Name[zh_CN]": "时间和日期",
"Name[zh_TW]": "日期與時間"
},
"X-KDE-Keywords": "theme, look, feel",
"X-KDE-Keywords[ast]": "estilu, aspeutu",
"X-KDE-Keywords[az]": "theme, look, feel,mövzu,xarici görünüş,hissetmə",
"X-KDE-Keywords[ca@valencia]": "tema, aspecte, comportament",
"X-KDE-Keywords[ca]": "tema, aspecte, comportament",
"X-KDE-Keywords[cs]": "motiv,vzhled,chování",
"X-KDE-Keywords[da]": "tema, udseende, fremtoning",
"X-KDE-Keywords[de]": "Design,Erscheinungsbild",
"X-KDE-Keywords[el]": "θέμα, όψη, αίσθηση",
"X-KDE-Keywords[en_GB]": "theme, look, feel",
"X-KDE-Keywords[es]": "tema, aspecto, visual",
"X-KDE-Keywords[et]": "teema, välimus",
"X-KDE-Keywords[eu]": "gaia,itxura,izaera",
"X-KDE-Keywords[fi]": "teema, ulkoasu, tuntuma",
"X-KDE-Keywords[fr]": "thème, apparence, comportement, graphique",
"X-KDE-Keywords[gl]": "tema, aparencia, estilo, comportamento",
"X-KDE-Keywords[hu]": "téma, kinézet, megjelenés",
"X-KDE-Keywords[ia]": "thema, semblantia, apparentia",
"X-KDE-Keywords[it]": "tema, aspetto",
"X-KDE-Keywords[ko]": "theme, look, feel, 테마, 모습과 느낌, 외형, 외관",
"X-KDE-Keywords[lt]": "apipavidalinimas, išvaizda, isvaizda, turinys",
"X-KDE-Keywords[nl]": "thema, uiterlijk, gedrag",
"X-KDE-Keywords[nn]": "tema, utsjånad, åtferd",
"X-KDE-Keywords[pa]": "ਥੀਮ, ਦਿੱਖ, ਰਵੱਈਆ",
"X-KDE-Keywords[pl]": "wystrój, wygląd, odczucia, wrażenia",
"X-KDE-Keywords[pt]": "tema, aparência, comportamento",
"X-KDE-Keywords[pt_BR]": "tema, visual, aparência",
"X-KDE-Keywords[ro]": "tematică, temă, aspect, comportament",
"X-KDE-Keywords[ru]": "theme,look,feel,тема,оформление,внешний вид,стиль",
"X-KDE-Keywords[sk]": "téma, vzhľad, nastavenie",
"X-KDE-Keywords[sl]": "tema, videz, občutek",
"X-KDE-Keywords[sv]": "tema, utseende, känsla",
"X-KDE-Keywords[ta]": "theme, look, feel, தோற்றம், தோற்றத்திட்டம், பார்வை, காட்சி, உணர்வு",
"X-KDE-Keywords[tr]": "tema, bak, hisset",
"X-KDE-Keywords[uk]": "theme,look,feel,тема,вигляд,поведінка",
"X-KDE-Keywords[vi]": "theme,look,feel,chủ đề,nhìn,cảm",
"X-KDE-Keywords[x-test]": "xxthemexx,xx lookxx,xx feelxx",
"X-KDE-Keywords[zh_CN]": "theme, look, feel, 主题, 外观, 视觉效果, 显示效果",
"X-KDE-Keywords[zh_TW]": "theme, look, feel, 主題, 外觀, 感覺",
"X-KDE-System-Settings-Parent-Category": "regionalsettings",
"X-KDE-Weight": 70
}

23
kcms/time/timezonedata.h Normal file
View file

@ -0,0 +1,23 @@
/*
SPDX-FileCopyrightText: 2014 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef TIMEZONEDATA_H
#define TIMEZONEDATA_H
#include <QString>
class TimeZoneData
{
public:
QString id;
QString region;
QString city;
QString comment;
bool checked;
int offsetFromUtc;
};
#endif // TIMEZONEDATA_H

217
kcms/time/timezonemodel.cpp Normal file
View file

@ -0,0 +1,217 @@
/*
SPDX-FileCopyrightText: 2014 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "timezonemodel.h"
#include "timezonesi18n.h"
#include <KLocalizedString>
#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 city = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::CityRole).toString();
const QString region = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::RegionRole).toString();
const QString comment = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::CommentRole).toString();
if (m_stringMatcher.indexIn(city) != -1 || m_stringMatcher.indexIn(region) != -1 || m_stringMatcher.indexIn(comment) != -1) {
return true;
}
return false;
}
void TimeZoneFilterProxy::setFilterString(const QString &filterString)
{
m_filterString = filterString;
m_stringMatcher.setPattern(filterString);
emit filterStringChanged();
invalidateFilter();
}
//=============================================================================
TimeZoneModel::TimeZoneModel(QObject *parent)
: QAbstractListModel(parent)
, m_timezonesI18n(new TimezonesI18n(this))
{
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()) {
TimeZoneData currentData = m_data.at(index.row());
switch (role) {
case TimeZoneIdRole:
return currentData.id;
case RegionRole:
return currentData.region;
case CityRole:
return currentData.city;
case CommentRole:
return currentData.comment;
case CheckedRole:
return currentData.checked;
}
}
return QVariant();
}
bool TimeZoneModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid() || value.isNull()) {
return false;
}
if (role == CheckedRole) {
m_data[index.row()].checked = value.toBool();
emit dataChanged(index, index);
if (m_data[index.row()].checked) {
m_selectedTimeZones.append(m_data[index.row()].id);
m_offsetData.insert(m_data[index.row()].id, m_data[index.row()].offsetFromUtc);
} else {
m_selectedTimeZones.removeAll(m_data[index.row()].id);
m_offsetData.remove(m_data[index.row()].id);
}
sortTimeZones();
emit selectedTimeZonesChanged();
return true;
}
return false;
}
void TimeZoneModel::update()
{
beginResetModel();
m_data.clear();
QTimeZone localZone = QTimeZone(QTimeZone::systemTimeZoneId());
const QStringList data = QString::fromUtf8(localZone.id()).split(QLatin1Char('/'));
TimeZoneData local;
local.id = QStringLiteral("Local");
local.region = i18nc("This means \"Local Timezone\"", "Local");
local.city = m_timezonesI18n->i18nCity(data.last());
local.comment = i18n("Your system time zone");
local.checked = false;
m_data.append(local);
QStringList cities;
QHash<QString, QTimeZone> zonesByCity;
const QList<QByteArray> systemTimeZones = QTimeZone::availableTimeZoneIds();
for (auto it = systemTimeZones.constBegin(); it != systemTimeZones.constEnd(); ++it) {
const QTimeZone zone(*it);
const QStringList splitted = QString::fromUtf8(zone.id()).split(QStringLiteral("/"));
// CITY | COUNTRY | CONTINENT
const QString key = QStringLiteral("%1|%2|%3").arg(splitted.last(), QLocale::countryToString(zone.country()), splitted.first());
cities.append(key);
zonesByCity.insert(key, zone);
}
cities.sort(Qt::CaseInsensitive);
Q_FOREACH (const QString &key, cities) {
const QTimeZone timeZone = zonesByCity.value(key);
QString comment = timeZone.comment();
// FIXME - this was in the old code but makes no sense
// if (!comment.isEmpty()) {
// comment = i18n(comment.toUtf8());
// }
const QStringList cityCountryContinent = key.split(QLatin1Char('|'));
TimeZoneData newData;
newData.id = QString::fromUtf8(timeZone.id());
newData.region = timeZone.country() == QLocale::AnyCountry
? QString()
: m_timezonesI18n->i18nContinents(cityCountryContinent.at(2)) + QLatin1Char('/') + m_timezonesI18n->i18nCountry(timeZone.country());
newData.city = m_timezonesI18n->i18nCity(cityCountryContinent.at(0));
newData.comment = comment;
newData.checked = false;
newData.offsetFromUtc = timeZone.offsetFromUtc(QDateTime::currentDateTimeUtc());
m_data.append(newData);
}
endResetModel();
}
void TimeZoneModel::setSelectedTimeZones(const QStringList &selectedTimeZones)
{
m_selectedTimeZones = selectedTimeZones;
for (int i = 0; i < m_data.size(); i++) {
if (m_selectedTimeZones.contains(m_data.at(i).id)) {
m_data[i].checked = true;
m_offsetData.insert(m_data[i].id, m_data[i].offsetFromUtc);
QModelIndex index = createIndex(i, 0);
emit dataChanged(index, index);
}
}
sortTimeZones();
}
void TimeZoneModel::selectLocalTimeZone()
{
m_data[0].checked = true;
QModelIndex index = createIndex(0, 0);
emit dataChanged(index, index);
m_selectedTimeZones << m_data[0].id;
emit selectedTimeZonesChanged();
}
QHash<int, QByteArray> TimeZoneModel::roleNames() const
{
return QHash<int, QByteArray>({
{TimeZoneIdRole, "timeZoneId"},
{RegionRole, "region"},
{CityRole, "city"},
{CommentRole, "comment"},
{CheckedRole, "checked"},
});
}
void TimeZoneModel::sortTimeZones()
{
std::sort(m_selectedTimeZones.begin(), m_selectedTimeZones.end(), [this](const QString &a, const QString &b) {
return m_offsetData.value(a) < m_offsetData.value(b);
});
}

80
kcms/time/timezonemodel.h Normal file
View file

@ -0,0 +1,80 @@
/*
SPDX-FileCopyrightText: 2014 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef TIMEZONEMODEL_H
#define TIMEZONEMODEL_H
#include <QAbstractListModel>
#include <QSortFilterProxyModel>
#include "timezonedata.h"
class TimezonesI18n;
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
Q_PROPERTY(QStringList selectedTimeZones WRITE setSelectedTimeZones MEMBER m_selectedTimeZones NOTIFY selectedTimeZonesChanged)
public:
explicit TimeZoneModel(QObject *parent = nullptr);
~TimeZoneModel() override;
enum Roles {
TimeZoneIdRole = Qt::UserRole + 1,
RegionRole,
CityRole,
CommentRole,
CheckedRole,
};
int rowCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
void update();
void setSelectedTimeZones(const QStringList &selectedTimeZones);
Q_INVOKABLE void selectLocalTimeZone();
Q_SIGNALS:
void selectedTimeZonesChanged();
protected:
QHash<int, QByteArray> roleNames() const override;
private:
void sortTimeZones();
QList<TimeZoneData> m_data;
QHash<QString, int> m_offsetData; // used for sorting
QStringList m_selectedTimeZones;
TimezonesI18n *m_timezonesI18n;
};
#endif // TIMEZONEMODEL_H

787
kcms/time/timezonesi18n.cpp Normal file
View file

@ -0,0 +1,787 @@
/*
SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "timezonesi18n.h"
#include <KLocalizedString>
TimezonesI18n::TimezonesI18n(QObject *parent)
: QObject(parent)
, m_isInitialized(false)
{
}
QString TimezonesI18n::i18nCity(const QString &city)
{
if (!m_isInitialized) {
init();
}
return m_i18nCities.value(city);
}
QString TimezonesI18n::i18nContinents(const QString &continent)
{
if (!m_isInitialized) {
init();
}
return m_i18nContinents.value(continent);
}
QString TimezonesI18n::i18nCountry(QLocale::Country country)
{
if (!m_isInitialized) {
init();
}
return m_i18nCountries.value(country);
}
void TimezonesI18n::init()
{
m_i18nCities =
QHash<QString, QString>({{QStringLiteral("Abidjan"), i18nc("This is a city associated with particular time zone", "Abidjan")},
{QStringLiteral("Accra"), i18nc("This is a city associated with particular time zone", "Accra")},
{QStringLiteral("Adak"), i18nc("This is a city associated with particular time zone", "Adak")},
{QStringLiteral("Addis_Ababa"), i18nc("This is a city associated with particular time zone", "Addis Ababa")},
{QStringLiteral("Adelaide"), i18nc("This is a city associated with particular time zone", "Adelaide")},
{QStringLiteral("Aden"), i18nc("This is a city associated with particular time zone", "Aden")},
{QStringLiteral("Algiers"), i18nc("This is a city associated with particular time zone", "Algiers")},
{QStringLiteral("Almaty"), i18nc("This is a city associated with particular time zone", "Almaty")},
{QStringLiteral("Amman"), i18nc("This is a city associated with particular time zone", "Amman")},
{QStringLiteral("Amsterdam"), i18nc("This is a city associated with particular time zone", "Amsterdam")},
{QStringLiteral("Anadyr"), i18nc("This is a city associated with particular time zone", "Anadyr")},
{QStringLiteral("Anchorage"), i18nc("This is a city associated with particular time zone", "Anchorage")},
{QStringLiteral("Andorra"), i18nc("This is a city associated with particular time zone", "Andorra")},
{QStringLiteral("Anguilla"), i18nc("This is a city associated with particular time zone", "Anguilla")},
{QStringLiteral("Antananarivo"), i18nc("This is a city associated with particular time zone", "Antananarivo")},
{QStringLiteral("Antigua"), i18nc("This is a city associated with particular time zone", "Antigua")},
{QStringLiteral("Apia"), i18nc("This is a city associated with particular time zone", "Apia")},
{QStringLiteral("Aqtau"), i18nc("This is a city associated with particular time zone", "Aqtau")},
{QStringLiteral("Aqtobe"), i18nc("This is a city associated with particular time zone", "Aqtobe")},
{QStringLiteral("Araguaina"), i18nc("This is a city associated with particular time zone", "Araguaina")},
{QStringLiteral("Aruba"), i18nc("This is a city associated with particular time zone", "Aruba")},
{QStringLiteral("Ashgabat"), i18nc("This is a city associated with particular time zone", "Ashgabat")},
{QStringLiteral("Asmara"), i18nc("This is a city associated with particular time zone", "Asmara")},
{QStringLiteral("Astrakhan"), i18nc("This is a city associated with particular time zone", "Astrakhan")},
{QStringLiteral("Asuncion"), i18nc("This is a city associated with particular time zone", "Asuncion")},
{QStringLiteral("Athens"), i18nc("This is a city associated with particular time zone", "Athens")},
{QStringLiteral("Atikokan"), i18nc("This is a city associated with particular time zone", "Atikokan")},
{QStringLiteral("Atyrau"), i18nc("This is a city associated with particular time zone", "Atyrau")},
{QStringLiteral("Auckland"), i18nc("This is a city associated with particular time zone", "Auckland")},
{QStringLiteral("Azores"), i18nc("This is a city associated with particular time zone", "Azores")},
{QStringLiteral("Baghdad"), i18nc("This is a city associated with particular time zone", "Baghdad")},
{QStringLiteral("Bahia_Banderas"), i18nc("This is a city associated with particular time zone", "Bahia Banderas")},
{QStringLiteral("Bahia"), i18nc("This is a city associated with particular time zone", "Bahia")},
{QStringLiteral("Bahrain"), i18nc("This is a city associated with particular time zone", "Bahrain")},
{QStringLiteral("Baku"), i18nc("This is a city associated with particular time zone", "Baku")},
{QStringLiteral("Bamako"), i18nc("This is a city associated with particular time zone", "Bamako")},
{QStringLiteral("Bangkok"), i18nc("This is a city associated with particular time zone", "Bangkok")},
{QStringLiteral("Bangui"), i18nc("This is a city associated with particular time zone", "Bangui")},
{QStringLiteral("Banjul"), i18nc("This is a city associated with particular time zone", "Banjul")},
{QStringLiteral("Barbados"), i18nc("This is a city associated with particular time zone", "Barbados")},
{QStringLiteral("Barnaul"), i18nc("This is a city associated with particular time zone", "Barnaul")},
{QStringLiteral("Beirut"), i18nc("This is a city associated with particular time zone", "Beirut")},
{QStringLiteral("Belem"), i18nc("This is a city associated with particular time zone", "Belem")},
{QStringLiteral("Belgrade"), i18nc("This is a city associated with particular time zone", "Belgrade")},
{QStringLiteral("Belize"), i18nc("This is a city associated with particular time zone", "Belize")},
{QStringLiteral("Berlin"), i18nc("This is a city associated with particular time zone", "Berlin")},
{QStringLiteral("Bermuda"), i18nc("This is a city associated with particular time zone", "Bermuda")},
{QStringLiteral("Beulah"), i18nc("This is a city associated with particular time zone", "Beulah")},
{QStringLiteral("Bishkek"), i18nc("This is a city associated with particular time zone", "Bishkek")},
{QStringLiteral("Bissau"), i18nc("This is a city associated with particular time zone", "Bissau")},
{QStringLiteral("Blanc-Sablon"), i18nc("This is a city associated with particular time zone", "Blanc-Sablon")},
{QStringLiteral("Blantyre"), i18nc("This is a city associated with particular time zone", "Blantyre")},
{QStringLiteral("Boa_Vista"), i18nc("This is a city associated with particular time zone", "Boa Vista")},
{QStringLiteral("Bogota"), i18nc("This is a city associated with particular time zone", "Bogota")},
{QStringLiteral("Boise"), i18nc("This is a city associated with particular time zone", "Boise")},
{QStringLiteral("Bougainville"), i18nc("This is a city associated with particular time zone", "Bougainville")},
{QStringLiteral("Bratislava"), i18nc("This is a city associated with particular time zone", "Bratislava")},
{QStringLiteral("Brazzaville"), i18nc("This is a city associated with particular time zone", "Brazzaville")},
{QStringLiteral("Brisbane"), i18nc("This is a city associated with particular time zone", "Brisbane")},
{QStringLiteral("Broken_Hill"), i18nc("This is a city associated with particular time zone", "Broken Hill")},
{QStringLiteral("Brunei"), i18nc("This is a city associated with particular time zone", "Brunei")},
{QStringLiteral("Brussels"), i18nc("This is a city associated with particular time zone", "Brussels")},
{QStringLiteral("Bucharest"), i18nc("This is a city associated with particular time zone", "Bucharest")},
{QStringLiteral("Budapest"), i18nc("This is a city associated with particular time zone", "Budapest")},
{QStringLiteral("Buenos_Aires"), i18nc("This is a city associated with particular time zone", "Buenos Aires")},
{QStringLiteral("Bujumbura"), i18nc("This is a city associated with particular time zone", "Bujumbura")},
{QStringLiteral("Busingen"), i18nc("This is a city associated with particular time zone", "Busingen")},
{QStringLiteral("Cairo"), i18nc("This is a city associated with particular time zone", "Cairo")},
{QStringLiteral("Cambridge_Bay"), i18nc("This is a city associated with particular time zone", "Cambridge Bay")},
{QStringLiteral("Campo_Grande"), i18nc("This is a city associated with particular time zone", "Campo Grande")},
{QStringLiteral("Canary"), i18nc("This is a city associated with particular time zone", "Canary")},
{QStringLiteral("Cancun"), i18nc("This is a city associated with particular time zone", "Cancun")},
{QStringLiteral("Cape_Verde"), i18nc("This is a city associated with particular time zone", "Cape Verde")},
{QStringLiteral("Caracas"), i18nc("This is a city associated with particular time zone", "Caracas")},
{QStringLiteral("Casablanca"), i18nc("This is a city associated with particular time zone", "Casablanca")},
{QStringLiteral("Casey"), i18nc("This is a city associated with particular time zone", "Casey")},
{QStringLiteral("Catamarca"), i18nc("This is a city associated with particular time zone", "Catamarca")},
{QStringLiteral("Cayenne"), i18nc("This is a city associated with particular time zone", "Cayenne")},
{QStringLiteral("Cayman"), i18nc("This is a city associated with particular time zone", "Cayman")},
{QStringLiteral("Center"), i18nc("This is a city associated with particular time zone", "Center")},
{QStringLiteral("Ceuta"), i18nc("This is a city associated with particular time zone", "Ceuta")},
{QStringLiteral("Chagos"), i18nc("This is a city associated with particular time zone", "Chagos")},
{QStringLiteral("Chatham"), i18nc("This is a city associated with particular time zone", "Chatham")},
{QStringLiteral("Chicago"), i18nc("This is a city associated with particular time zone", "Chicago")},
{QStringLiteral("Chihuahua"), i18nc("This is a city associated with particular time zone", "Chihuahua")},
{QStringLiteral("Chisinau"), i18nc("This is a city associated with particular time zone", "Chisinau")},
{QStringLiteral("Chita"), i18nc("This is a city associated with particular time zone", "Chita")},
{QStringLiteral("Choibalsan"), i18nc("This is a city associated with particular time zone", "Choibalsan")},
{QStringLiteral("Chongqing"), i18nc("This is a city associated with particular time zone", "Chongqing")},
{QStringLiteral("Christmas"), i18nc("This is a city associated with particular time zone", "Christmas")},
{QStringLiteral("Chuuk"), i18nc("This is a city associated with particular time zone", "Chuuk")},
{QStringLiteral("Cocos"), i18nc("This is a city associated with particular time zone", "Cocos")},
{QStringLiteral("Colombo"), i18nc("This is a city associated with particular time zone", "Colombo")},
{QStringLiteral("Comoro"), i18nc("This is a city associated with particular time zone", "Comoro")},
{QStringLiteral("Conakry"), i18nc("This is a city associated with particular time zone", "Conakry")},
{QStringLiteral("Copenhagen"), i18nc("This is a city associated with particular time zone", "Copenhagen")},
{QStringLiteral("Cordoba"), i18nc("This is a city associated with particular time zone", "Cordoba")},
{QStringLiteral("Costa_Rica"), i18nc("This is a city associated with particular time zone", "Costa Rica")},
{QStringLiteral("Creston"), i18nc("This is a city associated with particular time zone", "Creston")},
{QStringLiteral("Cuiaba"), i18nc("This is a city associated with particular time zone", "Cuiaba")},
{QStringLiteral("Curacao"), i18nc("This is a city associated with particular time zone", "Curacao")},
{QStringLiteral("Currie"), i18nc("This is a city associated with particular time zone", "Currie")},
{QStringLiteral("Dakar"), i18nc("This is a city associated with particular time zone", "Dakar")},
{QStringLiteral("Damascus"), i18nc("This is a city associated with particular time zone", "Damascus")},
{QStringLiteral("Danmarkshavn"), i18nc("This is a city associated with particular time zone", "Danmarkshavn")},
{QStringLiteral("Dar_es_Salaam"), i18nc("This is a city associated with particular time zone", "Dar es Salaam")},
{QStringLiteral("Darwin"), i18nc("This is a city associated with particular time zone", "Darwin")},
{QStringLiteral("Davis"), i18nc("This is a city associated with particular time zone", "Davis")},
{QStringLiteral("Dawson_Creek"), i18nc("This is a city associated with particular time zone", "Dawson Creek")},
{QStringLiteral("Dawson"), i18nc("This is a city associated with particular time zone", "Dawson")},
{QStringLiteral("Denver"), i18nc("This is a city associated with particular time zone", "Denver")},
{QStringLiteral("Detroit"), i18nc("This is a city associated with particular time zone", "Detroit")},
{QStringLiteral("Dhaka"), i18nc("This is a city associated with particular time zone", "Dhaka")},
{QStringLiteral("Dili"), i18nc("This is a city associated with particular time zone", "Dili")},
{QStringLiteral("Djibouti"), i18nc("This is a city associated with particular time zone", "Djibouti")},
{QStringLiteral("Dominica"), i18nc("This is a city associated with particular time zone", "Dominica")},
{QStringLiteral("Douala"), i18nc("This is a city associated with particular time zone", "Douala")},
{QStringLiteral("Dubai"), i18nc("This is a city associated with particular time zone", "Dubai")},
{QStringLiteral("Dublin"), i18nc("This is a city associated with particular time zone", "Dublin")},
{QStringLiteral("DumontDUrville"), i18nc("This is a city associated with particular time zone", "Dumont dUrville")},
{QStringLiteral("Dushanbe"), i18nc("This is a city associated with particular time zone", "Dushanbe")},
{QStringLiteral("Easter"), i18nc("This is a city associated with particular time zone", "Easter")},
{QStringLiteral("Edmonton"), i18nc("This is a city associated with particular time zone", "Edmonton")},
{QStringLiteral("Efate"), i18nc("This is a city associated with particular time zone", "Efate")},
{QStringLiteral("Eirunepe"), i18nc("This is a city associated with particular time zone", "Eirunepe")},
{QStringLiteral("El_Aaiun"), i18nc("This is a city associated with particular time zone", "El Aaiun")},
{QStringLiteral("El_Salvador"), i18nc("This is a city associated with particular time zone", "El Salvador")},
{QStringLiteral("Enderbury"), i18nc("This is a city associated with particular time zone", "Enderbury")},
{QStringLiteral("Eucla"), i18nc("This is a city associated with particular time zone", "Eucla")},
{QStringLiteral("Fakaofo"), i18nc("This is a city associated with particular time zone", "Fakaofo")},
{QStringLiteral("Famagusta"), i18nc("This is a city associated with particular time zone", "Famagusta")},
{QStringLiteral("Faroe"), i18nc("This is a city associated with particular time zone", "Faroe")},
{QStringLiteral("Fiji"), i18nc("This is a city associated with particular time zone", "Fiji")},
{QStringLiteral("Fort_Nelson"), i18nc("This is a city associated with particular time zone", "Fort Nelson")},
{QStringLiteral("Fortaleza"), i18nc("This is a city associated with particular time zone", "Fortaleza")},
{QStringLiteral("Freetown"), i18nc("This is a city associated with particular time zone", "Freetown")},
{QStringLiteral("Funafuti"), i18nc("This is a city associated with particular time zone", "Funafuti")},
{QStringLiteral("Gaborone"), i18nc("This is a city associated with particular time zone", "Gaborone")},
{QStringLiteral("Galapagos"), i18nc("This is a city associated with particular time zone", "Galapagos")},
{QStringLiteral("Gambier"), i18nc("This is a city associated with particular time zone", "Gambier")},
{QStringLiteral("Gaza"), i18nc("This is a city associated with particular time zone", "Gaza")},
{QStringLiteral("Gibraltar"), i18nc("This is a city associated with particular time zone", "Gibraltar")},
{QStringLiteral("Glace_Bay"), i18nc("This is a city associated with particular time zone", "Glace Bay")},
{QStringLiteral("Godthab"), i18nc("This is a city associated with particular time zone", "Godthab")},
{QStringLiteral("Goose_Bay"), i18nc("This is a city associated with particular time zone", "Goose Bay")},
{QStringLiteral("Grand_Turk"), i18nc("This is a city associated with particular time zone", "Grand Turk")},
{QStringLiteral("Grenada"), i18nc("This is a city associated with particular time zone", "Grenada")},
{QStringLiteral("Guadalcanal"), i18nc("This is a city associated with particular time zone", "Guadalcanal")},
{QStringLiteral("Guadeloupe"), i18nc("This is a city associated with particular time zone", "Guadeloupe")},
{QStringLiteral("Guam"), i18nc("This is a city associated with particular time zone", "Guam")},
{QStringLiteral("Guatemala"), i18nc("This is a city associated with particular time zone", "Guatemala")},
{QStringLiteral("Guayaquil"), i18nc("This is a city associated with particular time zone", "Guayaquil")},
{QStringLiteral("Guernsey"), i18nc("This is a city associated with particular time zone", "Guernsey")},
{QStringLiteral("Guyana"), i18nc("This is a city associated with particular time zone", "Guyana")},
{QStringLiteral("Halifax"), i18nc("This is a city associated with particular time zone", "Halifax")},
{QStringLiteral("Harare"), i18nc("This is a city associated with particular time zone", "Harare")},
{QStringLiteral("Harbin"), i18nc("This is a city associated with particular time zone", "Harbin")},
{QStringLiteral("Havana"), i18nc("This is a city associated with particular time zone", "Havana")},
{QStringLiteral("Hebron"), i18nc("This is a city associated with particular time zone", "Hebron")},
{QStringLiteral("Helsinki"), i18nc("This is a city associated with particular time zone", "Helsinki")},
{QStringLiteral("Hermosillo"), i18nc("This is a city associated with particular time zone", "Hermosillo")},
{QStringLiteral("Ho_Chi_Minh"), i18nc("This is a city associated with particular time zone", "Ho Chi Minh")},
{QStringLiteral("Hobart"), i18nc("This is a city associated with particular time zone", "Hobart")},
{QStringLiteral("Hong_Kong"), i18nc("This is a city associated with particular time zone", "Hong Kong")},
{QStringLiteral("Honolulu"), i18nc("This is a city associated with particular time zone", "Honolulu")},
{QStringLiteral("Hovd"), i18nc("This is a city associated with particular time zone", "Hovd")},
{QStringLiteral("Indianapolis"), i18nc("This is a city associated with particular time zone", "Indianapolis")},
{QStringLiteral("Inuvik"), i18nc("This is a city associated with particular time zone", "Inuvik")},
{QStringLiteral("Iqaluit"), i18nc("This is a city associated with particular time zone", "Iqaluit")},
{QStringLiteral("Irkutsk"), i18nc("This is a city associated with particular time zone", "Irkutsk")},
{QStringLiteral("Isle_of_Man"), i18nc("This is a city associated with particular time zone", "Isle of Man")},
{QStringLiteral("Istanbul"), i18nc("This is a city associated with particular time zone", "Istanbul")},
{QStringLiteral("Jakarta"), i18nc("This is a city associated with particular time zone", "Jakarta")},
{QStringLiteral("Jamaica"), i18nc("This is a city associated with particular time zone", "Jamaica")},
{QStringLiteral("Jayapura"), i18nc("This is a city associated with particular time zone", "Jayapura")},
{QStringLiteral("Jersey"), i18nc("This is a city associated with particular time zone", "Jersey")},
{QStringLiteral("Jerusalem"), i18nc("This is a city associated with particular time zone", "Jerusalem")},
{QStringLiteral("Johannesburg"), i18nc("This is a city associated with particular time zone", "Johannesburg")},
{QStringLiteral("Johnston"), i18nc("This is a city associated with particular time zone", "Johnston")},
{QStringLiteral("Juba"), i18nc("This is a city associated with particular time zone", "Juba")},
{QStringLiteral("Jujuy"), i18nc("This is a city associated with particular time zone", "Jujuy")},
{QStringLiteral("Juneau"), i18nc("This is a city associated with particular time zone", "Juneau")},
{QStringLiteral("Kabul"), i18nc("This is a city associated with particular time zone", "Kabul")},
{QStringLiteral("Kaliningrad"), i18nc("This is a city associated with particular time zone", "Kaliningrad")},
{QStringLiteral("Kamchatka"), i18nc("This is a city associated with particular time zone", "Kamchatka")},
{QStringLiteral("Kampala"), i18nc("This is a city associated with particular time zone", "Kampala")},
{QStringLiteral("Karachi"), i18nc("This is a city associated with particular time zone", "Karachi")},
{QStringLiteral("Kashgar"), i18nc("This is a city associated with particular time zone", "Kashgar")},
{QStringLiteral("Kathmandu"), i18nc("This is a city associated with particular time zone", "Kathmandu")},
{QStringLiteral("Kerguelen"), i18nc("This is a city associated with particular time zone", "Kerguelen")},
{QStringLiteral("Khandyga"), i18nc("This is a city associated with particular time zone", "Khandyga")},
{QStringLiteral("Khartoum"), i18nc("This is a city associated with particular time zone", "Khartoum")},
{QStringLiteral("Kiev"), i18nc("This is a city associated with particular time zone", "Kyiv")},
{QStringLiteral("Kigali"), i18nc("This is a city associated with particular time zone", "Kigali")},
{QStringLiteral("Kinshasa"), i18nc("This is a city associated with particular time zone", "Kinshasa")},
{QStringLiteral("Kiritimati"), i18nc("This is a city associated with particular time zone", "Kiritimati")},
{QStringLiteral("Kirov"), i18nc("This is a city associated with particular time zone", "Kirov")},
{QStringLiteral("Knox"), i18nc("This is a city associated with particular time zone", "Knox")},
{QStringLiteral("Kolkata"), i18nc("This is a city associated with particular time zone", "Kolkata")},
{QStringLiteral("Kosrae"), i18nc("This is a city associated with particular time zone", "Kosrae")},
{QStringLiteral("Kralendijk"), i18nc("This is a city associated with particular time zone", "Kralendijk")},
{QStringLiteral("Krasnoyarsk"), i18nc("This is a city associated with particular time zone", "Krasnoyarsk")},
{QStringLiteral("Kuala_Lumpur"), i18nc("This is a city associated with particular time zone", "Kuala Lumpur")},
{QStringLiteral("Kuching"), i18nc("This is a city associated with particular time zone", "Kuching")},
{QStringLiteral("Kuwait"), i18nc("This is a city associated with particular time zone", "Kuwait")},
{QStringLiteral("Kwajalein"), i18nc("This is a city associated with particular time zone", "Kwajalein")},
{QStringLiteral("La_Paz"), i18nc("This is a city associated with particular time zone", "La Paz")},
{QStringLiteral("La_Rioja"), i18nc("This is a city associated with particular time zone", "La Rioja")},
{QStringLiteral("Lagos"), i18nc("This is a city associated with particular time zone", "Lagos")},
{QStringLiteral("Libreville"), i18nc("This is a city associated with particular time zone", "Libreville")},
{QStringLiteral("Lima"), i18nc("This is a city associated with particular time zone", "Lima")},
{QStringLiteral("Lindeman"), i18nc("This is a city associated with particular time zone", "Lindeman")},
{QStringLiteral("Lisbon"), i18nc("This is a city associated with particular time zone", "Lisbon")},
{QStringLiteral("Ljubljana"), i18nc("This is a city associated with particular time zone", "Ljubljana")},
{QStringLiteral("Lome"), i18nc("This is a city associated with particular time zone", "Lome")},
{QStringLiteral("London"), i18nc("This is a city associated with particular time zone", "London")},
{QStringLiteral("Longyearbyen"), i18nc("This is a city associated with particular time zone", "Longyearbyen")},
{QStringLiteral("Lord_Howe"), i18nc("This is a city associated with particular time zone", "Lord Howe")},
{QStringLiteral("Los_Angeles"), i18nc("This is a city associated with particular time zone", "Los Angeles")},
{QStringLiteral("Louisville"), i18nc("This is a city associated with particular time zone", "Louisville")},
{QStringLiteral("Lower_Princes"), i18nc("This is a city associated with particular time zone", "Lower Princes")},
{QStringLiteral("Luanda"), i18nc("This is a city associated with particular time zone", "Luanda")},
{QStringLiteral("Lubumbashi"), i18nc("This is a city associated with particular time zone", "Lubumbashi")},
{QStringLiteral("Lusaka"), i18nc("This is a city associated with particular time zone", "Lusaka")},
{QStringLiteral("Luxembourg"), i18nc("This is a city associated with particular time zone", "Luxembourg")},
{QStringLiteral("Macau"), i18nc("This is a city associated with particular time zone", "Macau")},
{QStringLiteral("Maceio"), i18nc("This is a city associated with particular time zone", "Maceio")},
{QStringLiteral("Macquarie"), i18nc("This is a city associated with particular time zone", "Macquarie")},
{QStringLiteral("Madeira"), i18nc("This is a city associated with particular time zone", "Madeira")},
{QStringLiteral("Madrid"), i18nc("This is a city associated with particular time zone", "Madrid")},
{QStringLiteral("Magadan"), i18nc("This is a city associated with particular time zone", "Magadan")},
{QStringLiteral("Mahe"), i18nc("This is a city associated with particular time zone", "Mahe")},
{QStringLiteral("Majuro"), i18nc("This is a city associated with particular time zone", "Majuro")},
{QStringLiteral("Makassar"), i18nc("This is a city associated with particular time zone", "Makassar")},
{QStringLiteral("Malabo"), i18nc("This is a city associated with particular time zone", "Malabo")},
{QStringLiteral("Maldives"), i18nc("This is a city associated with particular time zone", "Maldives")},
{QStringLiteral("Malta"), i18nc("This is a city associated with particular time zone", "Malta")},
{QStringLiteral("Managua"), i18nc("This is a city associated with particular time zone", "Managua")},
{QStringLiteral("Manaus"), i18nc("This is a city associated with particular time zone", "Manaus")},
{QStringLiteral("Manila"), i18nc("This is a city associated with particular time zone", "Manila")},
{QStringLiteral("Maputo"), i18nc("This is a city associated with particular time zone", "Maputo")},
{QStringLiteral("Marengo"), i18nc("This is a city associated with particular time zone", "Marengo")},
{QStringLiteral("Mariehamn"), i18nc("This is a city associated with particular time zone", "Mariehamn")},
{QStringLiteral("Marigot"), i18nc("This is a city associated with particular time zone", "Marigot")},
{QStringLiteral("Marquesas"), i18nc("This is a city associated with particular time zone", "Marquesas")},
{QStringLiteral("Martinique"), i18nc("This is a city associated with particular time zone", "Martinique")},
{QStringLiteral("Maseru"), i18nc("This is a city associated with particular time zone", "Maseru")},
{QStringLiteral("Matamoros"), i18nc("This is a city associated with particular time zone", "Matamoros")},
{QStringLiteral("Mauritius"), i18nc("This is a city associated with particular time zone", "Mauritius")},
{QStringLiteral("Mawson"), i18nc("This is a city associated with particular time zone", "Mawson")},
{QStringLiteral("Mayotte"), i18nc("This is a city associated with particular time zone", "Mayotte")},
{QStringLiteral("Mazatlan"), i18nc("This is a city associated with particular time zone", "Mazatlan")},
{QStringLiteral("Mbabane"), i18nc("This is a city associated with particular time zone", "Mbabane")},
{QStringLiteral("McMurdo"), i18nc("This is a city associated with particular time zone", "McMurdo")},
{QStringLiteral("Melbourne"), i18nc("This is a city associated with particular time zone", "Melbourne")},
{QStringLiteral("Mendoza"), i18nc("This is a city associated with particular time zone", "Mendoza")},
{QStringLiteral("Menominee"), i18nc("This is a city associated with particular time zone", "Menominee")},
{QStringLiteral("Merida"), i18nc("This is a city associated with particular time zone", "Merida")},
{QStringLiteral("Metlakatla"), i18nc("This is a city associated with particular time zone", "Metlakatla")},
{QStringLiteral("Mexico_City"), i18nc("This is a city associated with particular time zone", "Mexico City")},
{QStringLiteral("Midway"), i18nc("This is a city associated with particular time zone", "Midway")},
{QStringLiteral("Minsk"), i18nc("This is a city associated with particular time zone", "Minsk")},
{QStringLiteral("Miquelon"), i18nc("This is a city associated with particular time zone", "Miquelon")},
{QStringLiteral("Mogadishu"), i18nc("This is a city associated with particular time zone", "Mogadishu")},
{QStringLiteral("Monaco"), i18nc("This is a city associated with particular time zone", "Monaco")},
{QStringLiteral("Moncton"), i18nc("This is a city associated with particular time zone", "Moncton")},
{QStringLiteral("Monrovia"), i18nc("This is a city associated with particular time zone", "Monrovia")},
{QStringLiteral("Monterrey"), i18nc("This is a city associated with particular time zone", "Monterrey")},
{QStringLiteral("Montevideo"), i18nc("This is a city associated with particular time zone", "Montevideo")},
{QStringLiteral("Monticello"), i18nc("This is a city associated with particular time zone", "Monticello")},
{QStringLiteral("Montserrat"), i18nc("This is a city associated with particular time zone", "Montserrat")},
{QStringLiteral("Moscow"), i18nc("This is a city associated with particular time zone", "Moscow")},
{QStringLiteral("Muscat"), i18nc("This is a city associated with particular time zone", "Muscat")},
{QStringLiteral("Nairobi"), i18nc("This is a city associated with particular time zone", "Nairobi")},
{QStringLiteral("Nassau"), i18nc("This is a city associated with particular time zone", "Nassau")},
{QStringLiteral("Nauru"), i18nc("This is a city associated with particular time zone", "Nauru")},
{QStringLiteral("Ndjamena"), i18nc("This is a city associated with particular time zone", "Ndjamena")},
{QStringLiteral("New_Salem"), i18nc("This is a city associated with particular time zone", "New Salem")},
{QStringLiteral("New_York"), i18nc("This is a city associated with particular time zone", "New York")},
{QStringLiteral("Niamey"), i18nc("This is a city associated with particular time zone", "Niamey")},
{QStringLiteral("Nicosia"), i18nc("This is a city associated with particular time zone", "Nicosia")},
{QStringLiteral("Nipigon"), i18nc("This is a city associated with particular time zone", "Nipigon")},
{QStringLiteral("Niue"), i18nc("This is a city associated with particular time zone", "Niue")},
{QStringLiteral("Nome"), i18nc("This is a city associated with particular time zone", "Nome")},
{QStringLiteral("Norfolk"), i18nc("This is a city associated with particular time zone", "Norfolk")},
{QStringLiteral("Noronha"), i18nc("This is a city associated with particular time zone", "Noronha")},
{QStringLiteral("Nouakchott"), i18nc("This is a city associated with particular time zone", "Nouakchott")},
{QStringLiteral("Noumea"), i18nc("This is a city associated with particular time zone", "Noumea")},
{QStringLiteral("Novokuznetsk"), i18nc("This is a city associated with particular time zone", "Novokuznetsk")},
{QStringLiteral("Novosibirsk"), i18nc("This is a city associated with particular time zone", "Novosibirsk")},
{QStringLiteral("Nuuk"), i18nc("This is a city associated with particular time zone", "Nuuk")},
{QStringLiteral("Ojinaga"), i18nc("This is a city associated with particular time zone", "Ojinaga")},
{QStringLiteral("Omsk"), i18nc("This is a city associated with particular time zone", "Omsk")},
{QStringLiteral("Oral"), i18nc("This is a city associated with particular time zone", "Oral")},
{QStringLiteral("Oslo"), i18nc("This is a city associated with particular time zone", "Oslo")},
{QStringLiteral("Ouagadougou"), i18nc("This is a city associated with particular time zone", "Ouagadougou")},
{QStringLiteral("Pago_Pago"), i18nc("This is a city associated with particular time zone", "Pago Pago")},
{QStringLiteral("Palau"), i18nc("This is a city associated with particular time zone", "Palau")},
{QStringLiteral("Palmer"), i18nc("This is a city associated with particular time zone", "Palmer")},
{QStringLiteral("Panama"), i18nc("This is a city associated with particular time zone", "Panama")},
{QStringLiteral("Pangnirtung"), i18nc("This is a city associated with particular time zone", "Pangnirtung")},
{QStringLiteral("Paramaribo"), i18nc("This is a city associated with particular time zone", "Paramaribo")},
{QStringLiteral("Paris"), i18nc("This is a city associated with particular time zone", "Paris")},
{QStringLiteral("Perth"), i18nc("This is a city associated with particular time zone", "Perth")},
{QStringLiteral("Petersburg"), i18nc("This is a city associated with particular time zone", "Petersburg")},
{QStringLiteral("Phnom_Penh"), i18nc("This is a city associated with particular time zone", "Phnom Penh")},
{QStringLiteral("Phoenix"), i18nc("This is a city associated with particular time zone", "Phoenix")},
{QStringLiteral("Pitcairn"), i18nc("This is a city associated with particular time zone", "Pitcairn")},
{QStringLiteral("Podgorica"), i18nc("This is a city associated with particular time zone", "Podgorica")},
{QStringLiteral("Pohnpei"), i18nc("This is a city associated with particular time zone", "Pohnpei")},
{QStringLiteral("Pontianak"), i18nc("This is a city associated with particular time zone", "Pontianak")},
{QStringLiteral("Port-au-Prince"), i18nc("This is a city associated with particular time zone", "Port-au-Prince")},
{QStringLiteral("Port_Moresby"), i18nc("This is a city associated with particular time zone", "Port Moresby")},
{QStringLiteral("Port_of_Spain"), i18nc("This is a city associated with particular time zone", "Port of Spain")},
{QStringLiteral("Porto-Novo"), i18nc("This is a city associated with particular time zone", "Porto-Novo")},
{QStringLiteral("Porto_Velho"), i18nc("This is a city associated with particular time zone", "Porto Velho")},
{QStringLiteral("Prague"), i18nc("This is a city associated with particular time zone", "Prague")},
{QStringLiteral("Puerto_Rico"), i18nc("This is a city associated with particular time zone", "Puerto Rico")},
{QStringLiteral("Punta_Arenas"), i18nc("This is a city associated with particular time zone", "Punta Arenas")},
{QStringLiteral("Pyongyang"), i18nc("This is a city associated with particular time zone", "Pyongyang")},
{QStringLiteral("Qatar"), i18nc("This is a city associated with particular time zone", "Qatar")},
{QStringLiteral("Qostanay"), i18nc("This is a city associated with particular time zone", "Qostanay")},
{QStringLiteral("Qyzylorda"), i18nc("This is a city associated with particular time zone", "Qyzylorda")},
{QStringLiteral("Rainy_River"), i18nc("This is a city associated with particular time zone", "Rainy River")},
{QStringLiteral("Rangoon"), i18nc("This is a city associated with particular time zone", "Rangoon")},
{QStringLiteral("Rankin_Inlet"), i18nc("This is a city associated with particular time zone", "Rankin Inlet")},
{QStringLiteral("Rarotonga"), i18nc("This is a city associated with particular time zone", "Rarotonga")},
{QStringLiteral("Recife"), i18nc("This is a city associated with particular time zone", "Recife")},
{QStringLiteral("Regina"), i18nc("This is a city associated with particular time zone", "Regina")},
{QStringLiteral("Resolute"), i18nc("This is a city associated with particular time zone", "Resolute")},
{QStringLiteral("Reunion"), i18nc("This is a city associated with particular time zone", "Reunion")},
{QStringLiteral("Reykjavik"), i18nc("This is a city associated with particular time zone", "Reykjavik")},
{QStringLiteral("Riga"), i18nc("This is a city associated with particular time zone", "Riga")},
{QStringLiteral("Rio_Branco"), i18nc("This is a city associated with particular time zone", "Rio Branco")},
{QStringLiteral("Rio_Gallegos"), i18nc("This is a city associated with particular time zone", "Rio Gallegos")},
{QStringLiteral("Riyadh"), i18nc("This is a city associated with particular time zone", "Riyadh")},
{QStringLiteral("Rome"), i18nc("This is a city associated with particular time zone", "Rome")},
{QStringLiteral("Rothera"), i18nc("This is a city associated with particular time zone", "Rothera")},
{QStringLiteral("Saipan"), i18nc("This is a city associated with particular time zone", "Saipan")},
{QStringLiteral("Sakhalin"), i18nc("This is a city associated with particular time zone", "Sakhalin")},
{QStringLiteral("Salta"), i18nc("This is a city associated with particular time zone", "Salta")},
{QStringLiteral("Samara"), i18nc("This is a city associated with particular time zone", "Samara")},
{QStringLiteral("Samarkand"), i18nc("This is a city associated with particular time zone", "Samarkand")},
{QStringLiteral("San_Juan"), i18nc("This is a city associated with particular time zone", "San Juan")},
{QStringLiteral("San_Luis"), i18nc("This is a city associated with particular time zone", "San Luis")},
{QStringLiteral("San_Marino"), i18nc("This is a city associated with particular time zone", "San Marino")},
{QStringLiteral("Santa_Isabel"), i18nc("This is a city associated with particular time zone", "Santa Isabel")},
{QStringLiteral("Santarem"), i18nc("This is a city associated with particular time zone", "Santarem")},
{QStringLiteral("Santiago"), i18nc("This is a city associated with particular time zone", "Santiago")},
{QStringLiteral("Santo_Domingo"), i18nc("This is a city associated with particular time zone", "Santo Domingo")},
{QStringLiteral("Sao_Paulo"), i18nc("This is a city associated with particular time zone", "Sao Paulo")},
{QStringLiteral("Sao_Tome"), i18nc("This is a city associated with particular time zone", "Sao Tome")},
{QStringLiteral("Sarajevo"), i18nc("This is a city associated with particular time zone", "Sarajevo")},
{QStringLiteral("Saratov"), i18nc("This is a city associated with particular time zone", "Saratov")},
{QStringLiteral("Scoresbysund"), i18nc("This is a city associated with particular time zone", "Scoresbysund")},
{QStringLiteral("Seoul"), i18nc("This is a city associated with particular time zone", "Seoul")},
{QStringLiteral("Shanghai"), i18nc("This is a city associated with particular time zone", "Shanghai")},
{QStringLiteral("Simferopol"), i18nc("This is a city associated with particular time zone", "Simferopol")},
{QStringLiteral("Singapore"), i18nc("This is a city associated with particular time zone", "Singapore")},
{QStringLiteral("Sitka"), i18nc("This is a city associated with particular time zone", "Sitka")},
{QStringLiteral("Skopje"), i18nc("This is a city associated with particular time zone", "Skopje")},
{QStringLiteral("Sofia"), i18nc("This is a city associated with particular time zone", "Sofia")},
{QStringLiteral("South_Georgia"), i18nc("This is a city associated with particular time zone", "South Georgia")},
{QStringLiteral("Srednekolymsk"), i18nc("This is a city associated with particular time zone", "Srednekolymsk")},
{QStringLiteral("St_Barthelemy"), i18nc("This is a city associated with particular time zone", "St Barthelemy")},
{QStringLiteral("St_Helena"), i18nc("This is a city associated with particular time zone", "St Helena")},
{QStringLiteral("St_Johns"), i18nc("This is a city associated with particular time zone", "St Johns")},
{QStringLiteral("St_Kitts"), i18nc("This is a city associated with particular time zone", "St Kitts")},
{QStringLiteral("St_Lucia"), i18nc("This is a city associated with particular time zone", "St Lucia")},
{QStringLiteral("St_Thomas"), i18nc("This is a city associated with particular time zone", "St Thomas")},
{QStringLiteral("St_Vincent"), i18nc("This is a city associated with particular time zone", "St Vincent")},
{QStringLiteral("Stanley"), i18nc("This is a city associated with particular time zone", "Stanley")},
{QStringLiteral("Stockholm"), i18nc("This is a city associated with particular time zone", "Stockholm")},
{QStringLiteral("Swift_Current"), i18nc("This is a city associated with particular time zone", "Swift Current")},
{QStringLiteral("Sydney"), i18nc("This is a city associated with particular time zone", "Sydney")},
{QStringLiteral("Syowa"), i18nc("This is a city associated with particular time zone", "Syowa")},
{QStringLiteral("Tahiti"), i18nc("This is a city associated with particular time zone", "Tahiti")},
{QStringLiteral("Taipei"), i18nc("This is a city associated with particular time zone", "Taipei")},
{QStringLiteral("Tallinn"), i18nc("This is a city associated with particular time zone", "Tallinn")},
{QStringLiteral("Tarawa"), i18nc("This is a city associated with particular time zone", "Tarawa")},
{QStringLiteral("Tashkent"), i18nc("This is a city associated with particular time zone", "Tashkent")},
{QStringLiteral("Tbilisi"), i18nc("This is a city associated with particular time zone", "Tbilisi")},
{QStringLiteral("Tegucigalpa"), i18nc("This is a city associated with particular time zone", "Tegucigalpa")},
{QStringLiteral("Tehran"), i18nc("This is a city associated with particular time zone", "Tehran")},
{QStringLiteral("Tell_City"), i18nc("This is a city associated with particular time zone", "Tell City")},
{QStringLiteral("Thimphu"), i18nc("This is a city associated with particular time zone", "Thimphu")},
{QStringLiteral("Thule"), i18nc("This is a city associated with particular time zone", "Thule")},
{QStringLiteral("Thunder_Bay"), i18nc("This is a city associated with particular time zone", "Thunder Bay")},
{QStringLiteral("Tijuana"), i18nc("This is a city associated with particular time zone", "Tijuana")},
{QStringLiteral("Tirane"), i18nc("This is a city associated with particular time zone", "Tirane")},
{QStringLiteral("Tokyo"), i18nc("This is a city associated with particular time zone", "Tokyo")},
{QStringLiteral("Tomsk"), i18nc("This is a city associated with particular time zone", "Tomsk")},
{QStringLiteral("Tongatapu"), i18nc("This is a city associated with particular time zone", "Tongatapu")},
{QStringLiteral("Toronto"), i18nc("This is a city associated with particular time zone", "Toronto")},
{QStringLiteral("Tortola"), i18nc("This is a city associated with particular time zone", "Tortola")},
{QStringLiteral("Tripoli"), i18nc("This is a city associated with particular time zone", "Tripoli")},
{QStringLiteral("Troll"), i18nc("This is a city associated with particular time zone", "Troll")},
{QStringLiteral("Tucuman"), i18nc("This is a city associated with particular time zone", "Tucuman")},
{QStringLiteral("Tunis"), i18nc("This is a city associated with particular time zone", "Tunis")},
{QStringLiteral("Ulaanbaatar"), i18nc("This is a city associated with particular time zone", "Ulaanbaatar")},
{QStringLiteral("Ulyanovsk"), i18nc("This is a city associated with particular time zone", "Ulyanovsk")},
{QStringLiteral("Urumqi"), i18nc("This is a city associated with particular time zone", "Urumqi")},
{QStringLiteral("Ushuaia"), i18nc("This is a city associated with particular time zone", "Ushuaia")},
{QStringLiteral("Ust-Nera"), i18nc("This is a city associated with particular time zone", "Ust-Nera")},
{QStringLiteral("UTC+00:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+00:00")},
{QStringLiteral("UTC+01:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+01:00")},
{QStringLiteral("UTC+02:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+02:00")},
{QStringLiteral("UTC+03:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+03:00")},
{QStringLiteral("UTC+03:30"), i18nc("This is a generic time zone name, localize as needed", "UTC+03:30")},
{QStringLiteral("UTC+04:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+04:00")},
{QStringLiteral("UTC+04:30"), i18nc("This is a generic time zone name, localize as needed", "UTC+04:30")},
{QStringLiteral("UTC+05:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+05:00")},
{QStringLiteral("UTC+05:30"), i18nc("This is a generic time zone name, localize as needed", "UTC+05:30")},
{QStringLiteral("UTC+05:45"), i18nc("This is a generic time zone name, localize as needed", "UTC+05:45")},
{QStringLiteral("UTC+06:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+06:00")},
{QStringLiteral("UTC+06:30"), i18nc("This is a generic time zone name, localize as needed", "UTC+06:30")},
{QStringLiteral("UTC+07:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+07:00")},
{QStringLiteral("UTC+08:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+08:00")},
{QStringLiteral("UTC+09:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+09:00")},
{QStringLiteral("UTC+09:30"), i18nc("This is a generic time zone name, localize as needed", "UTC+09:30")},
{QStringLiteral("UTC+10:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+10:00")},
{QStringLiteral("UTC+11:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+11:00")},
{QStringLiteral("UTC+12:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+12:00")},
{QStringLiteral("UTC+13:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+13:00")},
{QStringLiteral("UTC+14:00"), i18nc("This is a generic time zone name, localize as needed", "UTC+14:00")},
{QStringLiteral("UTC-00:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-00:00")},
{QStringLiteral("UTC-01:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-01:00")},
{QStringLiteral("UTC-02:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-02:00")},
{QStringLiteral("UTC-03:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-03:00")},
{QStringLiteral("UTC-03:30"), i18nc("This is a generic time zone name, localize as needed", "UTC-03:30")},
{QStringLiteral("UTC-04:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-04:00")},
{QStringLiteral("UTC-04:30"), i18nc("This is a generic time zone name, localize as needed", "UTC-04:30")},
{QStringLiteral("UTC-05:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-05:00")},
{QStringLiteral("UTC-06:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-06:00")},
{QStringLiteral("UTC-07:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-07:00")},
{QStringLiteral("UTC-08:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-08:00")},
{QStringLiteral("UTC-09:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-09:00")},
{QStringLiteral("UTC-10:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-10:00")},
{QStringLiteral("UTC-11:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-11:00")},
{QStringLiteral("UTC-12:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-12:00")},
{QStringLiteral("UTC-13:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-13:00")},
{QStringLiteral("UTC-14:00"), i18nc("This is a generic time zone name, localize as needed", "UTC-14:00")},
{QStringLiteral("UTC"), i18nc("This is a generic time zone name, localize as needed", "UTC")},
{QStringLiteral("Uzhgorod"), i18nc("This is a city associated with particular time zone", "Uzhhorod")},
{QStringLiteral("Vaduz"), i18nc("This is a city associated with particular time zone", "Vaduz")},
{QStringLiteral("Vancouver"), i18nc("This is a city associated with particular time zone", "Vancouver")},
{QStringLiteral("Vatican"), i18nc("This is a city associated with particular time zone", "Vatican")},
{QStringLiteral("Vevay"), i18nc("This is a city associated with particular time zone", "Vevay")},
{QStringLiteral("Vienna"), i18nc("This is a city associated with particular time zone", "Vienna")},
{QStringLiteral("Vientiane"), i18nc("This is a city associated with particular time zone", "Vientiane")},
{QStringLiteral("Vilnius"), i18nc("This is a city associated with particular time zone", "Vilnius")},
{QStringLiteral("Vincennes"), i18nc("This is a city associated with particular time zone", "Vincennes")},
{QStringLiteral("Vladivostok"), i18nc("This is a city associated with particular time zone", "Vladivostok")},
{QStringLiteral("Volgograd"), i18nc("This is a city associated with particular time zone", "Volgograd")},
{QStringLiteral("Vostok"), i18nc("This is a city associated with particular time zone", "Vostok")},
{QStringLiteral("Wake"), i18nc("This is a city associated with particular time zone", "Wake")},
{QStringLiteral("Wallis"), i18nc("This is a city associated with particular time zone", "Wallis")},
{QStringLiteral("Warsaw"), i18nc("This is a city associated with particular time zone", "Warsaw")},
{QStringLiteral("Whitehorse"), i18nc("This is a city associated with particular time zone", "Whitehorse")},
{QStringLiteral("Winamac"), i18nc("This is a city associated with particular time zone", "Winamac")},
{QStringLiteral("Windhoek"), i18nc("This is a city associated with particular time zone", "Windhoek")},
{QStringLiteral("Winnipeg"), i18nc("This is a city associated with particular time zone", "Winnipeg")},
{QStringLiteral("Yakutat"), i18nc("This is a city associated with particular time zone", "Yakutat")},
{QStringLiteral("Yakutsk"), i18nc("This is a city associated with particular time zone", "Yakutsk")},
{QStringLiteral("Yangon"), i18nc("This is a city associated with particular time zone", "Yangon")},
{QStringLiteral("Yekaterinburg"), i18nc("This is a city associated with particular time zone", "Yekaterinburg")},
{QStringLiteral("Yellowknife"), i18nc("This is a city associated with particular time zone", "Yellowknife")},
{QStringLiteral("Yerevan"), i18nc("This is a city associated with particular time zone", "Yerevan")},
{QStringLiteral("Zagreb"), i18nc("This is a city associated with particular time zone", "Zagreb")},
{QStringLiteral("Zaporozhye"), i18nc("This is a city associated with particular time zone", "Zaporizhzhia")},
{QStringLiteral("Zurich"), i18nc("This is a city associated with particular time zone", "Zurich")}});
#define ENTRY_ISO_3166(qlocale_enum, string) {QLocale::qlocale_enum, i18nd("iso_3166", string)}
/* Make sure the country names match their versions in iso-codes,
* ISO 3166. */
m_i18nCountries = QHash<QLocale::Country, QString>({
ENTRY_ISO_3166(IvoryCoast, "Côte d'Ivoire"),
ENTRY_ISO_3166(Ghana, "Ghana"),
ENTRY_ISO_3166(Ethiopia, "Ethiopia"),
ENTRY_ISO_3166(Algeria, "Algeria"),
ENTRY_ISO_3166(Eritrea, "Eritrea"),
ENTRY_ISO_3166(Mali, "Mali"),
ENTRY_ISO_3166(CentralAfricanRepublic, "Central African Republic"),
ENTRY_ISO_3166(Gambia, "Gambia"),
ENTRY_ISO_3166(GuineaBissau, "Guinea-Bissau"),
ENTRY_ISO_3166(Malawi, "Malawi"),
ENTRY_ISO_3166(CongoBrazzaville, "Congo"),
ENTRY_ISO_3166(Burundi, "Burundi"),
ENTRY_ISO_3166(Egypt, "Egypt"),
ENTRY_ISO_3166(Morocco, "Morocco"),
ENTRY_ISO_3166(Spain, "Spain"),
ENTRY_ISO_3166(Guinea, "Guinea"),
ENTRY_ISO_3166(Senegal, "Senegal"),
ENTRY_ISO_3166(Tanzania, "Tanzania"),
ENTRY_ISO_3166(Djibouti, "Djibouti"),
ENTRY_ISO_3166(Cameroon, "Cameroon"),
ENTRY_ISO_3166(WesternSahara, "Western Sahara"),
ENTRY_ISO_3166(SierraLeone, "Sierra Leone"),
ENTRY_ISO_3166(Botswana, "Botswana"),
ENTRY_ISO_3166(BouvetIsland, "Bouvet Island"),
ENTRY_ISO_3166(Zimbabwe, "Zimbabwe"),
ENTRY_ISO_3166(SouthAfrica, "South Africa"),
ENTRY_ISO_3166(SouthSudan, "South Sudan"),
ENTRY_ISO_3166(Uganda, "Uganda"),
ENTRY_ISO_3166(Sudan, "Sudan"),
ENTRY_ISO_3166(Rwanda, "Rwanda"),
ENTRY_ISO_3166(CongoKinshasa, "Congo, The Democratic Republic of the"),
ENTRY_ISO_3166(Nigeria, "Nigeria"),
ENTRY_ISO_3166(Gabon, "Gabon"),
ENTRY_ISO_3166(Togo, "Togo"),
ENTRY_ISO_3166(Angola, "Angola"),
ENTRY_ISO_3166(Zambia, "Zambia"),
ENTRY_ISO_3166(EquatorialGuinea, "Equatorial Guinea"),
ENTRY_ISO_3166(Mozambique, "Mozambique"),
ENTRY_ISO_3166(Lesotho, "Lesotho"),
ENTRY_ISO_3166(Swaziland, "Swaziland"),
ENTRY_ISO_3166(Somalia, "Somalia"),
ENTRY_ISO_3166(Liberia, "Liberia"),
ENTRY_ISO_3166(Kenya, "Kenya"),
ENTRY_ISO_3166(Chad, "Chad"),
ENTRY_ISO_3166(Niger, "Niger"),
ENTRY_ISO_3166(Mauritania, "Mauritania"),
ENTRY_ISO_3166(BurkinaFaso, "Burkina Faso"),
ENTRY_ISO_3166(Benin, "Benin"),
ENTRY_ISO_3166(SaoTomeAndPrincipe, "Sao Tome and Principe"),
ENTRY_ISO_3166(Libya, "Libya"),
ENTRY_ISO_3166(Tunisia, "Tunisia"),
ENTRY_ISO_3166(Namibia, "Namibia"),
ENTRY_ISO_3166(UnitedStates, "United States"),
ENTRY_ISO_3166(Anguilla, "Anguilla"),
ENTRY_ISO_3166(AntiguaAndBarbuda, "Antigua and Barbuda"),
ENTRY_ISO_3166(Brazil, "Brazil"),
ENTRY_ISO_3166(Argentina, "Argentina"),
ENTRY_ISO_3166(Aruba, "Aruba"),
ENTRY_ISO_3166(Paraguay, "Paraguay"),
ENTRY_ISO_3166(Canada, "Canada"),
ENTRY_ISO_3166(Mexico, "Mexico"),
ENTRY_ISO_3166(Barbados, "Barbados"),
ENTRY_ISO_3166(Belize, "Belize"),
ENTRY_ISO_3166(Colombia, "Colombia"),
ENTRY_ISO_3166(Venezuela, "Venezuela"),
ENTRY_ISO_3166(FrenchGuiana, "French Guiana"),
ENTRY_ISO_3166(CaymanIslands, "Cayman Islands"),
ENTRY_ISO_3166(CostaRica, "Costa Rica"),
ENTRY_ISO_3166(CuraSao, "Curaçao"),
ENTRY_ISO_3166(Greenland, "Greenland"),
ENTRY_ISO_3166(Dominica, "Dominica"),
ENTRY_ISO_3166(ElSalvador, "El Salvador"),
ENTRY_ISO_3166(TurksAndCaicosIslands, "Turks and Caicos Islands"),
ENTRY_ISO_3166(Grenada, "Grenada"),
ENTRY_ISO_3166(Guadeloupe, "Guadeloupe"),
ENTRY_ISO_3166(Guatemala, "Guatemala"),
ENTRY_ISO_3166(Ecuador, "Ecuador"),
ENTRY_ISO_3166(Guyana, "Guyana"),
ENTRY_ISO_3166(Cuba, "Cuba"),
ENTRY_ISO_3166(Jamaica, "Jamaica"),
ENTRY_ISO_3166(Bonaire, "Bonaire, Sint Eustatius and Saba"),
ENTRY_ISO_3166(Bolivia, "Bolivia"),
ENTRY_ISO_3166(Peru, "Peru"),
ENTRY_ISO_3166(SintMaarten, "Sint Maarten (Dutch part)"),
ENTRY_ISO_3166(Nicaragua, "Nicaragua"),
ENTRY_ISO_3166(SaintMartin, "Saint Martin (French part)"),
ENTRY_ISO_3166(Martinique, "Martinique"),
ENTRY_ISO_3166(SaintPierreAndMiquelon, "Saint Pierre and Miquelon"),
ENTRY_ISO_3166(Uruguay, "Uruguay"),
ENTRY_ISO_3166(Montserrat, "Montserrat"),
ENTRY_ISO_3166(Bahamas, "Bahamas"),
ENTRY_ISO_3166(Panama, "Panama"),
ENTRY_ISO_3166(Suriname, "Suriname"),
ENTRY_ISO_3166(Haiti, "Haiti"),
ENTRY_ISO_3166(HeardAndMcDonaldIslands, "Heard Island and McDonald Islands"),
ENTRY_ISO_3166(TrinidadAndTobago, "Trinidad and Tobago"),
ENTRY_ISO_3166(PuertoRico, "Puerto Rico"),
ENTRY_ISO_3166(Chile, "Chile"),
ENTRY_ISO_3166(DominicanRepublic, "Dominican Republic"),
ENTRY_ISO_3166(SaintBarthelemy, "Saint Barthélemy"),
ENTRY_ISO_3166(SaintKittsAndNevis, "Saint Kitts and Nevis"),
ENTRY_ISO_3166(SaintLucia, "Saint Lucia"),
ENTRY_ISO_3166(UnitedStatesVirginIslands, "Virgin Islands, U.S."),
ENTRY_ISO_3166(SaintVincentAndTheGrenadines, "Saint Vincent and the Grenadines"),
ENTRY_ISO_3166(Honduras, "Honduras"),
ENTRY_ISO_3166(BritishVirginIslands, "Virgin Islands, British"),
ENTRY_ISO_3166(Antarctica, "Antarctica"),
ENTRY_ISO_3166(Australia, "Australia"),
ENTRY_ISO_3166(SvalbardAndJanMayenIslands, "Svalbard and Jan Mayen"),
ENTRY_ISO_3166(Yemen, "Yemen"),
ENTRY_ISO_3166(Kazakhstan, "Kazakhstan"),
ENTRY_ISO_3166(Jordan, "Jordan"),
ENTRY_ISO_3166(Russia, "Russian Federation"),
ENTRY_ISO_3166(Turkmenistan, "Turkmenistan"),
ENTRY_ISO_3166(Iraq, "Iraq"),
ENTRY_ISO_3166(Bahrain, "Bahrain"),
ENTRY_ISO_3166(Azerbaijan, "Azerbaijan"),
ENTRY_ISO_3166(Thailand, "Thailand"),
ENTRY_ISO_3166(Lebanon, "Lebanon"),
ENTRY_ISO_3166(Kyrgyzstan, "Kyrgyzstan"),
ENTRY_ISO_3166(Brunei, "Brunei Darussalam"),
ENTRY_ISO_3166(Mongolia, "Mongolia"),
ENTRY_ISO_3166(China, "China"),
ENTRY_ISO_3166(SriLanka, "Sri Lanka"),
ENTRY_ISO_3166(Syria, "Syrian Arab Republic"),
ENTRY_ISO_3166(Bangladesh, "Bangladesh"),
ENTRY_ISO_3166(EastTimor, "Timor-Leste"),
ENTRY_ISO_3166(UnitedArabEmirates, "United Arab Emirates"),
ENTRY_ISO_3166(Tajikistan, "Tajikistan"),
ENTRY_ISO_3166(PalestinianTerritories, "Palestine, State of"),
ENTRY_ISO_3166(Vietnam, "Vietnam"),
ENTRY_ISO_3166(HongKong, "Hong Kong"),
ENTRY_ISO_3166(Indonesia, "Indonesia"),
ENTRY_ISO_3166(Israel, "Israel"),
ENTRY_ISO_3166(Afghanistan, "Afghanistan"),
ENTRY_ISO_3166(Pakistan, "Pakistan"),
ENTRY_ISO_3166(Nepal, "Nepal"),
ENTRY_ISO_3166(India, "India"),
ENTRY_ISO_3166(Malaysia, "Malaysia"),
ENTRY_ISO_3166(Kuwait, "Kuwait"),
ENTRY_ISO_3166(Macau, "Macao"),
ENTRY_ISO_3166(Philippines, "Philippines"),
ENTRY_ISO_3166(Oman, "Oman"),
ENTRY_ISO_3166(Cyprus, "Cyprus"),
ENTRY_ISO_3166(Cambodia, "Cambodia"),
ENTRY_ISO_3166(NorthKorea, "Korea, Democratic People's Republic of"),
ENTRY_ISO_3166(Qatar, "Qatar"),
ENTRY_ISO_3166(Myanmar, "Myanmar"),
ENTRY_ISO_3166(SaudiArabia, "Saudi Arabia"),
ENTRY_ISO_3166(Uzbekistan, "Uzbekistan"),
ENTRY_ISO_3166(SouthKorea, "Korea, Republic of"),
ENTRY_ISO_3166(Singapore, "Singapore"),
ENTRY_ISO_3166(Taiwan, "Taiwan"),
ENTRY_ISO_3166(Georgia, "Georgia"),
ENTRY_ISO_3166(Iran, "Iran, Islamic Republic of"),
ENTRY_ISO_3166(Bhutan, "Bhutan"),
ENTRY_ISO_3166(Japan, "Japan"),
ENTRY_ISO_3166(Laos, "Lao People's Democratic Republic"),
ENTRY_ISO_3166(Armenia, "Armenia"),
ENTRY_ISO_3166(Portugal, "Portugal"),
ENTRY_ISO_3166(Bermuda, "Bermuda"),
ENTRY_ISO_3166(CapeVerde, "Cabo Verde"),
ENTRY_ISO_3166(FaroeIslands, "Faroe Islands"),
ENTRY_ISO_3166(Iceland, "Iceland"),
ENTRY_ISO_3166(SouthGeorgiaAndTheSouthSandwichIslands, "South Georgia and the South Sandwich Islands"),
ENTRY_ISO_3166(SaintHelena, "Saint Helena, Ascension and Tristan da Cunha"),
ENTRY_ISO_3166(FalklandIslands, "Falkland Islands (Malvinas)"),
ENTRY_ISO_3166(Netherlands, "Netherlands"),
ENTRY_ISO_3166(Andorra, "Andorra"),
ENTRY_ISO_3166(Greece, "Greece"),
ENTRY_ISO_3166(Serbia, "Serbia"),
ENTRY_ISO_3166(Germany, "Germany"),
ENTRY_ISO_3166(Slovakia, "Slovakia"),
ENTRY_ISO_3166(Belgium, "Belgium"),
ENTRY_ISO_3166(Romania, "Romania"),
ENTRY_ISO_3166(Hungary, "Hungary"),
ENTRY_ISO_3166(Moldova, "Moldova"),
ENTRY_ISO_3166(Denmark, "Denmark"),
ENTRY_ISO_3166(Ireland, "Ireland"),
ENTRY_ISO_3166(Gibraltar, "Gibraltar"),
ENTRY_ISO_3166(Guernsey, "Guernsey"),
ENTRY_ISO_3166(Finland, "Finland"),
ENTRY_ISO_3166(IsleOfMan, "Isle of Man"),
ENTRY_ISO_3166(Turkey, "Turkey"),
ENTRY_ISO_3166(Jersey, "Jersey"),
ENTRY_ISO_3166(Ukraine, "Ukraine"),
ENTRY_ISO_3166(Slovenia, "Slovenia"),
ENTRY_ISO_3166(UnitedKingdom, "United Kingdom"),
ENTRY_ISO_3166(Luxembourg, "Luxembourg"),
ENTRY_ISO_3166(Malta, "Malta"),
ENTRY_ISO_3166(AlandIslands, "Åland Islands"),
ENTRY_ISO_3166(Belarus, "Belarus"),
ENTRY_ISO_3166(Monaco, "Monaco"),
ENTRY_ISO_3166(Norway, "Norway"),
ENTRY_ISO_3166(France, "France"),
ENTRY_ISO_3166(Montenegro, "Montenegro"),
ENTRY_ISO_3166(CzechRepublic, "Czechia"),
ENTRY_ISO_3166(Latvia, "Latvia"),
ENTRY_ISO_3166(Italy, "Italy"),
ENTRY_ISO_3166(SanMarino, "San Marino"),
ENTRY_ISO_3166(BosniaAndHerzegowina, "Bosnia and Herzegovina"),
ENTRY_ISO_3166(Macedonia, "Macedonia, Republic of"),
ENTRY_ISO_3166(Bulgaria, "Bulgaria"),
ENTRY_ISO_3166(Sweden, "Sweden"),
ENTRY_ISO_3166(Estonia, "Estonia"),
ENTRY_ISO_3166(Albania, "Albania"),
ENTRY_ISO_3166(Liechtenstein, "Liechtenstein"),
ENTRY_ISO_3166(VaticanCityState, "Holy See (Vatican City State)"),
ENTRY_ISO_3166(Austria, "Austria"),
ENTRY_ISO_3166(Lithuania, "Lithuania"),
ENTRY_ISO_3166(Poland, "Poland"),
ENTRY_ISO_3166(Croatia, "Croatia"),
ENTRY_ISO_3166(Switzerland, "Switzerland"),
ENTRY_ISO_3166(Madagascar, "Madagascar"),
ENTRY_ISO_3166(BritishIndianOceanTerritory, "British Indian Ocean Territory"),
ENTRY_ISO_3166(ChristmasIsland, "Christmas Island"),
ENTRY_ISO_3166(CocosIslands, "Cocos (Keeling) Islands"),
ENTRY_ISO_3166(Comoros, "Comoros"),
ENTRY_ISO_3166(FrenchSouthernTerritories, "French Southern Territories"),
ENTRY_ISO_3166(Seychelles, "Seychelles"),
ENTRY_ISO_3166(Maldives, "Maldives"),
ENTRY_ISO_3166(Mauritius, "Mauritius"),
ENTRY_ISO_3166(Mayotte, "Mayotte"),
ENTRY_ISO_3166(Reunion, "Réunion"),
ENTRY_ISO_3166(Samoa, "Samoa"),
ENTRY_ISO_3166(NewZealand, "New Zealand"),
ENTRY_ISO_3166(Micronesia, "Micronesia, Federated States of"),
ENTRY_ISO_3166(Vanuatu, "Vanuatu"),
ENTRY_ISO_3166(Kiribati, "Kiribati"),
ENTRY_ISO_3166(TokelauCountry, "Tokelau"),
ENTRY_ISO_3166(Fiji, "Fiji"),
ENTRY_ISO_3166(TuvaluCountry, "Tuvalu"),
ENTRY_ISO_3166(FrenchPolynesia, "French Polynesia"),
ENTRY_ISO_3166(SolomonIslands, "Solomon Islands"),
ENTRY_ISO_3166(Guam, "Guam"),
ENTRY_ISO_3166(UnitedStatesMinorOutlyingIslands, "United States Minor Outlying Islands"),
ENTRY_ISO_3166(MarshallIslands, "Marshall Islands"),
ENTRY_ISO_3166(NauruCountry, "Nauru"),
ENTRY_ISO_3166(Niue, "Niue"),
ENTRY_ISO_3166(NorfolkIsland, "Norfolk Island"),
ENTRY_ISO_3166(NewCaledonia, "New Caledonia"),
ENTRY_ISO_3166(AmericanSamoa, "American Samoa"),
ENTRY_ISO_3166(Palau, "Palau"),
ENTRY_ISO_3166(Pitcairn, "Pitcairn"),
ENTRY_ISO_3166(PapuaNewGuinea, "Papua New Guinea"),
ENTRY_ISO_3166(CookIslands, "Cook Islands"),
ENTRY_ISO_3166(NorthernMarianaIslands, "Northern Mariana Islands"),
ENTRY_ISO_3166(Tonga, "Tonga"),
ENTRY_ISO_3166(WallisAndFutunaIslands, "Wallis and Futuna")
// {QLocale::Default, i18nc("This is a country name associated with a particular time zone in a zone selection dialog",
// "Default")} },
});
#undef ENTRY_ISO_3166
m_i18nContinents =
QHash<QString, QString>({{QStringLiteral("Africa"), i18nc("This is a continent/area associated with a particular timezone", "Africa")},
{QStringLiteral("America"), i18nc("This is a continent/area associated with a particular timezone", "America")},
{QStringLiteral("Antarctica"), i18nc("This is a continent/area associated with a particular timezone", "Antarctica")},
{QStringLiteral("Asia"), i18nc("This is a continent/area associated with a particular timezone", "Asia")},
{QStringLiteral("Atlantic"), i18nc("This is a continent/area associated with a particular timezone", "Atlantic")},
{QStringLiteral("Australia"), i18nc("This is a continent/area associated with a particular timezone", "Australia")},
{QStringLiteral("Europe"), i18nc("This is a continent/area associated with a particular timezone", "Europe")},
{QStringLiteral("Indian"), i18nc("This is a continent/area associated with a particular timezone", "Indian")},
{QStringLiteral("Pacific"), i18nc("This is a continent/area associated with a particular timezone", "Pacific")}});
m_isInitialized = true;
}

34
kcms/time/timezonesi18n.h Normal file
View file

@ -0,0 +1,34 @@
/*
SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef TIMEZONESI18N_H
#define TIMEZONESI18N_H
#include <QHash>
#include <QLocale>
#include <QObject>
class TimezonesI18n : public QObject
{
Q_OBJECT
public:
explicit TimezonesI18n(QObject *parent = nullptr);
Q_INVOKABLE QString i18nCity(const QString &city);
Q_INVOKABLE QString i18nContinents(const QString &continent);
Q_INVOKABLE QString i18nCountry(QLocale::Country country);
private:
void init();
QHash<QString, QString> m_i18nCities;
QHash<QString, QString> m_i18nContinents;
QHash<QLocale::Country, QString> m_i18nCountries;
bool m_isInitialized;
};
#endif // TIMEZONESI18N_H

View file

@ -0,0 +1,27 @@
set(MALIIT_KEYBOARD_LANGUAGES_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/maliit/keyboard2/languages" CACHE PATH "Directory containing maliit-keyboard data")
add_definitions(-DQT_NO_KEYWORDS)
set(onscreenkeyboard_SRCS
languagemodel.cpp
gsettingsitem.cpp
virtualkeyboard.cpp
)
add_library(kcm_mobile_onscreenkeyboard MODULE ${onscreenkeyboard_SRCS})
target_compile_definitions(kcm_mobile_onscreenkeyboard PRIVATE "MALIIT_KEYBOARD_LANGUAGES_DIR=\"${MALIIT_KEYBOARD_LANGUAGES_DIR}\"")
target_link_libraries(kcm_mobile_onscreenkeyboard
Qt::Core
KF6::CoreAddons
KF6::I18n
KF6::QuickAddons
KF6::ConfigCore
PkgConfig::GIO
PkgConfig::GOBJECT
)
install(TARGETS kcm_mobile_onscreenkeyboard DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
kpackage_install_package(package kcm_mobile_virtualkeyboard kcms)

View file

@ -0,0 +1 @@
$XGETTEXT $(find . -name \*.cpp -o -name \*.h -o -name \*.qml) -o $podir/kcm_mobile_virtualkeyboard.pot

View file

@ -0,0 +1,131 @@
/*
SPDX-FileCopyrightText: 2018 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-License-Identifier: LGPL-2.1-only
*/
#include <QDebug>
#include <QString>
#include "gsettingsitem.h"
QVariant GSettingsItem::value(const QString &key) const
{
if (!m_settings) {
return QVariant();
}
GVariant *gvalue = g_settings_get_value(m_settings, key.toLatin1().data());
QVariant toReturn;
switch (g_variant_classify(gvalue)) {
case G_VARIANT_CLASS_BOOLEAN:
toReturn = QVariant(static_cast<bool>(g_variant_get_boolean(gvalue)));
break;
case G_VARIANT_CLASS_STRING:
toReturn = QVariant(QString::fromUtf8(g_variant_get_string(gvalue, nullptr)));
break;
case G_VARIANT_CLASS_ARRAY:
if (g_variant_is_of_type(gvalue, G_VARIANT_TYPE_STRING_ARRAY)) {
GVariantIter iter;
QStringList list;
const gchar *str;
g_variant_iter_init(&iter, gvalue);
while (g_variant_iter_next(&iter, "&s", &str)) {
list.append(str);
}
return QVariant(list);
}
break;
default:
qWarning() << "Unhandled variant type in value()";
}
g_variant_unref(gvalue);
return toReturn;
}
void GSettingsItem::set(const QString &key, const QVariant &val)
{
if (!m_settings) {
return;
}
// It might be hard to detect the right GVariant type from
// complex QVariant types such as string lists or more detailed
// types such as integers (GVariant has different sizes),
// therefore we get the current value for the key and convert
// to QVariant using the GVariant type
GVariant *oldValue = g_settings_get_value(m_settings, key.toLatin1().data());
GVariant *newValue = nullptr;
switch (g_variant_type_peek_string(g_variant_get_type(oldValue))[0]) {
case G_VARIANT_CLASS_BOOLEAN:
newValue = g_variant_new_boolean(val.toBool());
break;
case G_VARIANT_CLASS_STRING:
newValue = g_variant_new_string(val.toString().toUtf8().constData());
break;
case G_VARIANT_CLASS_ARRAY:
if (g_variant_is_of_type(oldValue, G_VARIANT_TYPE_STRING_ARRAY)) {
const QStringList list = val.toStringList();
GVariantBuilder builder;
g_variant_builder_init(&builder, G_VARIANT_TYPE_STRING_ARRAY);
for (const QString &string : list) {
g_variant_builder_add(&builder, "s", string.toUtf8().constData());
}
newValue = g_variant_builder_end(&builder);
break;
}
qWarning() << "Unhandled variant array type in set()";
break;
default:
qWarning() << "Unhandled variant type in set()";
}
if (newValue) {
g_settings_set_value(m_settings, key.toLatin1().data(), newValue);
}
g_variant_unref(oldValue);
}
bool GSettingsItem::isValid() const
{
return m_settings;
}
GSettingsItem::GSettingsItem(const QString &key, QObject *parent)
: QObject(parent)
{
const char schemaId[] = "org.maliit.keyboard.maliit";
// g_settings_new_with_path asserts if the schema doesn't exist, check this manually to avoid an abort.
auto *defaultSource = g_settings_schema_source_get_default();
if (!defaultSource) {
qWarning() << "No GSettings schemas are installed on the system";
return;
}
auto *schema = g_settings_schema_source_lookup(defaultSource, schemaId, true /*recursive*/);
if (!schema) {
qWarning() << "Settings schema" << schemaId << "is not installed";
return;
}
m_settings = g_settings_new_with_path(schemaId, key.toLatin1().data());
g_settings_schema_unref(schema);
g_signal_connect(m_settings, "changed", G_CALLBACK(GSettingsItem::settingChanged), this);
}
GSettingsItem::~GSettingsItem()
{
g_settings_sync();
if (m_settings)
g_object_unref(m_settings);
}

View file

@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2018 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-License-Identifier: LGPL-2.1-only
*/
#ifndef GSETTINGSITEM_H
#define GSETTINGSITEM_H
#include <QObject>
#include <QStringList>
#include <QVariant>
#include <gio/gio.h>
class GSettingsItem : public QObject
{
Q_OBJECT
public:
explicit GSettingsItem(const QString &key, QObject *parent = nullptr);
virtual ~GSettingsItem() override;
QVariant value(const QString &key) const;
void set(const QString &key, const QVariant &val);
bool isValid() const;
Q_SIGNALS:
void subtreeChanged();
private:
GSettings *m_settings = nullptr;
static void settingChanged(GSettings *settings, const gchar *key, gpointer data)
{
Q_UNUSED(settings)
Q_UNUSED(key)
GSettingsItem *self = static_cast<GSettingsItem *>(data);
Q_EMIT self->subtreeChanged();
}
};
#endif // GCONFITEM_H

View file

@ -0,0 +1,104 @@
/*
SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include <QDebug>
#include <QDirIterator>
#include <QPluginLoader>
#include "gsettingsitem.h"
#include "languagemodel.h"
LanguageModel::LanguageModel(QObject *parent, GSettingsItem *settings)
: QAbstractListModel(parent)
, m_gsettings(settings)
{
beginResetModel();
loadPlugins();
endResetModel();
}
void LanguageModel::loadPlugins()
{
const QStringList enabledLangs = m_gsettings->value("enabled-languages").toStringList();
QStringList langPaths;
QDirIterator it(QStringLiteral(MALIIT_KEYBOARD_LANGUAGES_DIR), {"*plugin.so"}, QDir::NoFilter, QDirIterator::Subdirectories);
while (it.hasNext()) {
langPaths << it.next();
}
m_languages.clear();
for (const auto &langPath : qAsConst(langPaths)) {
QPluginLoader langPlugin(langPath);
const auto &metadata = langPlugin.metaData().value("MetaData").toObject();
Data lang;
lang.langName = metadata.value("Language").toString();
lang.langCode = metadata.value("LanguageId").toString();
lang.enabled = enabledLangs.contains(lang.langCode);
m_languages.append(lang);
}
}
QVariant LanguageModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.row() >= m_languages.size()) {
return QVariant();
}
const Data data = m_languages.at(index.row());
switch (role) {
case EnabledRole:
return data.enabled;
case NameRole:
return data.langName;
case LanguageIdRole:
return data.langCode;
}
return QVariant();
}
bool LanguageModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid()) {
return QAbstractListModel::setData(index, value, role);
}
if (role == EnabledRole) {
Data &data = m_languages[index.row()];
if (data.enabled != value.toBool()) {
data.enabled = value.toBool();
}
Q_EMIT dataChanged(this->index(index.row(), 0), this->index(index.row(), 0));
}
QStringList enabledLangs;
for (const auto &data : qAsConst(m_languages)) {
if (data.enabled) {
enabledLangs << data.langCode;
}
}
m_gsettings->set("enabled-languages", enabledLangs);
return QAbstractListModel::setData(index, value, role);
}
int LanguageModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_languages.size();
}
QHash<int, QByteArray> LanguageModel::roleNames() const
{
return {
{NameRole, "name"},
{EnabledRole, "enabled"},
{LanguageIdRole, "langId"},
};
}

View file

@ -0,0 +1,43 @@
/*
SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef LANGUAGEMODEL_H
#define LANGUAGEMODEL_H
#include "gsettingsitem.h"
#include <QAbstractListModel>
struct Data {
QString langCode;
QString langName;
bool enabled;
};
class LanguageModel : public QAbstractListModel
{
enum ModelRoles {
NameRole = Qt::DisplayRole,
EnabledRole = Qt::UserRole + 1,
LanguageIdRole,
};
Q_OBJECT
public:
LanguageModel(QObject *parent, GSettingsItem *gsettingsItem);
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
QHash<int, QByteArray> roleNames() const override;
private:
QVector<Data> m_languages;
void loadPlugins();
GSettingsItem *m_gsettings;
};
#endif

View file

@ -0,0 +1,153 @@
{
"KPlugin": {
"Authors": [
{
"Name": "Bhushan Shah",
"Name[az]": "Bhushan Shah",
"Name[ca@valencia]": "Bhushan Shah",
"Name[ca]": "Bhushan Shah",
"Name[cs]": "Bhushan Shah",
"Name[de]": "Bhushan Shah",
"Name[en_GB]": "Bhushan Shah",
"Name[es]": "Bhushan Shah",
"Name[eu]": "Bhushan Shah",
"Name[fi]": "Bhushan Shah",
"Name[fr]": "Bhushan Shah",
"Name[hu]": "Bhushan Shah",
"Name[ia]": "Bhushan Shah",
"Name[id]": "Bhushan Shah",
"Name[is]": "Bhushan Shah",
"Name[it]": "Bhushan Shah",
"Name[ka]": "Bhushan Shah",
"Name[ko]": "Bhushan Shah",
"Name[lt]": "Bhushan Shah",
"Name[nl]": "Bhushan Shah",
"Name[nn]": "Bhushan Shah",
"Name[pa]": "ਭੂਸ਼ਨ ਸ਼ਾਹ",
"Name[pl]": "Bhushan Shah",
"Name[pt]": "Bhushan Shah",
"Name[pt_BR]": "Bhushan Shah",
"Name[ro]": "Bhushan Shah",
"Name[ru]": "Bhushan Shah",
"Name[sl]": "Bhushan Shah",
"Name[sv]": "Bhushan Shah",
"Name[tr]": "Buşan Şah",
"Name[uk]": "Bhushan Shah",
"Name[vi]": "Bhushan Shah",
"Name[x-test]": "xxBhushan Shahxx",
"Name[zh_CN]": "Bhushan Shah",
"Name[zh_TW]": "Bhushan Shah"
}
],
"Description": "On-Screen Keyboard configuration",
"Description[az]": "Ekran klaviaturası tənzimləməsi",
"Description[ca@valencia]": "Configureu el teclat en pantalla",
"Description[ca]": "Configuració del teclat en pantalla",
"Description[cs]": "Nastavení klávesnice na obrazovce",
"Description[de]": "Einrichtung der Bildschirmtastatur",
"Description[en_GB]": "On-Screen Keyboard configuration",
"Description[es]": "Configuración del teclado en pantalla",
"Description[eu]": "Pantailako teklatua konfiguratzea",
"Description[fi]": "Näyttönäppäimistön asetukset",
"Description[fr]": "Configuration du clavier virtuel",
"Description[hu]": "Képernyő-billentyűzet beállítása",
"Description[ia]": "Configuration de Claviero sur le schermo",
"Description[id]": "Konfigurasi On-Screen Keyboard",
"Description[is]": "Grunnstillingar skjályklaborðs",
"Description[it]": "Configurazione della tastiera su schermo",
"Description[ka]": "კლავიატურის კრანზე მორგება",
"Description[ko]": "가상 키보드 설정",
"Description[lt]": "Ekraninės klaviatūros konfigūracija",
"Description[nl]": "Configuratie van toetsenbord op het scherm",
"Description[nn]": "Oppsett av skjermtastatur",
"Description[pa]": "ਆਨ-ਸਕਰੀਨ ਕੀਬੋਰਡ ਦੀ ਸੰਰਚਨਾ",
"Description[pl]": "Ustawienia klawiatury ekranowej",
"Description[pt]": "Configuração do Teclado Virtual",
"Description[pt_BR]": "Configuração do teclado virtual",
"Description[ro]": "Configurarea tastaturii pe ecran",
"Description[ru]": "Настройка экранной клавиатуры",
"Description[sl]": "Nastavitev tipkovnice na zaslonu",
"Description[sv]": "Inställning av skärmtangentbord",
"Description[tr]": "Ekran Klavyesi yapılandırması",
"Description[uk]": "Налаштування екранної клавіатури",
"Description[vi]": "Cấu hình bàn phím trên màn hình",
"Description[x-test]": "xxOn-Screen Keyboard configurationxx",
"Description[zh_CN]": "屏幕键盘配置",
"Description[zh_TW]": "螢幕上的虛擬鍵盤設定",
"FormFactors": [
"handset",
"tablet"
],
"Icon": "input-keyboard",
"License": "GPL",
"Name": "On-Screen Keyboard",
"Name[az]": "Ekran klaviaturası",
"Name[ca@valencia]": "Teclat en pantalla",
"Name[ca]": "Teclat en pantalla",
"Name[cs]": "Klávesnice na obrazovce",
"Name[de]": "Bildschirmtastatur",
"Name[en_GB]": "On-Screen Keyboard",
"Name[es]": "Teclado en pantalla",
"Name[eu]": "Pantailako teklatua",
"Name[fi]": "Näyttönäppäimistö",
"Name[fr]": "Clavier virtuel",
"Name[hu]": "Képernyő-billentyűzet",
"Name[ia]": "Claviero sur schermo",
"Name[id]": "On-Screen Keyboard",
"Name[is]": "Skjályklaborð",
"Name[it]": "Tastiera su schermo",
"Name[ka]": "ეკრანის კლავიატურა",
"Name[ko]": "가상 키보드",
"Name[lt]": "Ekraninė klaviatūra",
"Name[nl]": "Toetsenbord op scherm",
"Name[nn]": "Skjermtastatur",
"Name[pa]": "ਆਨ-ਸਕਰੀਨ ਕੀਬੋਰਡ",
"Name[pl]": "Klawiatura ekranowa",
"Name[pt]": "Teclado no Ecrã",
"Name[pt_BR]": "Teclado virtual",
"Name[ro]": "Tastatură pe ecran",
"Name[ru]": "Экранная клавиатура",
"Name[sl]": "Tipkovnica na zaslonu",
"Name[sv]": "Tangentbord på skärmen",
"Name[tr]": "Ekran Klavyesi",
"Name[uk]": "Екранна клавіатура",
"Name[vi]": "Bàn phím trên màn hình",
"Name[x-test]": "xxOn-Screen Keyboardxx",
"Name[zh_CN]": "屏幕键盘",
"Name[zh_TW]": "螢幕上的虛擬鍵盤",
"Version": "1.0",
"Website": "https://plasma-mobile.org"
},
"X-KDE-Keywords": "onscreen,osk,virtualkeyboard,keyboard",
"X-KDE-Keywords[az]": "virtualkeyboard,keyboard,virtual klaviatura",
"X-KDE-Keywords[ca@valencia]": "teclat virtual,teclat",
"X-KDE-Keywords[ca]": "teclat virtual,teclat",
"X-KDE-Keywords[cs]": "virtuální klávesnice, klávesnice",
"X-KDE-Keywords[de]": "Virtuelle Tastatur,Tastatur",
"X-KDE-Keywords[en_GB]": "virtualkeyboard,keyboard",
"X-KDE-Keywords[es]": "tecladovirtual,teclado",
"X-KDE-Keywords[eu]": "alegiazko teklatua,teklatua",
"X-KDE-Keywords[fi]": "näyttönäppäimistö,virtuaalinäppäimistö,näppäimistö",
"X-KDE-Keywords[fr]": "clavier virtuel, clavier",
"X-KDE-Keywords[hu]": "virtuális billentyűzet,billentyűzet",
"X-KDE-Keywords[ia]": "virtualkeyboard,keyboard",
"X-KDE-Keywords[it]": "tastieravirtuale,tastiera",
"X-KDE-Keywords[ko]": "virtualkeyboard,keyboard,키보드,가상 키보드",
"X-KDE-Keywords[lt]": "virtualiklaviatūra,virtualiklaviatura,virtuali klaviatūra,virtuali klaviatura,klaviatūra,klaviatura",
"X-KDE-Keywords[nl]": "virtueeltoetsenbord,toetsenbord",
"X-KDE-Keywords[nn]": "virtuelt tastatur,tastatur",
"X-KDE-Keywords[pa]": "ਫ਼ਰਜ਼ੀ ਕੀਬੋਰਡ,ਵਰਚੁਅਲ ਕੀਬੋਰਡ,ਖਾਕਾ,ਕੀਬੋਰਡ",
"X-KDE-Keywords[pl]": "klawiatura ekranowa,klawiatura",
"X-KDE-Keywords[pt]": "teclado virtual,teclado",
"X-KDE-Keywords[pt_BR]": "teclado virtual,teclado",
"X-KDE-Keywords[ro]": "tastatură virtuală,tastatură",
"X-KDE-Keywords[ru]": "virtualkeyboard,keyboard,клавиатура,виртуальная клавиатура",
"X-KDE-Keywords[sk]": "virtuálna klávesnica, klávesnica",
"X-KDE-Keywords[sl]": "navidezna tipkovnica,tipkovnica",
"X-KDE-Keywords[sv]": "virtuellt tangentbord,tangentbord",
"X-KDE-Keywords[ta]": "virtualkeyboard, keyboard, மெய்நிகர் விசைப்பலகை, விசைப்பலகை, திரையில் விசைப்பலகை, திரை விசைப்பலகை, திரைவிசைப்பலகை",
"X-KDE-Keywords[uk]": "virtualkeyboard,keyboard,екранна,віртуальна,клавіатура,клавіші",
"X-KDE-Keywords[vi]": "virtualkeyboard,keyboard,bàn phím ảo,bàn phím",
"X-KDE-Keywords[x-test]": "xxvirtualkeyboardxx,xxkeyboardxx",
"X-KDE-Keywords[zh_CN]": "virtualkeyboard,keyboard,虚拟键盘,键盘"
}

View file

@ -0,0 +1,52 @@
/*
SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.7
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.11 as QQC2
import org.kde.kirigami 2.10 as Kirigami
import org.kde.kcm 1.3 as KCM
import org.kde.kitemmodels 1.0 as KItemModel
import org.kde.kcm.virtualkeyboard 1.0
KCM.ScrollViewKCM {
id: root
title: i18n("Languages")
view: ListView {
id: languageList
clip: true
model: KItemModel.KSortFilterProxyModel {
sourceModel: kcm.languageModel
sortRole: "name"
sortOrder: Qt.Ascending
}
delegate: Kirigami.AbstractListItem {
QQC2.CheckBox {
text: model.name
checked: model.enabled
onCheckedChanged: {
model.enabled = checked
}
}
}
}
footer: RowLayout {
QQC2.Button {
text: i18n("Apply")
icon.name: "dialog-ok"
onClicked: kcm.pop()
Layout.alignment: Qt.AlignRight
}
}
}

View file

@ -0,0 +1,150 @@
/*
SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.7
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.11 as QQC2
import org.kde.kirigami 2.19 as Kirigami
import org.kde.kcm 1.3 as KCM
import org.kde.kitemmodels 1.0 as KItemModel
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
KCM.SimpleKCM {
id: root
title: i18n("On-Screen Keyboard")
leftPadding: 0
rightPadding: 0
topPadding: Kirigami.Units.gridUnit
bottomPadding: Kirigami.Units.gridUnit
ColumnLayout {
spacing: 0
width: parent.width
MobileForm.FormCard {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.AbstractFormDelegate {
Layout.fillWidth: true
background: Item {}
contentItem: RowLayout {
QQC2.TextField {
Layout.fillWidth: true
placeholderText: i18n("Type anything here…")
}
}
}
}
}
MobileForm.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: "Feedback"
}
MobileForm.FormSwitchDelegate {
id: firstFeedbackCheckBox
text: i18n("Sound")
description: i18n("Whether to emit a sound on keypress.")
checked: kcm.soundFeedback
onCheckedChanged: kcm.soundFeedback = checked;
}
MobileForm.FormDelegateSeparator { above: firstFeedbackCheckBox; below: secondFeedbackCheckBox }
MobileForm.FormSwitchDelegate {
id: secondFeedbackCheckBox
text: i18n("Vibration")
description: i18n("Whether to vibrate on keypress.")
checked: kcm.vibrateFeedback
onCheckedChanged: kcm.vibrateFeedback = checked;
}
}
}
MobileForm.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: "Text Correction"
}
MobileForm.FormCheckDelegate {
id: firstTextCorrectionCheckBox
text: i18n("Check spelling of entered text")
checked: kcm.spellCheck
onCheckedChanged: kcm.spellCheck = checked;
}
MobileForm.FormDelegateSeparator { above: firstTextCorrectionCheckBox; below: capitalizeCheck }
MobileForm.FormCheckDelegate {
id: capitalizeCheck
text: i18n("Capitalize the first letter of each sentence")
checked: kcm.autoCapitalize
onCheckedChanged: kcm.autoCapitalize = checked;
}
MobileForm.FormDelegateSeparator { above: capitalizeCheck; below: wordCompletionCheck }
MobileForm.FormCheckDelegate {
id: wordCompletionCheck
text: i18n("Complete current word with first suggestion when hitting space")
checked: kcm.autoCompleteOnSpace
onCheckedChanged: kcm.autoCompleteOnSpace = checked;
}
MobileForm.FormDelegateSeparator { above: wordCompletionCheck; below: wordSuggestionCheck }
MobileForm.FormCheckDelegate {
id: wordSuggestionCheck
text: i18n("Suggest potential words in word ribbon")
checked: kcm.showSuggestions
onCheckedChanged: {
kcm.showSuggestions = checked;
}
}
MobileForm.FormDelegateSeparator { above: wordSuggestionCheck; below: fullStopCheck }
MobileForm.FormCheckDelegate {
id: fullStopCheck
text: i18n("Insert a full-stop when space is pressed twice")
checked: kcm.fullStopOnDoubleSpace
onCheckedChanged: {
kcm.fullStopOnDoubleSpace = checked;
}
}
MobileForm.FormDelegateSeparator { above: fullStopCheck; below: languageButton }
MobileForm.FormButtonDelegate {
id: languageButton
text: i18n("Configure Languages")
icon.name: "set-language"
onClicked: kcm.push("languages.qml")
}
}
}
}
}

View file

@ -0,0 +1,57 @@
/*
SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "virtualkeyboard.h"
#include <KLocalizedString>
#include <KPluginFactory>
#include <QDir>
#include "languagemodel.h"
// clang-format off
#define SETTER(setter, member, gsetting, signal) \
void VirtualKeyboard::setter(bool enabled) \
{ \
if (member != enabled) {\
member = enabled; \
m_gsettings->set(gsetting, enabled); \
Q_EMIT signal();\
}\
}
// clang-format on
K_PLUGIN_CLASS_WITH_JSON(VirtualKeyboard, "metadata.json")
VirtualKeyboard::VirtualKeyboard(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, metaData, args)
, m_gsettings(new GSettingsItem("/org/maliit/keyboard/maliit/", parent))
, m_langModel(new LanguageModel(this, m_gsettings))
{
qmlRegisterAnonymousType<LanguageModel>("org.kde.kcm.virtualkeyboard", 1);
m_autoCapitalize = m_gsettings->value("auto-capitalization").toBool();
m_autoCompleteOnSpace = m_gsettings->value("auto-completion").toBool();
m_showSuggestions = m_gsettings->value("predictive-text").toBool();
m_fullStopOnDoubleSpace = m_gsettings->value("double-space-full-stop").toBool();
m_spellCheck = m_gsettings->value("spell-checking").toBool();
m_soundFeedback = m_gsettings->value("key-press-feedback").toBool();
m_vibrateFeedback = m_gsettings->value("key-press-haptic-feedback").toBool();
}
SETTER(setAutoCapitalize, m_autoCapitalize, "auto-capitalization", autoCapitalizeChanged)
SETTER(setAutoCompleteOnSpace, m_autoCompleteOnSpace, "auto-completion", autoCompleteOnSpaceChanged);
SETTER(setShowSuggestions, m_showSuggestions, "predictive-text", showSuggestionsChanged)
SETTER(setFullStopOnDoubleSpace, m_fullStopOnDoubleSpace, "double-space-full-stop", fullStopOnDoubleSpaceChanged)
SETTER(setSpellCheck, m_spellCheck, "spell-checking", spellCheckChanged)
SETTER(setSoundFeedback, m_soundFeedback, "key-press-feedback", soundFeedbackChanged)
SETTER(setVibrateFeedback, m_vibrateFeedback, "key-press-haptic-feedback", vibrateFeedbackChanged)
#include "virtualkeyboard.moc"

View file

@ -0,0 +1,67 @@
/*
SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "languagemodel.h"
#include <KQuickAddons/ConfigModule>
#include <QList>
#include <QString>
#ifndef VIRTUALKEYBOARD_H
#define VIRTUALKEYBOARD_H
class VirtualKeyboard : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(LanguageModel *languageModel MEMBER m_langModel CONSTANT)
Q_PROPERTY(bool spellCheck MEMBER m_spellCheck WRITE setSpellCheck NOTIFY spellCheckChanged)
Q_PROPERTY(bool autoCapitalize MEMBER m_autoCapitalize WRITE setAutoCapitalize NOTIFY autoCapitalizeChanged)
Q_PROPERTY(bool autoCompleteOnSpace MEMBER m_autoCompleteOnSpace WRITE setAutoCompleteOnSpace NOTIFY autoCompleteOnSpaceChanged)
Q_PROPERTY(bool showSuggestions MEMBER m_showSuggestions WRITE setShowSuggestions NOTIFY showSuggestionsChanged)
Q_PROPERTY(bool fullStopOnDoubleSpace MEMBER m_fullStopOnDoubleSpace WRITE setFullStopOnDoubleSpace NOTIFY fullStopOnDoubleSpaceChanged)
Q_PROPERTY(bool soundFeedback MEMBER m_soundFeedback WRITE setSoundFeedback NOTIFY soundFeedbackChanged)
Q_PROPERTY(bool vibrateFeedback MEMBER m_vibrateFeedback WRITE setVibrateFeedback NOTIFY vibrateFeedbackChanged)
public:
VirtualKeyboard(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args);
void setSpellCheck(bool enabled);
void setAutoCapitalize(bool enabled);
void setAutoCompleteOnSpace(bool enabled);
void setShowSuggestions(bool enabled);
void setFullStopOnDoubleSpace(bool enabled);
void setSoundFeedback(bool enabled);
void setVibrateFeedback(bool enabled);
Q_SIGNALS:
void spellCheckChanged();
void autoCapitalizeChanged();
void autoCompleteOnSpaceChanged();
void showSuggestionsChanged();
void fullStopOnDoubleSpaceChanged();
void soundFeedbackChanged();
void vibrateFeedbackChanged();
private:
GSettingsItem *m_gsettings;
LanguageModel *m_langModel;
// spell check
bool m_spellCheck;
bool m_autoCapitalize;
bool m_autoCompleteOnSpace;
bool m_showSuggestions;
bool m_fullStopOnDoubleSpace;
// feedback
bool m_soundFeedback;
bool m_vibrateFeedback;
};
#endif