refactor: use the framework's MimeApps and BatteryStatus

main
reionwong 4 weeks ago
parent a41710c1d7
commit dc848f1d14

@ -22,7 +22,7 @@ pkg_check_modules(Xcursor REQUIRED IMPORTED_TARGET xcursor)
find_package(Libcrypt REQUIRED)
find_package(CutefishFramework REQUIRED COMPONENTS Application Appearance IO)
find_package(CutefishFramework REQUIRED COMPONENTS Application Appearance IO Power)
pkg_search_module(FontConfig REQUIRED fontconfig IMPORTED_TARGET)
pkg_search_module(ICU REQUIRED icu-i18n)
@ -56,7 +56,6 @@ set(SRCS
src/shortcuts/shortcutsmodel.cpp
src/networkproxy.cpp
src/defaultapplications.cpp
src/mimeapps.cpp
src/inputmethod.cpp
src/keyboard.cpp
src/mock/networkmockmodel.cpp
@ -77,6 +76,7 @@ target_link_libraries(${PROJECT_NAME}
Cutefish::Application
Cutefish::Appearance
Cutefish::IO
Cutefish::Power
Qt6::Core
Qt6::Widgets
Qt6::Quick

@ -3,11 +3,11 @@
#include "battery.h"
#include <QCoreApplication>
#include <QDBusArgument>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDBusReply>
#include <QDateTime>
#include <QDebug>
#include <QFile>
@ -15,52 +15,19 @@
#include <QSettings>
#include <QTimer>
#include <batterystatus.h>
#include <preferences.h>
static const QString s_sServer = "com.cutefish.Services";
static const QString s_sPath = "/com/cutefish/Services/Battery";
static const QString s_sInterface = "com.cutefish.Services.Battery";
static bool mockBatteryEnabled()
{
return QCoreApplication::arguments().contains(QStringLiteral("--mock-battery"));
}
// DBus Battery Info Structure
struct BatteryInfo {
uint time, state;
double value;
};
Q_DECLARE_METATYPE(BatteryInfo)
const QDBusArgument &operator<<(QDBusArgument &argument, const BatteryInfo &info)
{
argument.beginStructure();
argument << info.time << info.value << info.state;
argument.endStructure();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument, BatteryInfo &info)
{
argument.beginStructure();
argument >> info.time >> info.value >> info.state;
argument.endStructure();
return argument;
}
Battery::Battery(QObject *parent)
: QObject(parent)
, m_upowerInterface("org.freedesktop.UPower", "/org/freedesktop/UPower",
"org.freedesktop.UPower", QDBusConnection::systemBus())
, m_interface("com.cutefish.Services", "/com/cutefish/Services/Battery",
"com.cutefish.Services.Battery", QDBusConnection::sessionBus())
, m_available(false)
, m_onBattery(false)
, m_status(BatteryStatus::instance())
{
if (mockBatteryEnabled()) {
m_available = true;
m_onBattery = true;
m_udi = QStringLiteral("mock-battery");
m_chargeLimitSupported = true;
m_chargeLimitStart = 75;
@ -68,100 +35,56 @@ Battery::Battery(QObject *parent)
return;
}
m_available = m_interface.isValid() && !m_interface.lastError().isValid();
if (m_available) {
QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface,
"chargeStateChanged", this,
SLOT(chargeStateChanged(int)));
QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface,
"chargePercentChanged", this,
SLOT(chargePercentChanged(int)));
QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface,
"lastChargedPercentChanged", this,
SLOT(lastChargedPercentChanged()));
QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface, "capacityChanged",
this, SLOT(capacityChanged(int)));
QDBusConnection::sessionBus().connect(s_sServer, s_sPath, s_sInterface,
"remainingTimeChanged", this,
SLOT(remainingTimeChanged(qlonglong)));
QDBusInterface interface("org.freedesktop.UPower", "/org/freedesktop/UPower",
"org.freedesktop.UPower", QDBusConnection::systemBus());
QDBusConnection::systemBus().connect(
"org.freedesktop.UPower", "/org/freedesktop/UPower",
"org.freedesktop.DBus.Properties", "PropertiesChanged", this,
SLOT(onPropertiesChanged(QString, QVariantMap, QStringList)));
if (interface.isValid()) {
m_onBattery = interface.property("OnBattery").toBool();
}
connect(m_status, &BatteryStatus::readyChanged, this, &Battery::readyChanged);
connect(m_status, &BatteryStatus::availableChanged, this, &Battery::validChanged);
connect(m_status, &BatteryStatus::onBatteryChanged, this, &Battery::onBatteryChanged);
connect(m_status, &BatteryStatus::chargeStateChanged, this, &Battery::chargeStateChanged);
connect(m_status, &BatteryStatus::chargePercentChanged, this, &Battery::chargePercentChanged);
connect(m_status, &BatteryStatus::capacityChanged, this, &Battery::capacityChanged);
connect(m_status, &BatteryStatus::statusStringChanged, this, &Battery::remainingTimeChanged);
connect(m_status, &BatteryStatus::lastChargedChanged, this,
&Battery::lastChargedPercentChanged);
connect(m_status, &BatteryStatus::udiChanged, this, &Battery::watchDevice);
watchDevice();
}
void Battery::watchDevice()
{
if (m_status->udi() == m_udi)
return;
m_udi = m_interface.property("udi").toString();
QDBusConnection bus = QDBusConnection::systemBus();
if (!m_udi.isEmpty())
bus.disconnect("org.freedesktop.UPower", m_udi, "org.freedesktop.DBus.Properties",
"PropertiesChanged", this,
SLOT(onDevicePropertiesChanged(QString, QVariantMap, QStringList)));
if (!m_udi.isEmpty()) {
QDBusConnection::systemBus().connect(
"org.freedesktop.UPower", m_udi, "org.freedesktop.DBus.Properties",
m_udi = m_status->udi();
if (!m_udi.isEmpty()) {
bus.connect("org.freedesktop.UPower", m_udi, "org.freedesktop.DBus.Properties",
"PropertiesChanged", this,
SLOT(onDevicePropertiesChanged(QString, QVariantMap, QStringList)));
loadChargeLimit();
}
emit udiChanged();
emit validChanged();
loadChargeLimit();
}
}
bool Battery::available() const
{
return m_available;
emit udiChanged();
}
bool Battery::onBattery() const
bool Battery::ready() const
{
return m_onBattery;
return mockBatteryEnabled() || m_status->ready();
}
void Battery::refresh()
bool Battery::available() const
{
if (mockBatteryEnabled())
return;
m_interface.call("refresh");
return mockBatteryEnabled() || m_status->available();
}
QVariantList Battery::getHistory(const int timespan, const int resolution)
bool Battery::onBattery() const
{
QVariantList listValues;
QVariantMap listItem;
QDBusMessage historyMessage = QDBusMessage::createMethodCall(
"org.freedesktop.UPower", m_udi, "org.freedesktop.UPower.Device", "GetHistory");
QVariantList historyMessageArguments;
historyMessageArguments.append("charge");
historyMessageArguments.append((uint)timespan);
historyMessageArguments.append((uint)resolution);
historyMessage.setArguments(historyMessageArguments);
quint64 offset = QDateTime::currentDateTime().toSecsSinceEpoch();
QDBusReply<QDBusArgument> historyArgument = QDBusConnection::systemBus().call(historyMessage);
if (historyArgument.isValid()) {
QDBusArgument arrayArgument = historyArgument.value();
arrayArgument.beginArray();
while (!arrayArgument.atEnd()) {
BatteryInfo info;
arrayArgument >> info;
listItem.insert("time", offset - info.time);
listItem.insert("value", info.value);
if (info.value > 0)
listValues += listItem;
}
}
return listValues;
return mockBatteryEnabled() || m_status->onBattery();
}
int Battery::chargeState() const
@ -169,7 +92,7 @@ int Battery::chargeState() const
if (mockBatteryEnabled())
return 2;
return m_interface.property("chargeState").toInt();
return m_status->chargeState();
}
int Battery::chargePercent() const
@ -177,7 +100,7 @@ int Battery::chargePercent() const
if (mockBatteryEnabled())
return 78;
return m_interface.property("chargePercent").toInt();
return m_status->chargePercent();
}
int Battery::lastChargedPercent() const
@ -185,7 +108,7 @@ int Battery::lastChargedPercent() const
if (mockBatteryEnabled())
return 100;
return m_interface.property("lastChargedPercent").toInt();
return m_status->lastChargedPercent();
}
int Battery::capacity() const
@ -193,7 +116,7 @@ int Battery::capacity() const
if (mockBatteryEnabled())
return 94;
return m_interface.property("capacity").toInt();
return m_status->capacity();
}
QString Battery::statusString() const
@ -201,13 +124,13 @@ QString Battery::statusString() const
if (mockBatteryEnabled())
return tr("Discharging · 3 hours 24 minutes remaining");
return m_interface.property("statusString").toString();
return m_status->statusString();
}
QString Battery::lastChargedTime() const
{
const qint64 secs = mockBatteryEnabled() ? QDateTime::currentSecsSinceEpoch() - 2 * 3600
: m_interface.property("lastChargedSecs").toLongLong();
: m_status->lastChargedSecs();
if (secs <= 0)
return QString();
@ -369,21 +292,6 @@ void Battery::updateChargeLimit(const QVariantMap &props)
QTimer::singleShot(5000, this, &Battery::readKernelChargeLimit);
}
void Battery::onPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps,
const QStringList &invalidatedProps)
{
Q_UNUSED(ifaceName);
Q_UNUSED(changedProps);
Q_UNUSED(invalidatedProps);
bool onBattery = m_upowerInterface.property("OnBattery").toBool();
if (onBattery != m_onBattery) {
m_onBattery = onBattery;
m_interface.call("refresh");
emit onBatteryChanged();
}
}
void Battery::onDevicePropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps,
const QStringList &invalidatedProps)
{

@ -5,11 +5,15 @@
#define BATTERY_H
#include <QObject>
#include <QDBusInterface>
#include <QVariantMap>
class BatteryStatus;
// The battery page's view of cutefish-services' battery, plus UPower's charge limit.
class Battery : public QObject
{
Q_OBJECT
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
Q_PROPERTY(bool available READ available NOTIFY validChanged)
Q_PROPERTY(int chargeState READ chargeState NOTIFY chargeStateChanged)
Q_PROPERTY(int chargePercent READ chargePercent NOTIFY chargePercentChanged)
@ -29,12 +33,10 @@ class Battery : public QObject
public:
explicit Battery(QObject *parent = nullptr);
bool ready() const;
bool available() const;
bool onBattery() const;
Q_INVOKABLE void refresh();
Q_INVOKABLE QVariantList getHistory(const int timespan, const int resolution);
int chargeState() const;
int chargePercent() const;
int lastChargedPercent() const;
@ -58,25 +60,24 @@ public:
QString udi() const;
signals:
void readyChanged();
void validChanged();
void chargeStateChanged(int);
void chargePercentChanged(int);
void capacityChanged(int);
void remainingTimeChanged(qlonglong time);
void chargeStateChanged();
void chargePercentChanged();
void capacityChanged();
void remainingTimeChanged();
void onBatteryChanged();
void lastChargedPercentChanged();
void udiChanged();
void chargeLimitChanged();
private slots:
void onPropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps);
void onDevicePropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps);
private:
QDBusInterface m_upowerInterface;
QDBusInterface m_interface;
bool m_available;
bool m_onBattery;
void watchDevice();
BatteryStatus *m_status;
QString m_udi;
bool m_chargeLimitSupported = false;

@ -8,7 +8,6 @@
#include <QDebug>
#include <QFileInfo>
#include <QStandardPaths>
DefaultApplications::DefaultApplications(QObject *parent)
: QObject(parent)
@ -50,12 +49,12 @@ void DefaultApplications::loadApps()
}
const QString defaultBrowser =
MimeApps::associations(mimeAppsListFilePath(), "Default Applications",
MimeApps::associations(MimeApps::userListPath(), "Default Applications",
"x-scheme-handler/http").value(0);
const QString defaultEMail =
MimeApps::associations(mimeAppsListFilePath(), "Default Applications",
MimeApps::associations(MimeApps::userListPath(), "Default Applications",
"x-scheme-handler/mailto").value(0);
const QString defaultTerminal = MimeApps::defaultTerminal(terminalsListFilePath());
const QString defaultTerminal = MimeApps::defaultTerminal(MimeApps::userTerminalsListPath());
// Init indexes.
for (int i = 0; i < m_browserList.size(); ++i) {
@ -168,7 +167,8 @@ void DefaultApplications::setDefaultTerminal(int index)
if (index < 0 || index >= m_terminalList.size())
return;
if (!MimeApps::setDefaultTerminal(terminalsListFilePath(), m_terminalList.at(index).fileName)) {
if (!MimeApps::setDefaultTerminal(MimeApps::userTerminalsListPath(),
m_terminalList.at(index).fileName)) {
qWarning() << "Failed to save the default terminal";
return;
}
@ -177,21 +177,9 @@ void DefaultApplications::setDefaultTerminal(int index)
void DefaultApplications::setDefaultApp(const QString &mimeType, const QString &path)
{
if (!MimeApps::setDefault(mimeAppsListFilePath(), mimeType, path)) {
if (!MimeApps::setDefault(MimeApps::userListPath(), mimeType, path)) {
qWarning() << "Failed to save default application for" << mimeType;
return;
}
loadApps();
}
QString DefaultApplications::mimeAppsListFilePath() const
{
return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)
+ QLatin1String("/mimeapps.list");
}
QString DefaultApplications::terminalsListFilePath() const
{
return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)
+ QLatin1String("/xdg-terminals.list");
}

@ -46,8 +46,6 @@ public:
private:
void setDefaultApp(const QString &mimeType, const QString &path);
QString mimeAppsListFilePath() const;
QString terminalsListFilePath() const;
signals:
void loadFinished();

@ -1,148 +0,0 @@
#include "mimeapps.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLockFile>
#include <QSaveFile>
namespace {
QStringList readEntries(const QStringList &lines, const QString &group, const QString &key)
{
QString current;
for (const QString &line : lines) {
const QString trimmed = line.trimmed();
if (trimmed.startsWith('[') && trimmed.endsWith(']'))
current = trimmed.mid(1, trimmed.size() - 2);
else if (current == group && !trimmed.startsWith('#')) {
const auto equals = line.indexOf('=');
if (equals >= 0 && line.left(equals).trimmed() == key)
return line.mid(equals + 1).trimmed().split(';', Qt::SkipEmptyParts);
}
}
return {};
}
void writeEntry(QStringList &lines, const QString &group, const QString &key,
const QStringList &values)
{
const QString entry = key + '=' + values.join(';') + ';';
QString current;
int insertion = -1;
for (int i = 0; i < lines.size(); ++i) {
const QString trimmed = lines.at(i).trimmed();
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
if (current == group)
break;
current = trimmed.mid(1, trimmed.size() - 2);
if (current == group)
insertion = i + 1;
} else if (current == group) {
const auto equals = lines.at(i).indexOf('=');
if (equals >= 0 && lines.at(i).left(equals).trimmed() == key) {
lines.removeAt(i--);
} else {
insertion = i + 1;
}
}
}
if (insertion < 0) {
lines.append('[' + group + ']');
insertion = lines.size();
}
lines.insert(insertion, entry);
}
// An entry may carry a "+" (include) or "-" (exclude) prefix and an ":action" suffix.
QString terminalId(const QString &line)
{
QString entry = line.trimmed();
if (entry.startsWith('+') || entry.startsWith('-'))
entry.remove(0, 1);
return entry.section(':', 0, 0);
}
} // namespace
QStringList MimeApps::associations(const QString &path, const QString &group,
const QString &mimeType)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly))
return {};
return readEntries(QString::fromUtf8(file.readAll()).split('\n'), group, mimeType);
}
bool MimeApps::setDefault(const QString &path, const QString &mimeType, const QString &desktopId)
{
if (mimeType.isEmpty() || mimeType.contains('\n') || mimeType.contains('\r')
|| mimeType.contains('=') || desktopId.isEmpty() || desktopId.contains('\n')
|| desktopId.contains('\r') || desktopId.contains(';'))
return false;
if (!QDir().mkpath(QFileInfo(path).absolutePath()))
return false;
QLockFile lock(path + ".lock");
if (!lock.tryLock())
return false;
QFile input(path);
QStringList lines;
if (input.exists()) {
if (!input.open(QIODevice::ReadOnly))
return false;
lines = QString::fromUtf8(input.readAll()).split('\n');
input.close();
}
auto added = readEntries(lines, "Added Associations", mimeType);
added.removeAll(desktopId);
added.prepend(desktopId);
writeEntry(lines, "Default Applications", mimeType, {desktopId});
writeEntry(lines, "Added Associations", mimeType, added);
auto removed = readEntries(lines, "Removed Associations", mimeType);
if (removed.removeAll(desktopId))
writeEntry(lines, "Removed Associations", mimeType, removed);
QSaveFile output(path);
if (!output.open(QIODevice::WriteOnly))
return false;
const auto data = (lines.join('\n') + '\n').toUtf8();
return output.write(data) == data.size() && output.commit();
}
QString MimeApps::defaultTerminal(const QString &path)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly))
return {};
for (const QString &line : QString::fromUtf8(file.readAll()).split('\n')) {
const QString entry = line.trimmed();
if (!entry.isEmpty() && !entry.startsWith('#') && !entry.startsWith('-'))
return terminalId(entry);
}
return {};
}
bool MimeApps::setDefaultTerminal(const QString &path, const QString &desktopId)
{
if (desktopId.isEmpty() || desktopId.contains('\n') || desktopId.contains('\r'))
return false;
if (!QDir().mkpath(QFileInfo(path).absolutePath()))
return false;
QLockFile lock(path + ".lock");
if (!lock.tryLock())
return false;
QFile input(path);
QStringList lines;
if (input.exists()) {
if (!input.open(QIODevice::ReadOnly))
return false;
lines = QString::fromUtf8(input.readAll()).split('\n', Qt::SkipEmptyParts);
input.close();
}
lines.removeIf([&desktopId](const QString &line) {
return !line.trimmed().startsWith('#') && terminalId(line) == desktopId;
});
lines.prepend(desktopId);
QSaveFile output(path);
if (!output.open(QIODevice::WriteOnly))
return false;
const auto data = (lines.join('\n') + '\n').toUtf8();
return output.write(data) == data.size() && output.commit();
}

@ -1,12 +0,0 @@
#pragma once
#include <QString>
#include <QStringList>
namespace MimeApps {
QStringList associations(const QString &path, const QString &group, const QString &mimeType);
bool setDefault(const QString &path, const QString &mimeType, const QString &desktopId);
// xdg-terminals.list of the xdg-terminal-exec spec: desktop entry IDs, most preferred first.
QString defaultTerminal(const QString &path);
bool setDefaultTerminal(const QString &path, const QString &desktopId);
} // namespace MimeApps

@ -896,10 +896,14 @@ Item {
Battery {
id: _battery
Component.onCompleted: {
if (!_battery.available)
// The service is read in the background; until then there is no battery either way.
function syncBattery() {
if (ready && !available)
removeItem("battery")
}
onReadyChanged: syncBattery()
Component.onCompleted: syncBattery()
}
Touchpad {

@ -4,7 +4,7 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD 17)
find_package(Qt6 REQUIRED COMPONENTS Core Test)
find_package(CutefishFramework REQUIRED COMPONENTS Application)
add_executable(tst_mimeapps tst_mimeapps.cpp ../../src/mimeapps.cpp ../../src/defaultapplications.cpp)
add_executable(tst_mimeapps tst_mimeapps.cpp ../../src/defaultapplications.cpp)
target_include_directories(tst_mimeapps PRIVATE ../../src)
target_link_libraries(tst_mimeapps PRIVATE Qt6::Core Qt6::Test Cutefish::Application)
enable_testing()

@ -68,52 +68,6 @@ private slots:
QCOMPARE(reopened.emailIndex(), 0);
QCOMPARE(reopened.terminalIndex(), 0);
}
void preservesAssociations()
{
QTemporaryDir dir;
const QString path = dir.filePath("mimeapps.list");
QFile file(path);
QVERIFY(file.open(QIODevice::WriteOnly));
file.write("# Keep this comment\n[Default Applications]\nimage/png=image.desktop;\n"
"[Added Associations]\ntext/plain=old.desktop;new.desktop;\n"
"[Removed Associations]\ntext/plain=new.desktop;blocked.desktop;\n");
file.close();
QVERIFY(MimeApps::setDefault(path, "text/plain", "new.desktop"));
QVERIFY(MimeApps::setDefault(path, "text/plain", "new.desktop"));
QCOMPARE(MimeApps::associations(path, "Default Applications", "text/plain"),
QStringList {"new.desktop"});
QCOMPARE(MimeApps::associations(path, "Default Applications", "image/png"),
QStringList {"image.desktop"});
QCOMPARE(MimeApps::associations(path, "Added Associations", "text/plain"),
QStringList({"new.desktop", "old.desktop"}));
QCOMPARE(MimeApps::associations(path, "Removed Associations", "text/plain"),
QStringList {"blocked.desktop"});
QVERIFY(file.open(QIODevice::ReadOnly));
const auto contents = file.readAll();
QVERIFY(contents.contains("# Keep this comment"));
QVERIFY(contents.contains("text/plain=new.desktop;"));
QVERIFY(!contents.contains("text%2Fplain"));
QVERIFY(!MimeApps::setDefault(path, "text/plain", "bad;entry.desktop"));
QVERIFY(!MimeApps::setDefault(dir.path(), "text/plain", "new.desktop"));
}
void terminalListPrependsTheDefault()
{
QTemporaryDir dir;
const QString path = dir.filePath("xdg-terminals.list");
QFile file(path);
QVERIFY(file.open(QIODevice::WriteOnly));
file.write("# Keep this comment\n-blocked.desktop\nfirst.desktop\n+new.desktop:new-window\n");
file.close();
QCOMPARE(MimeApps::defaultTerminal(path), QStringLiteral("first.desktop"));
QVERIFY(MimeApps::setDefaultTerminal(path, "new.desktop"));
QCOMPARE(MimeApps::defaultTerminal(path), QStringLiteral("new.desktop"));
QVERIFY(file.open(QIODevice::ReadOnly));
QCOMPARE(file.readAll(), QByteArray("new.desktop\n# Keep this comment\n-blocked.desktop\n"
"first.desktop\n"));
QVERIFY(!MimeApps::setDefaultTerminal(path, "bad\nentry.desktop"));
}
};
QTEST_GUILESS_MAIN(MimeAppsTest)
#include "tst_mimeapps.moc"

Loading…
Cancel
Save