486 lines
17 KiB
C++
486 lines
17 KiB
C++
#include "modules/upower.hpp"
|
|
|
|
#include <giomm/dbuswatchname.h>
|
|
#include <gtkmm/tooltip.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
namespace waybar::modules {
|
|
|
|
UPower::UPower(const std::string &id, const Json::Value &config)
|
|
: AIconLabel(config, "upower", id, "{percentage}", 0, true, true, true), sleeping_{false} {
|
|
box_.set_name(name_);
|
|
box_.set_spacing(0);
|
|
box_.set_has_tooltip(AModule::tooltipEnabled());
|
|
// Tooltip box
|
|
contentBox_.set_orientation((box_.get_orientation() == Gtk::ORIENTATION_HORIZONTAL)
|
|
? Gtk::ORIENTATION_VERTICAL
|
|
: Gtk::ORIENTATION_HORIZONTAL);
|
|
// Get current theme
|
|
gtkTheme_ = Gtk::IconTheme::get_default();
|
|
|
|
// Icon Size
|
|
if (config_["icon-size"].isInt()) {
|
|
iconSize_ = config_["icon-size"].asInt();
|
|
}
|
|
image_.set_pixel_size(iconSize_);
|
|
|
|
// Show icon only when "show-icon" isn't set to false
|
|
if (config_["show-icon"].isBool()) showIcon_ = config_["show-icon"].asBool();
|
|
if (!showIcon_) box_.remove(image_);
|
|
// Device user wants
|
|
if (config_["native-path"].isString()) nativePath_ = config_["native-path"].asString();
|
|
|
|
// Hide If Empty
|
|
if (config_["hide-if-empty"].isBool()) hideIfEmpty_ = config_["hide-if-empty"].asBool();
|
|
|
|
// Tooltip Spacing
|
|
if (config_["tooltip-spacing"].isInt()) tooltip_spacing_ = config_["tooltip-spacing"].asInt();
|
|
|
|
// Tooltip Padding
|
|
if (config_["tooltip-padding"].isInt()) {
|
|
tooltip_padding_ = config_["tooltip-padding"].asInt();
|
|
contentBox_.set_margin_top(tooltip_padding_);
|
|
contentBox_.set_margin_bottom(tooltip_padding_);
|
|
contentBox_.set_margin_left(tooltip_padding_);
|
|
contentBox_.set_margin_right(tooltip_padding_);
|
|
}
|
|
|
|
// Tooltip Format
|
|
if (config_["tooltip-format"].isString()) tooltipFormat_ = config_["tooltip-format"].asString();
|
|
|
|
// Start watching DBUS
|
|
watcherID_ = Gio::DBus::watch_name(
|
|
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.UPower",
|
|
sigc::mem_fun(*this, &UPower::onAppear), sigc::mem_fun(*this, &UPower::onVanished),
|
|
Gio::DBus::BusNameWatcherFlags::BUS_NAME_WATCHER_FLAGS_AUTO_START);
|
|
// Get DBus async connect
|
|
Gio::DBus::Connection::get(Gio::DBus::BusType::BUS_TYPE_SYSTEM,
|
|
sigc::mem_fun(*this, &UPower::getConn_cb));
|
|
|
|
// Make UPower client
|
|
GError **gErr = NULL;
|
|
upClient_ = up_client_new_full(NULL, gErr);
|
|
if (upClient_ == NULL)
|
|
spdlog::error("Upower. UPower client connection error. {}", (*gErr)->message);
|
|
|
|
// Subscribe UPower events
|
|
g_signal_connect(upClient_, "device-added", G_CALLBACK(deviceAdded_cb), this);
|
|
g_signal_connect(upClient_, "device-removed", G_CALLBACK(deviceRemoved_cb), this);
|
|
|
|
// Subscribe tooltip query events
|
|
box_.set_has_tooltip();
|
|
box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::queryTooltipCb), false);
|
|
|
|
resetDevices();
|
|
setDisplayDevice();
|
|
// Update the widget
|
|
dp.emit();
|
|
}
|
|
|
|
UPower::~UPower() {
|
|
if (upDevice_.upDevice != NULL) g_object_unref(upDevice_.upDevice);
|
|
if (upClient_ != NULL) g_object_unref(upClient_);
|
|
if (subscrID_ > 0u) {
|
|
conn_->signal_unsubscribe(subscrID_);
|
|
subscrID_ = 0u;
|
|
}
|
|
Gio::DBus::unwatch_name(watcherID_);
|
|
watcherID_ = 0u;
|
|
removeDevices();
|
|
}
|
|
|
|
static const std::string getDeviceStatus(UpDeviceState &state) {
|
|
switch (state) {
|
|
case UP_DEVICE_STATE_CHARGING:
|
|
case UP_DEVICE_STATE_PENDING_CHARGE:
|
|
return "charging";
|
|
case UP_DEVICE_STATE_DISCHARGING:
|
|
case UP_DEVICE_STATE_PENDING_DISCHARGE:
|
|
return "discharging";
|
|
case UP_DEVICE_STATE_FULLY_CHARGED:
|
|
return "full";
|
|
case UP_DEVICE_STATE_EMPTY:
|
|
return "empty";
|
|
default:
|
|
return "unknown-status";
|
|
}
|
|
}
|
|
|
|
static const std::string getDeviceIcon(UpDeviceKind &kind) {
|
|
switch (kind) {
|
|
case UP_DEVICE_KIND_LINE_POWER:
|
|
return "ac-adapter-symbolic";
|
|
case UP_DEVICE_KIND_BATTERY:
|
|
return "battery-symbolic";
|
|
case UP_DEVICE_KIND_UPS:
|
|
return "uninterruptible-power-supply-symbolic";
|
|
case UP_DEVICE_KIND_MONITOR:
|
|
return "video-display-symbolic";
|
|
case UP_DEVICE_KIND_MOUSE:
|
|
return "input-mouse-symbolic";
|
|
case UP_DEVICE_KIND_KEYBOARD:
|
|
return "input-keyboard-symbolic";
|
|
case UP_DEVICE_KIND_PDA:
|
|
return "pda-symbolic";
|
|
case UP_DEVICE_KIND_PHONE:
|
|
return "phone-symbolic";
|
|
case UP_DEVICE_KIND_MEDIA_PLAYER:
|
|
return "multimedia-player-symbolic";
|
|
case UP_DEVICE_KIND_TABLET:
|
|
return "computer-apple-ipad-symbolic";
|
|
case UP_DEVICE_KIND_COMPUTER:
|
|
return "computer-symbolic";
|
|
case UP_DEVICE_KIND_GAMING_INPUT:
|
|
return "input-gaming-symbolic";
|
|
case UP_DEVICE_KIND_PEN:
|
|
return "input-tablet-symbolic";
|
|
case UP_DEVICE_KIND_TOUCHPAD:
|
|
return "input-touchpad-symbolic";
|
|
case UP_DEVICE_KIND_MODEM:
|
|
return "modem-symbolic";
|
|
case UP_DEVICE_KIND_NETWORK:
|
|
return "network-wired-symbolic";
|
|
case UP_DEVICE_KIND_HEADSET:
|
|
return "audio-headset-symbolic";
|
|
case UP_DEVICE_KIND_HEADPHONES:
|
|
return "audio-headphones-symbolic";
|
|
case UP_DEVICE_KIND_OTHER_AUDIO:
|
|
case UP_DEVICE_KIND_SPEAKERS:
|
|
return "audio-speakers-symbolic";
|
|
case UP_DEVICE_KIND_VIDEO:
|
|
return "camera-web-symbolic";
|
|
case UP_DEVICE_KIND_PRINTER:
|
|
return "printer-symbolic";
|
|
case UP_DEVICE_KIND_SCANNER:
|
|
return "scanner-symbolic";
|
|
case UP_DEVICE_KIND_CAMERA:
|
|
return "camera-photo-symbolic";
|
|
case UP_DEVICE_KIND_BLUETOOTH_GENERIC:
|
|
return "bluetooth-active-symbolic";
|
|
case UP_DEVICE_KIND_TOY:
|
|
case UP_DEVICE_KIND_REMOTE_CONTROL:
|
|
case UP_DEVICE_KIND_WEARABLE:
|
|
case UP_DEVICE_KIND_LAST:
|
|
default:
|
|
return "battery-symbolic";
|
|
}
|
|
}
|
|
|
|
static std::string secondsToString(const std::chrono::seconds sec) {
|
|
const auto ds{std::chrono::duration_cast<std::chrono::days>(sec)};
|
|
const auto hrs{std::chrono::duration_cast<std::chrono::hours>(sec - ds)};
|
|
const auto min{std::chrono::duration_cast<std::chrono::minutes>(sec - ds - hrs)};
|
|
std::string_view strRet{(ds.count() > 0) ? "{D}d {H}h {M}min"
|
|
: (hrs.count() > 0) ? "{H}h {M}min"
|
|
: (min.count() > 0) ? "{M}min"
|
|
: ""};
|
|
spdlog::debug(
|
|
"UPower::secondsToString(). seconds: \"{0}\", minutes: \"{1}\", hours: \"{2}\", \
|
|
days: \"{3}\", strRet: \"{4}\"",
|
|
sec.count(), min.count(), hrs.count(), ds.count(), strRet);
|
|
return fmt::format(fmt::runtime(strRet), fmt::arg("D", ds.count()), fmt::arg("H", hrs.count()),
|
|
fmt::arg("M", min.count()));
|
|
}
|
|
|
|
auto UPower::update() -> void {
|
|
std::lock_guard<std::mutex> guard{mutex_};
|
|
// Don't update widget if the UPower service isn't running
|
|
if (!upRunning_ || sleeping_) {
|
|
if (hideIfEmpty_) box_.hide();
|
|
return;
|
|
}
|
|
|
|
getUpDeviceInfo(upDevice_);
|
|
|
|
if (upDevice_.upDevice == NULL && hideIfEmpty_) {
|
|
box_.hide();
|
|
return;
|
|
}
|
|
/* Every Device which is handled by Upower and which is not
|
|
* UP_DEVICE_KIND_UNKNOWN (0) or UP_DEVICE_KIND_LINE_POWER (1) is a Battery
|
|
*/
|
|
const bool upDeviceValid{upDevice_.kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN &&
|
|
upDevice_.kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER};
|
|
// Get CSS status
|
|
const auto status{getDeviceStatus(upDevice_.state)};
|
|
// Remove last status if it exists
|
|
if (!lastStatus_.empty() && box_.get_style_context()->has_class(lastStatus_))
|
|
box_.get_style_context()->remove_class(lastStatus_);
|
|
if (!box_.get_style_context()->has_class(status)) box_.get_style_context()->add_class(status);
|
|
lastStatus_ = status;
|
|
|
|
if (devices_.size() == 0 && !upDeviceValid && hideIfEmpty_) {
|
|
box_.hide();
|
|
// Call parent update
|
|
AModule::update();
|
|
return;
|
|
}
|
|
|
|
label_.set_markup(getText(upDevice_, format_));
|
|
// Set icon
|
|
if (upDevice_.icon_name == NULL || !gtkTheme_->has_icon(upDevice_.icon_name))
|
|
upDevice_.icon_name = (char *)NO_BATTERY.c_str();
|
|
image_.set_from_icon_name(upDevice_.icon_name, Gtk::ICON_SIZE_INVALID);
|
|
|
|
box_.show();
|
|
|
|
// Call parent update
|
|
ALabel::update();
|
|
}
|
|
|
|
void UPower::getConn_cb(Glib::RefPtr<Gio::AsyncResult> &result) {
|
|
try {
|
|
conn_ = Gio::DBus::Connection::get_finish(result);
|
|
// Subscribe DBUs events
|
|
subscrID_ = conn_->signal_subscribe(sigc::mem_fun(*this, &UPower::prepareForSleep_cb),
|
|
"org.freedesktop.login1", "org.freedesktop.login1.Manager",
|
|
"PrepareForSleep", "/org/freedesktop/login1");
|
|
|
|
} catch (const Glib::Error &e) {
|
|
spdlog::error("Upower. DBus connection error. {}", e.what().c_str());
|
|
}
|
|
}
|
|
|
|
void UPower::onAppear(const Glib::RefPtr<Gio::DBus::Connection> &conn, const Glib::ustring &name,
|
|
const Glib::ustring &name_owner) {
|
|
upRunning_ = true;
|
|
}
|
|
|
|
void UPower::onVanished(const Glib::RefPtr<Gio::DBus::Connection> &conn,
|
|
const Glib::ustring &name) {
|
|
upRunning_ = false;
|
|
}
|
|
|
|
void UPower::prepareForSleep_cb(const Glib::RefPtr<Gio::DBus::Connection> &connection,
|
|
const Glib::ustring &sender_name, const Glib::ustring &object_path,
|
|
const Glib::ustring &interface_name,
|
|
const Glib::ustring &signal_name,
|
|
const Glib::VariantContainerBase ¶meters) {
|
|
if (parameters.is_of_type(Glib::VariantType("(b)"))) {
|
|
Glib::Variant<bool> sleeping;
|
|
parameters.get_child(sleeping, 0);
|
|
if (!sleeping.get()) {
|
|
resetDevices();
|
|
setDisplayDevice();
|
|
sleeping_ = false;
|
|
// Update the widget
|
|
dp.emit();
|
|
} else
|
|
sleeping_ = true;
|
|
}
|
|
}
|
|
|
|
void UPower::deviceAdded_cb(UpClient *client, UpDevice *device, gpointer data) {
|
|
UPower *up{static_cast<UPower *>(data)};
|
|
up->addDevice(device);
|
|
up->setDisplayDevice();
|
|
// Update the widget
|
|
up->dp.emit();
|
|
}
|
|
|
|
void UPower::deviceRemoved_cb(UpClient *client, const gchar *objectPath, gpointer data) {
|
|
UPower *up{static_cast<UPower *>(data)};
|
|
up->removeDevice(objectPath);
|
|
up->setDisplayDevice();
|
|
// Update the widget
|
|
up->dp.emit();
|
|
}
|
|
|
|
void UPower::deviceNotify_cb(UpDevice *device, GParamSpec *pspec, gpointer data) {
|
|
UPower *up{static_cast<UPower *>(data)};
|
|
// Update the widget
|
|
up->dp.emit();
|
|
}
|
|
|
|
void UPower::addDevice(UpDevice *device) {
|
|
std::lock_guard<std::mutex> guard{mutex_};
|
|
|
|
if (G_IS_OBJECT(device)) {
|
|
const gchar *objectPath{up_device_get_object_path(device)};
|
|
|
|
// Due to the device getting cleared after this event is fired, we
|
|
// create a new object pointing to its objectPath
|
|
device = up_device_new();
|
|
upDevice_output upDevice{.upDevice = device};
|
|
gboolean ret{up_device_set_object_path_sync(device, objectPath, NULL, NULL)};
|
|
if (!ret) {
|
|
g_object_unref(G_OBJECT(device));
|
|
return;
|
|
}
|
|
|
|
if (devices_.find(objectPath) != devices_.cend()) {
|
|
auto upDevice{devices_[objectPath]};
|
|
if (G_IS_OBJECT(upDevice.upDevice)) g_object_unref(upDevice.upDevice);
|
|
devices_.erase(objectPath);
|
|
}
|
|
|
|
g_signal_connect(device, "notify", G_CALLBACK(deviceNotify_cb), this);
|
|
devices_.emplace(Devices::value_type(objectPath, upDevice));
|
|
}
|
|
}
|
|
|
|
void UPower::removeDevice(const gchar *objectPath) {
|
|
std::lock_guard<std::mutex> guard{mutex_};
|
|
if (devices_.find(objectPath) != devices_.cend()) {
|
|
auto upDevice{devices_[objectPath]};
|
|
if (G_IS_OBJECT(upDevice.upDevice)) g_object_unref(upDevice.upDevice);
|
|
devices_.erase(objectPath);
|
|
}
|
|
}
|
|
|
|
void UPower::removeDevices() {
|
|
std::lock_guard<std::mutex> guard{mutex_};
|
|
if (!devices_.empty()) {
|
|
auto it{devices_.cbegin()};
|
|
while (it != devices_.cend()) {
|
|
if (G_IS_OBJECT(it->second.upDevice)) g_object_unref(it->second.upDevice);
|
|
devices_.erase(it++);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Removes all devices and adds the current devices
|
|
void UPower::resetDevices() {
|
|
// Remove all devices
|
|
removeDevices();
|
|
|
|
// Adds all devices
|
|
GPtrArray *newDevices = up_client_get_devices2(upClient_);
|
|
if (newDevices != NULL)
|
|
for (guint i{0}; i < newDevices->len; ++i) {
|
|
UpDevice *device{(UpDevice *)g_ptr_array_index(newDevices, i)};
|
|
if (device && G_IS_OBJECT(device)) addDevice(device);
|
|
}
|
|
}
|
|
|
|
void UPower::setDisplayDevice() {
|
|
std::lock_guard<std::mutex> guard{mutex_};
|
|
|
|
if (nativePath_.empty()) {
|
|
// Unref current upDevice
|
|
if (upDevice_.upDevice != NULL) g_object_unref(upDevice_.upDevice);
|
|
|
|
upDevice_.upDevice = up_client_get_display_device(upClient_);
|
|
getUpDeviceInfo(upDevice_);
|
|
} else {
|
|
g_ptr_array_foreach(
|
|
up_client_get_devices2(upClient_),
|
|
[](gpointer data, gpointer user_data) {
|
|
upDevice_output upDevice;
|
|
auto thisPtr{static_cast<UPower *>(user_data)};
|
|
upDevice.upDevice = static_cast<UpDevice *>(data);
|
|
thisPtr->getUpDeviceInfo(upDevice);
|
|
if (upDevice.nativePath == nullptr) return;
|
|
if (0 == std::strcmp(upDevice.nativePath, thisPtr->nativePath_.c_str())) {
|
|
// Unref current upDevice
|
|
if (thisPtr->upDevice_.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice);
|
|
// Reassign new upDevice
|
|
thisPtr->upDevice_ = upDevice;
|
|
}
|
|
},
|
|
this);
|
|
}
|
|
|
|
if (upDevice_.upDevice != NULL)
|
|
g_signal_connect(upDevice_.upDevice, "notify", G_CALLBACK(deviceNotify_cb), this);
|
|
}
|
|
|
|
void UPower::getUpDeviceInfo(upDevice_output &upDevice_) {
|
|
if (upDevice_.upDevice != NULL && G_IS_OBJECT(upDevice_.upDevice)) {
|
|
g_object_get(upDevice_.upDevice, "kind", &upDevice_.kind, "state", &upDevice_.state,
|
|
"percentage", &upDevice_.percentage, "icon-name", &upDevice_.icon_name,
|
|
"time-to-empty", &upDevice_.time_empty, "time-to-full", &upDevice_.time_full,
|
|
"temperature", &upDevice_.temperature, "native-path", &upDevice_.nativePath,
|
|
"model", &upDevice_.model, NULL);
|
|
spdlog::debug(
|
|
"UPower. getUpDeviceInfo. kind: \"{0}\". state: \"{1}\". percentage: \"{2}\". \
|
|
icon_name: \"{3}\". time-to-empty: \"{4}\". time-to-full: \"{5}\". temperature: \"{6}\". \
|
|
native_path: \"{7}\". model: \"{8}\"",
|
|
fmt::format_int(upDevice_.kind).str(), fmt::format_int(upDevice_.state).str(),
|
|
upDevice_.percentage, upDevice_.icon_name, upDevice_.time_empty, upDevice_.time_full,
|
|
upDevice_.temperature, upDevice_.nativePath, upDevice_.model);
|
|
}
|
|
}
|
|
|
|
const Glib::ustring UPower::getText(const upDevice_output &upDevice_, const std::string &format) {
|
|
Glib::ustring ret{""};
|
|
if (upDevice_.upDevice != NULL) {
|
|
std::string timeStr{""};
|
|
switch (upDevice_.state) {
|
|
case UP_DEVICE_STATE_CHARGING:
|
|
case UP_DEVICE_STATE_PENDING_CHARGE:
|
|
timeStr = secondsToString(std::chrono::seconds(upDevice_.time_full));
|
|
break;
|
|
case UP_DEVICE_STATE_DISCHARGING:
|
|
case UP_DEVICE_STATE_PENDING_DISCHARGE:
|
|
timeStr = secondsToString(std::chrono::seconds(upDevice_.time_empty));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = fmt::format(
|
|
fmt::runtime(format),
|
|
fmt::arg("percentage", std::to_string((int)std::round(upDevice_.percentage)) + '%'),
|
|
fmt::arg("time", timeStr),
|
|
fmt::arg("temperature", fmt::format("{:-.2g}C", upDevice_.temperature)),
|
|
fmt::arg("model", upDevice_.model), fmt::arg("native-path", upDevice_.nativePath));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool UPower::queryTooltipCb(int x, int y, bool keyboard_tooltip,
|
|
const Glib::RefPtr<Gtk::Tooltip> &tooltip) {
|
|
std::lock_guard<std::mutex> guard{mutex_};
|
|
|
|
// Clear content box
|
|
contentBox_.forall([this](Gtk::Widget &wg) { contentBox_.remove(wg); });
|
|
|
|
// Fill content box with the content
|
|
for (auto pairDev : devices_) {
|
|
// Get device info
|
|
getUpDeviceInfo(pairDev.second);
|
|
|
|
if (pairDev.second.kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN &&
|
|
pairDev.second.kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER) {
|
|
// Make box record
|
|
Gtk::Box *boxRec{new Gtk::Box{box_.get_orientation(), tooltip_spacing_}};
|
|
contentBox_.add(*boxRec);
|
|
Gtk::Box *boxDev{new Gtk::Box{box_.get_orientation()}};
|
|
Gtk::Box *boxUsr{new Gtk::Box{box_.get_orientation()}};
|
|
boxRec->add(*boxDev);
|
|
boxRec->add(*boxUsr);
|
|
// Construct device box
|
|
// Set icon from kind
|
|
std::string iconNameDev{getDeviceIcon(pairDev.second.kind)};
|
|
if (!gtkTheme_->has_icon(iconNameDev)) iconNameDev = (char *)NO_BATTERY.c_str();
|
|
Gtk::Image *iconDev{new Gtk::Image{}};
|
|
iconDev->set_from_icon_name(iconNameDev, Gtk::ICON_SIZE_INVALID);
|
|
iconDev->set_pixel_size(iconSize_);
|
|
boxDev->add(*iconDev);
|
|
// Set label from model
|
|
Gtk::Label *labelDev{new Gtk::Label{pairDev.second.model}};
|
|
boxDev->add(*labelDev);
|
|
// Construct user box
|
|
// Set icon from icon state
|
|
if (pairDev.second.icon_name == NULL || !gtkTheme_->has_icon(pairDev.second.icon_name))
|
|
pairDev.second.icon_name = (char *)NO_BATTERY.c_str();
|
|
Gtk::Image *iconTooltip{new Gtk::Image{}};
|
|
iconTooltip->set_from_icon_name(pairDev.second.icon_name, Gtk::ICON_SIZE_INVALID);
|
|
iconTooltip->set_pixel_size(iconSize_);
|
|
boxUsr->add(*iconTooltip);
|
|
// Set markup text
|
|
Gtk::Label *labelTooltip{new Gtk::Label{}};
|
|
labelTooltip->set_markup(getText(pairDev.second, tooltipFormat_));
|
|
boxUsr->add(*labelTooltip);
|
|
}
|
|
}
|
|
tooltip->set_custom(contentBox_);
|
|
contentBox_.show_all();
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace waybar::modules
|