mouse baterry

This commit is contained in:
2026-08-04 21:01:38 +02:00
parent db0aa8c0aa
commit 420dff0fc5
12 changed files with 892 additions and 1 deletions
+2
View File
@@ -8,6 +8,7 @@
#include "widgets/clock.hpp"
#include "widgets/controlCenter/controlCenter.hpp"
#include "widgets/date.hpp"
#include "widgets/mouseBattery.hpp"
#include "widgets/tray.hpp"
#include "widgets/volumeWidget.hpp"
#include "widgets/webWidget.hpp"
@@ -28,6 +29,7 @@ class Bar : public Gtk::Window {
Clock clock;
Date date;
MouseBatteryWidget mouseBattery;
WebWidget homeAssistant{Icon::HOME_ASSISTANT, "Home Assistant", "https://home.rivercry.com"};
ControlCenter controlCenter{Icon::MENU, "Control Center"};
+11 -1
View File
@@ -36,6 +36,11 @@ class Icon {
DONE_ALL,
REMOVE_DONE,
BATTERY_FULL,
BATTERY_ALERT,
BATTERY_CHARGING_FULL,
BATTERY_UNKNOWN,
};
static const std::string toString(Type type) {
@@ -71,8 +76,13 @@ class Icon {
{VERIFIED, "\uef76"},
{VERIFIED_OFF, "\uf30e"},
{DONE_ALL, "\ue877"},
{REMOVE_DONE, "\ue9d3"},
{BATTERY_FULL, "\ue1a5"},
{BATTERY_ALERT, "\ue19c"},
{BATTERY_CHARGING_FULL, "\ue1a3"},
{BATTERY_UNKNOWN, "\ue1a6"},
};
};
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include <cstdint>
#include <optional>
#include <string>
namespace hidpp {
/// Battery state of a wireless Logitech device.
struct BatteryStatus {
enum class Level : uint8_t { UNKNOWN,
CRITICAL,
LOW,
GOOD,
FULL };
bool connected = false;
int percentage = -1; // 0..100, or -1 when the device only reports a level
Level level = Level::UNKNOWN;
bool charging = false;
bool externalPower = false;
};
/// Reads the battery level of a Logitech HID++ 2.0 device.
///
/// Prefers the power_supply entry that hid-logitech-hidpp exports, and falls
/// back to speaking HID++ 2.0 over /dev/hidraw* when that driver is not bound
/// (the receiver then sits on hid-generic and exports no battery at all).
///
/// read() blocks on wireless round-trips that can take seconds when the device
/// is asleep, so it must be called off the GTK main thread.
class BatteryReader {
public:
explicit BatteryReader(std::string deviceName);
~BatteryReader();
BatteryReader(const BatteryReader &) = delete;
BatteryReader &operator=(const BatteryReader &) = delete;
BatteryStatus read();
private:
/// A resolved HID++ endpoint: which hidraw node, which device slot on it,
/// and the feature index the battery lives behind.
struct Endpoint {
int fd = -1;
uint8_t deviceIndex = 0;
uint8_t featureIdx = 0;
uint16_t featureId = 0; // kFeatureUnifiedBattery or kFeatureBatteryStatus
};
std::string deviceName;
Endpoint endpoint{};
std::optional<BatteryStatus> readFromSysfs() const;
std::optional<BatteryStatus> readFromHidpp();
/// Finds the hidraw node and device slot our device answers on. Expensive:
/// only called on first use and after the cached endpoint stops replying.
bool resolveEndpoint();
void closeEndpoint();
};
} // namespace hidpp
+57
View File
@@ -0,0 +1,57 @@
#pragma once
#include <condition_variable>
#include <glibmm/dispatcher.h>
#include <memory>
#include <mutex>
#include <sigc++/sigc++.h>
#include <string>
#include <thread>
#include "services/hidppBattery.hpp"
/// Polls the mouse battery once for the whole application.
///
/// The bar builds one window per monitor, so the widget cannot own the polling
/// itself without every monitor opening its own HID++ session against the same
/// receiver and competing for it. This service keeps a single worker thread and
/// fans the result out to every widget through updated().
class MouseBatteryService {
public:
static constexpr const char *kDeviceName = "MX Ergo S";
static std::shared_ptr<MouseBatteryService> getInstance();
~MouseBatteryService();
MouseBatteryService(const MouseBatteryService &) = delete;
MouseBatteryService &operator=(const MouseBatteryService &) = delete;
/// Last reading. Safe to call from the main thread.
hidpp::BatteryStatus getStatus() const;
/// Emitted on the main thread whenever a new reading lands.
sigc::signal<void()> &updated() { return updatedSignal; }
/// Stops the worker thread; called from the application's shutdown handler
/// so it cannot outlive the main loop the dispatcher posts to.
void stop();
private:
explicit MouseBatteryService(std::string deviceName);
void poll();
hidpp::BatteryReader reader;
Glib::Dispatcher dispatcher;
sigc::signal<void()> updatedSignal;
std::thread worker;
mutable std::mutex mutex;
std::condition_variable wakeup;
bool running = true;
hidpp::BatteryStatus status{};
inline static std::shared_ptr<MouseBatteryService> instance = nullptr;
};
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <gtkmm/box.h>
#include <gtkmm/label.h>
#include <memory>
#include <sigc++/sigc++.h>
#include "services/mouseBatteryService.hpp"
/// Shows the battery level of the wireless mouse tracked by MouseBatteryService.
class MouseBatteryWidget : public Gtk::Box {
public:
MouseBatteryWidget();
~MouseBatteryWidget() override;
private:
Gtk::Label icon;
Gtk::Label label;
std::shared_ptr<MouseBatteryService> service;
sigc::connection updatedConn;
void refresh();
};