Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ option(SERVICE_PIPEWIRE "PipeWire service" ON)
option(SERVICE_MPRIS "Mpris service" ON)
option(SERVICE_PAM "Pam service" ON)
option(SERVICE_GREETD "Greet service" ON)
option(SERVICE_UPOWER "UPower service" ON)

message(STATUS "Quickshell configuration")
message(STATUS " Jemalloc: ${USE_JEMALLOC}")
Expand All @@ -43,6 +44,7 @@ message(STATUS " PipeWire: ${SERVICE_PIPEWIRE}")
message(STATUS " Mpris: ${SERVICE_MPRIS}")
message(STATUS " Pam: ${SERVICE_PAM}")
message(STATUS " Greetd: ${SERVICE_GREETD}")
message(STATUS " UPower: ${SERVICE_UPOWER}")
message(STATUS " Hyprland: ${HYPRLAND}")
if (HYPRLAND)
message(STATUS " IPC: ${HYPRLAND_IPC}")
Expand Down
4 changes: 4 additions & 0 deletions src/services/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ endif()
if (SERVICE_GREETD)
add_subdirectory(greetd)
endif()

if (SERVICE_UPOWER)
add_subdirectory(upower)
endif()
39 changes: 39 additions & 0 deletions src/services/upower/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
set_source_files_properties(org.freedesktop.UPower.xml PROPERTIES
CLASSNAME DBusUPowerService
NO_NAMESPACE TRUE
)

qt_add_dbus_interface(DBUS_INTERFACES
org.freedesktop.UPower.xml
dbus_service
)

set_source_files_properties(org.freedesktop.UPower.Device.xml PROPERTIES
CLASSNAME DBusUPowerDevice
NO_NAMESPACE TRUE
)

qt_add_dbus_interface(DBUS_INTERFACES
org.freedesktop.UPower.Device.xml
dbus_device
)

qt_add_library(quickshell-service-upower STATIC
core.cpp
device.cpp
${DBUS_INTERFACES}
)

# dbus headers
target_include_directories(quickshell-service-upower PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

qt_add_qml_module(quickshell-service-upower
URI Quickshell.Services.UPower
VERSION 0.1
)

target_link_libraries(quickshell-service-upower PRIVATE ${QT_DEPS} quickshell-dbus)
target_link_libraries(quickshell PRIVATE quickshell-service-upowerplugin)

qs_pch(quickshell-service-upower)
qs_pch(quickshell-service-upowerplugin)
165 changes: 165 additions & 0 deletions src/services/upower/core.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#include "core.hpp"

#include <qcontainerfwd.h>
#include <qdbusconnection.h>
#include <qdbusconnectioninterface.h>
#include <qdbusextratypes.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qdbusservicewatcher.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qqmllist.h>
#include <qtmetamacros.h>

#include "../../core/model.hpp"
#include "../../dbus/properties.hpp"
#include "dbus_service.h"
#include "device.hpp"

namespace qs::service::upower {

Q_LOGGING_CATEGORY(logUPower, "quickshell.service.upower", QtWarningMsg);

UPower::UPower() {
qCDebug(logUPower) << "Starting UPower";

auto bus = QDBusConnection::systemBus();

if (!bus.isConnected()) {
qCWarning(logUPower) << "Could not connect to DBus. UPower service will not work.";
return;
}

this->service =
new DBusUPowerService("org.freedesktop.UPower", "/org/freedesktop/UPower", bus, this);

if (!this->service->isValid()) {
qCWarning(logUPower) << "Cannot connect to the UPower service.";
return;
}

QObject::connect(
&this->pOnBattery,
&dbus::AbstractDBusProperty::changed,
this,
&UPower::onBatteryChanged
);

this->serviceProperties.setInterface(this->service);
this->serviceProperties.updateAllViaGetAll();

this->registerExisting();
}

void UPower::registerExisting() {
this->registerDevice("/org/freedesktop/UPower/devices/DisplayDevice");

auto pending = this->service->EnumerateDevices();
auto* call = new QDBusPendingCallWatcher(pending, this);

auto responseCallback = [this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<QList<QDBusObjectPath>> reply = *call;

if (reply.isError()) {
qCWarning(logUPower) << "Failed to enumerate devices:" << reply.error().message();
} else {
for (const QDBusObjectPath& devicePath: reply.value()) {
this->registerDevice(devicePath.path());
}
}

delete call;
};

QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}

void UPower::onDeviceReady() {
auto* device = qobject_cast<UPowerDevice*>(this->sender());

if (device->path() == "/org/freedesktop/UPower/devices/DisplayDevice") {
this->mDisplayDevice = device;
emit this->displayDeviceChanged();
qCDebug(logUPower) << "Display UPowerDevice" << device->path() << "ready";
return;
}

this->readyDevices.insertObject(device);
qCDebug(logUPower) << "UPowerDevice" << device->path() << "ready";
}

void UPower::onDeviceDestroyed(QObject* object) {
auto* device = static_cast<UPowerDevice*>(object); // NOLINT

this->mDevices.remove(device->path());

if (device == this->mDisplayDevice) {
this->mDisplayDevice = nullptr;
emit this->displayDeviceChanged();
qCDebug(logUPower) << "Display UPowerDevice" << device->path() << "destroyed";
return;
}

this->readyDevices.removeObject(device);
qCDebug(logUPower) << "UPowerDevice" << device->path() << "destroyed";
}

void UPower::registerDevice(const QString& path) {
if (this->mDevices.contains(path)) {
qCDebug(logUPower) << "Skipping duplicate registration of UPowerDevice" << path;
return;
}

auto* device = new UPowerDevice(path, this);
if (!device->isValid()) {
qCWarning(logUPower) << "Ignoring invalid UPowerDevice registration of" << path;
delete device;
return;
}

this->mDevices.insert(path, device);
QObject::connect(device, &UPowerDevice::ready, this, &UPower::onDeviceReady);
QObject::connect(device, &QObject::destroyed, this, &UPower::onDeviceDestroyed);

qCDebug(logUPower) << "Registered UPowerDevice" << path;
}

UPowerDevice* UPower::displayDevice() { return this->mDisplayDevice; }

ObjectModel<UPowerDevice>* UPower::devices() { return &this->readyDevices; }

bool UPower::onBattery() const { return this->pOnBattery.get(); }

UPower* UPower::instance() {
static UPower* instance = new UPower(); // NOLINT
return instance;
}

UPowerQml::UPowerQml(QObject* parent): QObject(parent) {
QObject::connect(
UPower::instance(),
&UPower::displayDeviceChanged,
this,
&UPowerQml::displayDeviceChanged
);
QObject::connect(
UPower::instance(),
&UPower::onBatteryChanged,
this,
&UPowerQml::onBatteryChanged
);
}

UPowerDevice* UPowerQml::displayDevice() { // NOLINT
return UPower::instance()->displayDevice();
}

ObjectModel<UPowerDevice>* UPowerQml::devices() { // NOLINT
return UPower::instance()->devices();
}

bool UPowerQml::onBattery() { return UPower::instance()->onBattery(); }

} // namespace qs::service::upower
77 changes: 77 additions & 0 deletions src/services/upower/core.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#pragma once

#include <qdbusservicewatcher.h>
#include <qhash.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qqmllist.h>
#include <qtmetamacros.h>

#include "../../core/model.hpp"
#include "../../dbus/properties.hpp"
#include "dbus_service.h"
#include "device.hpp"

namespace qs::service::upower {

class UPower: public QObject {
Q_OBJECT;

public:
[[nodiscard]] UPowerDevice* displayDevice();
[[nodiscard]] ObjectModel<UPowerDevice>* devices();
[[nodiscard]] bool onBattery() const;

static UPower* instance();

signals:
void displayDeviceChanged();
void onBatteryChanged();

private slots:
void onDeviceReady();
void onDeviceDestroyed(QObject* object);

private:
explicit UPower();

void registerExisting();
void registerDevice(const QString& path);

UPowerDevice* mDisplayDevice = nullptr;
QHash<QString, UPowerDevice*> mDevices;
ObjectModel<UPowerDevice> readyDevices {this};

dbus::DBusPropertyGroup serviceProperties;
dbus::DBusProperty<bool> pOnBattery {this->serviceProperties, "OnBattery"};

DBusUPowerService* service = nullptr;
};

class UPowerQml: public QObject {
Q_OBJECT;
QML_NAMED_ELEMENT(UPower);
QML_SINGLETON;
/// UPower's DisplayDevice for your system. Can be `null`.
///
/// This is an aggregate device and not a physical one, meaning you will not find it in `devices`.
/// It is typically the device that is used for displaying information in desktop environments.
Q_PROPERTY(UPowerDevice* displayDevice READ displayDevice NOTIFY displayDeviceChanged);
/// All connected UPower devices.
Q_PROPERTY(ObjectModel<UPowerDevice>* devices READ devices CONSTANT);
/// If the system is currently running on battery power, or discharging.
Q_PROPERTY(bool onBattery READ onBattery NOTIFY onBatteryChanged);

public:
explicit UPowerQml(QObject* parent = nullptr);

[[nodiscard]] UPowerDevice* displayDevice();
[[nodiscard]] ObjectModel<UPowerDevice>* devices();
[[nodiscard]] static bool onBattery();

signals:
void displayDeviceChanged();
void onBatteryChanged();
};

} // namespace qs::service::upower
Loading