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 10 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
48 changes: 33 additions & 15 deletions applications/system-service/powerapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ PowerAPI* PowerAPI::singleton(PowerAPI* self){
}

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,29 +21,47 @@ PowerAPI::PowerAPI(QObject* parent)
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){
UDev::singleton()->addMonitor("platform", NULL);
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";
UDev::singleton()->stop();
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved
}

void PowerAPI::setEnabled(bool enabled) {
if(enabled){
timer->start();
}else{
timer->stop();
}
if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM1){
UDev::singleton()->start();
}else{
timer->start();
}
}else if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM1){
UDev::singleton()->stop();
}else{
timer->stop();
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved
}

int PowerAPI::state(){
Expand Down
3 changes: 2 additions & 1 deletion applications/system-service/powerapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define BATTERYAPI_H

#include <liboxide.h>
#include <liboxide/udev.h>

#include <QObject>
#include <QDebug>
Expand Down Expand Up @@ -63,7 +64,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
261 changes: 261 additions & 0 deletions shared/liboxide/udev.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
#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;
Eeems marked this conversation as resolved.
Show resolved Hide resolved
if(instance == nullptr){
instance = new UDev();
instance->start();
}
return instance;
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved

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 == NULL || device.deviceType == deviceType)){
callback(device);
}
});
singleton()->addMonitor(subsystem, deviceType);
Eeems marked this conversation as resolved.
Show resolved Hide resolved
Eeems marked this conversation as resolved.
Show resolved Hide resolved
}

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...");
while(running){
qApp->processEvents();
}
}
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved

void UDev::addMonitor(QString subsystem, QString deviceType){
O_DEBUG("UDev::Adding" << subsystem << deviceType);
QStringList* list;
if(monitors.contains(subsystem)){
list = monitors[subsystem];
}else{
list = new QStringList();
monitors.insert(subsystem, list);
}
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;
}
}

QList<UDev::Device> UDev::getDeviceList(const QString& subsystem){
QList<Device> deviceList;
struct udev_enumerate* udevEnumeration = udev_enumerate_new(udevLib);
if(udevEnumeration == nullptr){
return deviceList;
}
udev_enumerate_add_match_subsystem(udevEnumeration, subsystem.toUtf8().constData());
udev_enumerate_scan_devices(udevEnumeration);
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);
device.action = getActionType(udevDevice);
device.path = path;
device.subsystem = subsystem;
device.deviceType = QString(udev_device_get_devtype(udevDevice));
Eeems marked this conversation as resolved.
Show resolved Hide resolved
udev_device_unref(udevDevice);
deviceList.append(device);
}
}
udev_enumerate_unref(udevEnumeration);
return deviceList;
}

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;
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved
if(actionType == "OFFLINE"){
return Offline;
}
if(actionType == "ONLINE"){
return Online;
}
return Unknown;
}

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() ? deviceType.toUtf8().constData() : NULL
);
if(err < 0){
O_WARNING("UDev::Monitor Unable to add filter: " << strerror(err));
}
Eeems marked this conversation as resolved.
Show resolved Hide resolved
}
}
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");
return;
}
if(update || !mon){
O_DEBUG("UDev::Monitor reloading...");
update = false;
udev_monitor_unref(mon);
timer->deleteLater();
QTimer::singleShot(0, [this]{
monitor();
});
return;
}
udev_device* dev = udev_monitor_receive_device(mon);
if(dev != nullptr){
Device device;
device.action = getActionType(dev);
device.path = QString(udev_device_get_devnode(dev));
device.subsystem = QString(udev_device_get_subsystem(dev));
device.deviceType = QString(udev_device_get_devtype(dev));
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