Merge pull request #4208 from markx86/feat/battery-events

battery: Add support for battery level related events
This commit is contained in:
Alexis Rouillard
2025-08-08 08:54:32 +02:00
committed by GitHub
4 changed files with 56 additions and 6 deletions

View File

@ -35,6 +35,7 @@ class Battery : public ALabel {
std::tuple<uint8_t, float, std::string, float, uint16_t, float> getInfos(); std::tuple<uint8_t, float, std::string, float, uint16_t, float> getInfos();
const std::string formatTimeRemaining(float hoursRemaining); const std::string formatTimeRemaining(float hoursRemaining);
void setBarClass(std::string&); void setBarClass(std::string&);
void processEvents(std::string& state, std::string& status, uint8_t capacity);
int global_watch; int global_watch;
std::map<fs::path, int> batteries_; std::map<fs::path, int> batteries_;
@ -43,6 +44,7 @@ class Battery : public ALabel {
int global_watch_fd_; int global_watch_fd_;
std::mutex battery_list_mutex_; std::mutex battery_list_mutex_;
std::string old_status_; std::string old_status_;
std::string last_event_;
bool warnFirstTime_{true}; bool warnFirstTime_{true};
const Bar& bar_; const Bar& bar_;

View File

@ -116,7 +116,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y
*menu-file*: ++ *menu-file*: ++
typeof: string ++ typeof: string ++
Location of the menu descriptor file. There need to be an element of type Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu* GtkMenu with id *menu*.
*menu-actions*: ++ *menu-actions*: ++
typeof: array ++ typeof: array ++
@ -127,6 +127,10 @@ The *battery* module displays the current capacity and state (eg. charging) of y
default: false ++ default: false ++
Enables this module to consume all left over space dynamically. Enables this module to consume all left over space dynamically.
*events*: ++
typeof: object ++
Specifies commands to be executed on specific battery states. See *EVENTS* section below.
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
*{capacity}*: Capacity in percentage *{capacity}*: Capacity in percentage
@ -166,6 +170,19 @@ The *battery* module allows one to define custom formats based on up to two fact
- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state. Each class gets activated when the current capacity is equal to or below the configured *<value>*. - The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state. Each class gets activated when the current capacity is equal to or below the configured *<value>*.
- Also each state can have its own *format*. Those can be configured via *format-<name>*. Or if you want to differentiate a bit more even as *format-<status>-<state>*. For more information see *custom-formats*. - Also each state can have its own *format*. Those can be configured via *format-<name>*. Or if you want to differentiate a bit more even as *format-<status>-<state>*. For more information see *custom-formats*.
# EVENTS
Every entry in the *events* object consists of a *<event-name>* (typeof: *string*) and a *<command>* (typeof: *string*). ++
*<event-name>* can be in one of the following formats:
- *on-<status>-<state>*
- *on-<status>-<capacity>*
Where:
- *<status>* is either *charging* or *discharging*,
- *<state>* is the name of one of the states specified in the *states* object,
- *<capacity>* is a battery level value (between *0-100*).
# EXAMPLES # EXAMPLES
@ -178,6 +195,11 @@ The *battery* module allows one to define custom formats based on up to two fact
"warning": 30, "warning": 30,
"critical": 15 "critical": 15
}, },
"events": {
"on-discharging-warning": "notify-send -u normal 'Low Battery'",
"on-discharging-critical": "notify-send -u critical 'Very Low Battery'",
"on-charging-100": "notify-send -u normal 'Battery Full!'"
},
"format": "{capacity}% {icon}", "format": "{capacity}% {icon}",
"format-icons": ["", "", "", "", ""], "format-icons": ["", "", "", "", ""],
"max-length": 25 "max-length": 25

View File

@ -200,7 +200,7 @@ std::string ALabel::getState(uint8_t value, bool lesser) {
} }
} }
// Sort states // Sort states
std::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) { std::ranges::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) {
return lesser ? a.second < b.second : a.second > b.second; return lesser ? a.second < b.second : a.second > b.second;
}); });
std::string valid_state; std::string valid_state;

View File

@ -1,14 +1,16 @@
#include "modules/battery.hpp" #include "modules/battery.hpp"
#include <algorithm> #include <algorithm>
#include <cctype>
#include "util/command.hpp"
#if defined(__FreeBSD__) #if defined(__FreeBSD__)
#include <sys/sysctl.h> #include <sys/sysctl.h>
#endif #endif
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <iostream>
waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config) waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60), bar_(bar) { : ALabel(config, "battery", id, "{capacity}%", 60), last_event_(""), bar_(bar) {
#if defined(__linux__) #if defined(__linux__)
battery_watch_fd_ = inotify_init1(IN_CLOEXEC); battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) { if (battery_watch_fd_ == -1) {
@ -26,6 +28,7 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J
throw std::runtime_error("Could not watch for battery plug/unplug"); throw std::runtime_error("Could not watch for battery plug/unplug");
} }
#endif #endif
spdlog::debug("battery: worker interval is {}", interval_.count());
worker(); worker();
} }
@ -677,10 +680,11 @@ auto waybar::modules::Battery::update() -> void {
} }
auto status_pretty = status; auto status_pretty = status;
// Transform to lowercase and replace space with dash // Transform to lowercase and replace space with dash
std::transform(status.begin(), status.end(), status.begin(), std::ranges::transform(status.begin(), status.end(), status.begin(),
[](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); });
auto format = format_; auto format = format_;
auto state = getState(capacity, true); auto state = getState(capacity, true);
processEvents(state, status, capacity);
setBarClass(state); setBarClass(state);
auto time_remaining_formatted = formatTimeRemaining(time_remaining); auto time_remaining_formatted = formatTimeRemaining(time_remaining);
if (tooltipEnabled()) { if (tooltipEnabled()) {
@ -770,3 +774,25 @@ void waybar::modules::Battery::setBarClass(std::string& state) {
bar_.window.get_style_context()->add_class(new_class); bar_.window.get_style_context()->add_class(new_class);
} }
} }
void waybar::modules::Battery::processEvents(std::string& state, std::string& status,
uint8_t capacity) {
// There are no events specified, skip
auto events = config_["events"];
if (!events.isObject() || events.empty()) {
return;
}
std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging",
state.empty() ? std::to_string(capacity) : state);
if (last_event_ != event_name) {
spdlog::debug("battery: triggering event {}", event_name);
if (events[event_name].isString()) {
std::string exec = events[event_name].asString();
// Execute the command if it is not empty
if (!exec.empty()) {
util::command::exec(exec, "");
}
}
last_event_ = event_name;
}
}