From c1429443babbdec25036a4e9a75a8c0f32c7d337 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 30 Jun 2023 23:49:02 -0600 Subject: [PATCH] Start implementing udev monitoring --- applications/system-service/application.cpp | 2 +- applications/system-service/powerapi.h | 46 +++-- shared/liboxide/debug.h | 1 + shared/liboxide/liboxide.pro | 8 +- shared/liboxide/udev.cpp | 193 ++++++++++++++++++++ shared/liboxide/udev.h | 83 +++++++++ 6 files changed, 317 insertions(+), 16 deletions(-) create mode 100644 shared/liboxide/udev.cpp create mode 100644 shared/liboxide/udev.h diff --git a/applications/system-service/application.cpp b/applications/system-service/application.cpp index 6c3f55922..16d5804c7 100644 --- a/applications/system-service/application.cpp +++ b/applications/system-service/application.cpp @@ -515,7 +515,7 @@ void Application::showSplashScreen(){ EPFrameBuffer::sendUpdate(textRect, EPFrameBuffer::Grayscale, EPFrameBuffer::PartialUpdate, true); painter.end(); }); - qDebug() << "Waitng for screen to finish..."; + qDebug() << "Waiting for screen to finish..."; Oxide::Sentry::sentry_span(t, "wait", "Wait for screen finish updating", [](){ EPFrameBuffer::waitForLastUpdate(); }); diff --git a/applications/system-service/powerapi.h b/applications/system-service/powerapi.h index c3eabdf67..17947bbb8 100644 --- a/applications/system-service/powerapi.h +++ b/applications/system-service/powerapi.h @@ -2,6 +2,7 @@ #define BATTERYAPI_H #include +#include #include #include @@ -46,25 +47,45 @@ class PowerAPI : public APIBase { 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 = new UDev(this); + udev->addMonitor("power_supply", NULL); + connect(udev, &UDev::event, this, QOverload<>::of(&PowerAPI::update)); + udev->start(); + }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)); + timer->start(); + } }); }); } ~PowerAPI(){ - qDebug() << "Killing timer"; - timer->stop(); - delete timer; + if(timer != nullptr){ + qDebug() << "Killing timer"; + timer->stop(); + delete timer; + } + if(udev != nullptr){ + qDebug() << "Killing UDev monitor"; + udev->stop(); + delete udev; + } } void setEnabled(bool enabled) override { if(enabled){ - timer->start(); + if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM1){ + udev->start(); + }else{ + timer->start(); + } + }else if(deviceSettings.getDeviceType() == Oxide::DeviceSettings::RM1){ + udev->stop(); }else{ timer->stop(); } @@ -152,7 +173,8 @@ class PowerAPI : public APIBase { void chargerWarning(); private: - QTimer* timer; + QTimer* timer = nullptr; + UDev* udev = nullptr; int m_state = Normal; int m_batteryState = BatteryUnknown; int m_batteryLevel = 0; diff --git a/shared/liboxide/debug.h b/shared/liboxide/debug.h index 84644e83c..873346fa4 100644 --- a/shared/liboxide/debug.h +++ b/shared/liboxide/debug.h @@ -13,6 +13,7 @@ * \brief Log a debug message if compiled with DEBUG mode, and debugging is enabled * \param msg Debug message to log */ +#define DEBUG #ifdef DEBUG #define O_DEBUG(msg) if(Oxide::debugEnabled()){ qDebug() << msg; } #else diff --git a/shared/liboxide/liboxide.pro b/shared/liboxide/liboxide.pro index d5526977f..8bee136c1 100644 --- a/shared/liboxide/liboxide.pro +++ b/shared/liboxide/liboxide.pro @@ -23,7 +23,8 @@ SOURCES += \ settingsfile.cpp \ slothandler.cpp \ sysobject.cpp \ - signalhandler.cpp + signalhandler.cpp \ + udev.cpp HEADERS += \ applications.h \ @@ -40,7 +41,8 @@ HEADERS += \ settingsfile.h \ slothandler.h \ sysobject.h \ - signalhandler.h + signalhandler.h \ + udev.h PRECOMPILED_HEADER = \ liboxide_stable.h @@ -59,7 +61,7 @@ DBUS_INTERFACES += \ ../../interfaces/notificationapi.xml \ ../../interfaces/notification.xml -LIBS += -lsystemd +LIBS += -lsystemd -ludev liboxide_liboxide_h.target = include/liboxide/liboxide.h liboxide_liboxide_h.commands = \ diff --git a/shared/liboxide/udev.cpp b/shared/liboxide/udev.cpp new file mode 100644 index 000000000..d4d09d6be --- /dev/null +++ b/shared/liboxide/udev.cpp @@ -0,0 +1,193 @@ +#include "udev.h" +#include "debug.h" + +#include +#include + +#include +#include + +namespace Oxide { + + UDev::UDev(QObject *parent) : QObject(), _thread(this){ + qRegisterMetaType("UDev::Device"); + udevLib = udev_new(); + connect(parent, &QObject::destroyed, this, &QObject::deleteLater); + connect(&_thread, &QThread::started, this, &UDev::run); + connect(&_thread, &QThread::finished, [this]{ + running = false; + }); + moveToThread(&_thread); + } + + UDev::~UDev(){ + if(udevLib != nullptr){ + udev_unref(udevLib); + udevLib = nullptr; + } + } + + void UDev::start(){ + if(running){ + return; + } + wait(); + qDebug() << "UDev::Starting..."; + running = true; + _thread.start(); + qDebug() << "UDev::Started"; + } + + void UDev::stop(){ + qDebug() << "UDev::Stopping..."; + if(running){ + running = false; + wait(); + } + qDebug() << "UDev::Stopped"; + } + + bool UDev::isRunning(){ return running || _thread.isRunning(); } + + void UDev::wait(){ + qDebug() << "UDev::Waiting..."; + while(isRunning()){ + thread()->yieldCurrentThread(); + } + qDebug() << "UDev::Waited"; + } + + void UDev::addMonitor(QString subsystem, QString 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); + } + } + void UDev::removeMonitor(QString subsystem, QString deviceType){ + if(monitors.contains(subsystem)){ + monitors[subsystem]->removeAll(deviceType); + } + } + + QList UDev::getDeviceList(const QString& subsystem){ + QList 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)); + 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()); + } + + 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; + } + + void UDev::run(){ + 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; + } + for(QString subsystem : monitors.keys()){ + for(QString deviceType : *monitors[subsystem]){ + O_DEBUG("UDev::Monitor " << 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)); + } + } + } + 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; + } + while(running){ + 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)); + } + auto timestamp = QDateTime::currentMSecsSinceEpoch(); + QThread::yieldCurrentThread(); + if(QDateTime::currentMSecsSinceEpoch() - timestamp < 30){ + QThread::msleep(30); + } + } + O_DEBUG("UDev::Monitor stopping"); + udev_monitor_unref(mon); + } + QDebug operator<<(QDebug debug, const UDev::Device& device){ + QDebugStateSaver saver(debug); + Q_UNUSED(saver) + debug.nospace() << device.debugString().c_str(); + return debug.maybeSpace(); + } +} diff --git a/shared/liboxide/udev.h b/shared/liboxide/udev.h new file mode 100644 index 000000000..86a3cf691 --- /dev/null +++ b/shared/liboxide/udev.h @@ -0,0 +1,83 @@ +/*! + * \addtogroup Oxide + * @{ + * \file + */ +#pragma once + +#include "liboxide_global.h" + +#include + +#include + +using namespace std; + +namespace Oxide { + class UDev : public QObject { + Q_OBJECT + + public: + static void ensureMaxThreads(); + explicit UDev(QObject *parent = nullptr); + ~UDev(); + + enum ActionType { + Add, + Remove, + Change, + Online, + Offline, + Unknown + }; + struct Device { + QString subsystem; + QString deviceType; + QString path; + ActionType action = Unknown; + QString actionString() const { + switch(action){ + case Add: + return "ADD"; + case Remove: + return "REMOVE"; + case Change: + return "CHANGE"; + case Online: + return "ONLINE"; + case Offline: + return "OFFLINE"; + case Unknown: + default: + return "UNKNOWN"; + } + } + string debugString() const { + return QString("").arg(subsystem, deviceType, actionString()).toStdString(); + } + }; + void start(); + void stop(); + bool isRunning(); + void wait(); + void addMonitor(QString subsystem, QString deviceType); + void removeMonitor(QString subsystem, QString deviceType); + QList getDeviceList(const QString& subsystem); + ActionType getActionType(udev_device* udevDevice); + ActionType getActionType(const QString& actionType); + + signals: + void event(Device device); + + private: + struct udev* udevLib = nullptr; + bool running = false; + QMap monitors; + QThread _thread; + + protected: + void run(); + }; + QDebug operator<<(QDebug debug, const UDev::Device& device); +} +Q_DECLARE_METATYPE(Oxide::UDev::Device)