diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c61e095..b5422aba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.12' - name: Install toltecmk run: pip install toltecmk requests==2.26.0 - name: Build packages diff --git a/applications/system-service/powerapi.cpp b/applications/system-service/powerapi.cpp index c7f60b53..3f4773f9 100644 --- a/applications/system-service/powerapi.cpp +++ b/applications/system-service/powerapi.cpp @@ -1,5 +1,7 @@ #include "powerapi.h" +#include + PowerAPI* PowerAPI::singleton(PowerAPI* self){ static PowerAPI* instance; if(self != nullptr){ @@ -9,7 +11,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 API Init", "init", [this](Oxide::Sentry::Transaction* t){ Oxide::Sentry::sentry_span(t, "singleton", "Setup singleton", [this]{ singleton(this); @@ -21,24 +23,44 @@ 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){ + Oxide::UDev::singleton()->addMonitor("platform", NULL); + Oxide::UDev::singleton()->subsystem("power_supply", [this]{ + QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); + }); + }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::~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{ diff --git a/applications/system-service/powerapi.h b/applications/system-service/powerapi.h index a1172247..67ec1711 100644 --- a/applications/system-service/powerapi.h +++ b/applications/system-service/powerapi.h @@ -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; diff --git a/shared/liboxide/liboxide.pro b/shared/liboxide/liboxide.pro index 05e0b620..73bbf319 100644 --- a/shared/liboxide/liboxide.pro +++ b/shared/liboxide/liboxide.pro @@ -34,6 +34,7 @@ SOURCES += \ slothandler.cpp \ sysobject.cpp \ signalhandler.cpp \ + udev.cpp \ xochitlsettings.cpp HEADERS += \ @@ -56,6 +57,7 @@ HEADERS += \ slothandler.h \ sysobject.h \ signalhandler.h \ + udev.h \ xochitlsettings.h PRECOMPILED_HEADER = \ @@ -75,7 +77,7 @@ DBUS_INTERFACES += \ ../../interfaces/notificationapi.xml \ ../../interfaces/notification.xml -LIBS += -lsystemd +LIBS += -lsystemd -ludev include(../../qmake/common.pri) diff --git a/shared/liboxide/udev.cpp b/shared/liboxide/udev.cpp new file mode 100644 index 00000000..a2916412 --- /dev/null +++ b/shared/liboxide/udev.cpp @@ -0,0 +1,289 @@ +#include "udev.h" +#include "debug.h" +#include "liboxide.h" + +#include +#include + +#include +#include + +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("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); + } + + UDev::~UDev(){ + if(udevLib != nullptr){ + udev_unref(udevLib); + udevLib = nullptr; + } + } + + void UDev::subsystem(const QString& subsystem, std::function callback){ + deviceType(subsystem, "", callback); + } + + void UDev::subsystem(const QString& subsystem, std::function callback){ + deviceType(subsystem, "", callback); + } + + void UDev::deviceType(const QString& subsystem, const QString& deviceType, std::function callback){ + connect(singleton(), &UDev::event, [callback, subsystem, deviceType](const Device& device){ + if( + device.subsystem == subsystem + && ( + deviceType.isNull() + || deviceType.isEmpty() + || device.deviceType == deviceType + ) + ){ + callback(device); + } + }); + singleton()->addMonitor(subsystem, deviceType); + } + + void UDev::deviceType(const QString& subsystem, const QString& deviceType, std::function 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"); + }); + } + + 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); + QStringList& list = monitors[subsystem]; + if(!list.contains(deviceType)){ + list.append(deviceType); + update = true; + } + } + void UDev::removeMonitor(QString subsystem, QString deviceType){ + O_DEBUG("UDev::Removing" << subsystem << deviceType); + if(!monitors.contains(subsystem)){ + return; + } + monitors[subsystem].removeAll(deviceType); + if(monitors[subsystem].isEmpty()){ + monitors.remove(subsystem); + } + update = true; + } + + QList UDev::getDeviceList(const QString& subsystem){ + QList 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"); + udev_enumerate_unref(udevEnumeration); + return deviceList; + } + if(udev_enumerate_scan_devices(udevEnumeration) < 0){ + O_WARNING("Failed to scan devices"); + udev_enumerate_unref(udevEnumeration); + return deviceList; + } + 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; + } + + UDev::ActionType UDev::getActionType(udev_device* udevDevice){ + if(udevDevice == nullptr){ + return Unknown; + } + auto devType = udev_device_get_action(udevDevice); + return getActionType(QString(devType ? devType : "").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::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; + 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 : ""); + 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"); + } + + 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 00000000..5a18487a --- /dev/null +++ b/shared/liboxide/udev.h @@ -0,0 +1,90 @@ +/*! + * \addtogroup Oxide + * @{ + * \file + */ +#pragma once + +#include "liboxide_global.h" + +#include + +#include +#include + +namespace Oxide { + class UDev : public QObject { + Q_OBJECT + + public: + static UDev* singleton(); + explicit UDev(); + ~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"; + } + } + std::string debugString() const { + return QString("").arg(subsystem, deviceType, actionString()).toStdString(); + } + }; + static void subsystem(const QString& subsystem, std::function callback); + static void subsystem(const QString& subsystem, std::function callback); + static void deviceType(const QString& subsystem, const QString& deviceType, std::function callback); + static void deviceType(const QString& subsystem, const QString& deviceType, std::function callback); + 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(const Device& device); + void stopped(); + + private: + struct udev* udevLib = nullptr; + bool running = false; + bool exitRequested = false; + bool update = false; + QMap monitors; + QThread _thread; + QMutex statelock; + + protected: + void monitor(); + }; + QDebug operator<<(QDebug debug, const UDev::Device& device); +} +Q_DECLARE_METATYPE(Oxide::UDev::Device)