Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing udev monitoring for rM1 #314

Merged
merged 25 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from 16 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
46 changes: 34 additions & 12 deletions applications/system-service/powerapi.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "powerapi.h"

#include <liboxide/udev.h>

PowerAPI* PowerAPI::singleton(PowerAPI* self){
static PowerAPI* instance;
if(self != nullptr){
Expand All @@ -9,7 +11,7 @@
}

PowerAPI::PowerAPI(QObject* parent)
: APIBase(parent), m_chargerState(ChargerUnknown){
: APIBase(parent), m_chargerState(ChargerUnknown){
Oxide::Sentry::sentry_transaction("power", "init", [this](Oxide::Sentry::Transaction* t){
Oxide::Sentry::sentry_span(t, "singleton", "Setup singleton", [this]{
singleton(this);
Expand All @@ -21,24 +23,44 @@
Oxide::Sentry::sentry_span(t, "update", "Update current state", [this]{
update();
});
Oxide::Sentry::sentry_span(t, "timer", "Setup timer", [this]{
timer = new QTimer(this);
timer->setSingleShot(false);
timer->setInterval(3 * 1000); // 3 seconds
timer->moveToThread(qApp->thread());
connect(timer, &QTimer::timeout, this, QOverload<>::of(&PowerAPI::update));
timer->start();
Oxide::Sentry::sentry_span(t, "monitor", "Setup monitor", [this]{
if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM1){
Oxide::UDev::singleton()->addMonitor("platform", NULL);
Oxide::UDev::singleton()->subsystem("power_supply", [this]{
update();
});
}else{
timer = new QTimer(this);
timer->setSingleShot(false);
timer->setInterval(3 * 1000); // 3 seconds
timer->moveToThread(qApp->thread());
connect(timer, &QTimer::timeout, this, QOverload<>::of(&PowerAPI::update));
Eeems marked this conversation as resolved.
Show resolved Hide resolved
timer->start();
}
});
});
}

PowerAPI::~PowerAPI(){
O_DEBUG("Killing timer");
timer->stop();
delete timer;
if(timer != nullptr){
qDebug() << "Killing timer";
timer->stop();
delete timer;
}else{
qDebug() << "Killing UDev monitor";
Oxide::UDev::singleton()->stop();
}
}

void PowerAPI::setEnabled(bool enabled) {
void PowerAPI::setEnabled(bool enabled){
if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM1){
if(enabled){
Oxide::UDev::singleton()->start();
}else{
Oxide::UDev::singleton()->stop();
}
return;
}
if(enabled){
timer->start();
}else{
Expand Down Expand Up @@ -113,60 +135,60 @@
void PowerAPI::setChargerState(int chargerState){
m_chargerState = chargerState;
emit chargerStateChanged(chargerState);
}

void PowerAPI::updateBattery(){
if(!Oxide::Power::batteries()->length()){
if(m_batteryState != BatteryUnknown){
setBatteryState(BatteryUnknown);
}
if(!m_batteryWarning){
O_WARNING("Can't find battery information");
m_batteryWarning = true;
emit batteryWarning();
}
return;
}
if(!Oxide::Power::batteryPresent()){
if(m_batteryState != BatteryNotPresent){
O_WARNING("Battery is somehow not in the device?");
setBatteryState(BatteryNotPresent);
}
if(!m_batteryWarning){
O_WARNING("Battery is somehow not in the device?");
m_batteryWarning = true;
emit batteryWarning();
}
return;
}
int battery_level = Oxide::Power::batteryLevel();
if(m_batteryLevel != battery_level){
setBatteryLevel(battery_level);
}
bool charging = Oxide::Power::batteryCharging();
if(charging && m_batteryState != BatteryCharging){
setBatteryState(BatteryCharging);
}else if(!charging && m_batteryState != BatteryDischarging){
setBatteryState(BatteryDischarging);
}
bool alert = Oxide::Power::batteryHasAlert();
if(m_batteryAlert != alert){
m_batteryAlert = alert;
if(alert){
emit batteryAlert();
}
}
bool warning = Oxide::Power::batteryHasWarning();
if(m_batteryWarning != warning){
if(warning){
emit batteryWarning();
}
m_batteryWarning = warning;
}
int temperature = Oxide::Power::batteryTemperature();
if(m_batteryTemperature != temperature){
setBatteryTemperature(temperature);
}

Check notice on line 191 in applications/system-service/powerapi.cpp

View check run for this annotation

codefactor.io / CodeFactor

applications/system-service/powerapi.cpp#L138-L191

Complex Method
}

void PowerAPI::updateCharger(){
Expand Down
2 changes: 1 addition & 1 deletion applications/system-service/powerapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class PowerAPI : public APIBase {
void chargerWarning();

private:
QTimer* timer;
QTimer* timer = nullptr;
int m_state = Normal;
int m_batteryState = BatteryUnknown;
int m_batteryLevel = 0;
Expand Down
4 changes: 3 additions & 1 deletion shared/liboxide/liboxide.pro
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ SOURCES += \
slothandler.cpp \
sysobject.cpp \
signalhandler.cpp \
udev.cpp \
xochitlsettings.cpp

HEADERS += \
Expand All @@ -56,6 +57,7 @@ HEADERS += \
slothandler.h \
sysobject.h \
signalhandler.h \
udev.h \
xochitlsettings.h

PRECOMPILED_HEADER = \
Expand All @@ -75,7 +77,7 @@ DBUS_INTERFACES += \
../../interfaces/notificationapi.xml \
../../interfaces/notification.xml

LIBS += -lsystemd
LIBS += -lsystemd -ludev

include(../../qmake/common.pri)

Expand Down
291 changes: 291 additions & 0 deletions shared/liboxide/udev.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
#include "udev.h"
#include "debug.h"
#include "liboxide.h"

#include <fcntl.h>
#include <cerrno>

#include <QtConcurrent/QtConcurrentRun>
#include <QThread>

namespace Oxide {

UDev* UDev::singleton(){
static UDev* instance = nullptr;
static std::once_flag initFlag;
std::call_once(initFlag, [](){
instance = new UDev();
instance->start();
});
return instance;
}

UDev::UDev() : QObject(), _thread(this){
qRegisterMetaType<Device>("UDev::Device");
udevLib = udev_new();
connect(&_thread, &QThread::started, [this]{
O_DEBUG("UDev::Thread started");
});
connect(&_thread, &QThread::finished, [this]{
O_DEBUG("UDev::Thread finished");
});
_thread.start(QThread::LowPriority);
moveToThread(&_thread);
Eeems marked this conversation as resolved.
Show resolved Hide resolved
}

UDev::~UDev(){
if(udevLib != nullptr){
udev_unref(udevLib);
udevLib = nullptr;
}
}

void UDev::subsystem(const QString& subsystem, std::function<void(const Device&)> callback){
deviceType(subsystem, NULL, callback);
Eeems marked this conversation as resolved.
Show resolved Hide resolved
}

void UDev::subsystem(const QString& subsystem, std::function<void()> callback){
deviceType(subsystem, NULL, callback);
}

void UDev::deviceType(const QString& subsystem, const QString& deviceType, std::function<void(const Device&)> callback){
connect(singleton(), &UDev::event, [callback, subsystem, deviceType](const Device& device){
if(
device.subsystem == subsystem
&& (
deviceType.isNull()
|| deviceType.isEmpty()
|| device.deviceType == deviceType
)
){
callback(device);
}
});
Eeems marked this conversation as resolved.
Show resolved Hide resolved
singleton()->addMonitor(subsystem, deviceType);
}

void UDev::deviceType(const QString& subsystem, const QString& deviceType, std::function<void()> callback){
UDev::deviceType(subsystem, deviceType, [callback](const Device& device){
Q_UNUSED(device);
callback();
});
}

void UDev::start(){
statelock.lock();
O_DEBUG("UDev::Starting...");
exitRequested = false;
if(running){
statelock.unlock();
O_DEBUG("UDev::Already running");
return;
}
QTimer::singleShot(0, [this](){
monitor();
statelock.unlock();
O_DEBUG("UDev::Started");
});
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved

void UDev::stop(){
statelock.lock();
O_DEBUG("UDev::Stopping...");
if(running){
exitRequested = true;
}
statelock.unlock();
}

bool UDev::isRunning(){ return running; }

void UDev::wait(){
if(isRunning()){
O_DEBUG("UDev::Waiting to stop...");
QEventLoop loop;
connect(this, &UDev::stopped, &loop, &QEventLoop::quit);
loop.exec();
}
}

void UDev::addMonitor(QString subsystem, QString deviceType){
O_DEBUG("UDev::Adding" << subsystem << deviceType);
auto& list = monitors[subsystem];
if (!list) {
list = QSharedPointer<QStringList>::create();
}
if(!list->contains(deviceType)){
list->append(deviceType);
update = true;
}
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved
void UDev::removeMonitor(QString subsystem, QString deviceType){
O_DEBUG("UDev::Removing" << subsystem << deviceType);
if(monitors.contains(subsystem)){
monitors[subsystem]->removeAll(deviceType);
update = true;
}

auto it = monitors.find(subsystem);
if(it != monitors.end() && *it){
(*it)->removeAll(deviceType);
update = true;
}
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved

QList<UDev::Device> UDev::getDeviceList(const QString& subsystem){
QList<Device> deviceList;
struct udev_enumerate* udevEnumeration = udev_enumerate_new(udevLib);
if(udevEnumeration == nullptr){
static std::once_flag onceFlag;
std::call_once(onceFlag, [](){
O_WARNING("Failed to enumerate udev");
});
return deviceList;
}
if(udev_enumerate_add_match_subsystem(udevEnumeration, subsystem.toUtf8().constData()) < 0){
O_WARNING("Failed to add subsystem");
return deviceList;
}
if(udev_enumerate_scan_devices(udevEnumeration) < 0){
O_WARNING("Failed to scan devices");
return deviceList;
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved
struct udev_list_entry* udevDeviceList = udev_enumerate_get_list_entry(udevEnumeration);
if(udevDeviceList != nullptr){
struct udev_list_entry* entry = nullptr;
udev_list_entry_foreach(entry, udevDeviceList){
if(entry == nullptr){
continue;
}
const char* path = udev_list_entry_get_name(entry);
if(path == nullptr){
continue;
}
Device device;
struct udev_device* udevDevice = udev_device_new_from_syspath(udevLib, path);
if(udevDevice == nullptr) {
O_WARNING("Failed to create udev device from syspath");
continue;
}
device.action = getActionType(udevDevice);
device.path = path;
device.subsystem = subsystem;
auto devType = udev_device_get_devtype(udevDevice);
device.deviceType = QString(devType ? devType : "");
udev_device_unref(udevDevice);
deviceList.append(device);
}
}
udev_enumerate_unref(udevEnumeration);
return deviceList;
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved

UDev::ActionType UDev::getActionType(udev_device* udevDevice){
if(udevDevice == nullptr){
return Unknown;
}
return getActionType(QString(udev_device_get_action(udevDevice)).trimmed().toUpper());
Eeems marked this conversation as resolved.
Show resolved Hide resolved
}

UDev::ActionType UDev::getActionType(const QString& actionType){
if(actionType == "ADD"){
return Add;
}
if(actionType == "REMOVE"){
return Remove;
}
if(actionType == "CHANGE"){
return Change;
}
if(actionType == "OFFLINE"){
return Offline;
}
if(actionType == "ONLINE"){
return Online;
}
return Unknown;
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved

void UDev::monitor(){
running = true;
O_DEBUG("UDev::Monitor starting...");
udev_monitor* mon = udev_monitor_new_from_netlink(udevLib, "udev");
if(!mon){
O_WARNING("UDev::Monitor Unable to listen to UDev: Failed to create netlink monitor");
O_DEBUG(strerror(errno))
return;
}
O_DEBUG("UDev::Monitor applying filters...");
for(QString subsystem : monitors.keys()){
for(QString deviceType : *monitors[subsystem]){
O_DEBUG("UDev::Monitor filter" << subsystem << deviceType);
int err = udev_monitor_filter_add_match_subsystem_devtype(
mon,
subsystem.toUtf8().constData(),
deviceType.isNull() || deviceType.isEmpty()
? NULL
: deviceType.toUtf8().constData()
);
if(err < 0){
O_WARNING("UDev::Monitor Unable to add filter: " << strerror(err));
}
}
}
O_DEBUG("UDev::Monitor enabling...");
int err = udev_monitor_enable_receiving(mon);
if(err < 0){
O_WARNING("UDev::Monitor Unable to listen to UDev:" << strerror(err));
udev_monitor_unref(mon);
return;
}
O_DEBUG("UDev::Monitor setting up timer...");
auto timer = new QTimer();
timer->setTimerType(Qt::PreciseTimer);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, [this, mon, timer]{
if(exitRequested){
O_DEBUG("UDev::Monitor stopping...");
udev_monitor_unref(mon);
timer->deleteLater();
running = false;
O_DEBUG("UDev::Stopped");
emit stopped();
return;
}
if(update || !mon){
O_DEBUG("UDev::Monitor reloading...");
update = false;

Check notice on line 257 in shared/liboxide/udev.cpp

View check run for this annotation

codefactor.io / CodeFactor

shared/liboxide/udev.cpp#L185-L257

Complex Method
udev_monitor_unref(mon);
timer->deleteLater();
QTimer::singleShot(0, this, &UDev::monitor);
return;
}
udev_device* dev = udev_monitor_receive_device(mon);
if(dev != nullptr){
Device device;
device.action = getActionType(dev);
auto devNode = udev_device_get_devnode(dev);
device.path = QString(devNode ? devNode : "");
auto devSubsystem = udev_device_get_subsystem(dev);
device.subsystem = QString(devSubsystem ? devSubsystem : "");
auto devType = udev_device_get_devtype(dev);
device.deviceType = QString(devType ? devType : "");
Eeems marked this conversation as resolved.
Show resolved Hide resolved
udev_device_unref(dev);
O_DEBUG("UDev::Monitor UDev event" << device);
emit event(device);
}else if(errno && errno != EAGAIN){
O_WARNING("UDev::Monitor error checking event:" << strerror(errno));
}
timer->start(30);
});
timer->start(30);
O_DEBUG("UDev::Monitor event loop started");
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved

QDebug operator<<(QDebug debug, const UDev::Device& device){
QDebugStateSaver saver(debug);
Q_UNUSED(saver)
debug.nospace() << device.debugString().c_str();
return debug.maybeSpace();
}
}
Loading
Loading