Upower refactoring (#3220)

Signed-off-by: Viktar Lukashonak <myxabeer@gmail.com>
This commit is contained in:
ViktarL 2024-05-07 11:29:52 +03:00 committed by GitHub
parent 29917fb073
commit 2ead1bbf84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 574 additions and 699 deletions

View File

@ -0,0 +1,92 @@
#pragma once
#include <giomm/dbusconnection.h>
#include <gtkmm/icontheme.h>
#include <libupower-glib/upower.h>
#include <unordered_map>
#include "AIconLabel.hpp"
namespace waybar::modules {
class UPower final : public AIconLabel {
public:
UPower(const std::string &, const Json::Value &);
virtual ~UPower();
auto update() -> void override;
private:
const std::string NO_BATTERY{"battery-missing-symbolic"};
// Config
bool showIcon_{true};
bool hideIfEmpty_{true};
int iconSize_{20};
int tooltip_spacing_{4};
int tooltip_padding_{4};
Gtk::Box contentBox_; // tooltip box
std::string tooltipFormat_;
// UPower device info
struct upDevice_output {
UpDevice *upDevice{NULL};
double percentage{0.0};
double temperature{0.0};
guint64 time_full{0u};
guint64 time_empty{0u};
gchar *icon_name{(char *)'\0'};
bool upDeviceValid{false};
UpDeviceState state;
UpDeviceKind kind;
char *nativePath{(char *)'\0'};
char *model{(char *)'\0'};
};
// Technical variables
std::string nativePath_;
std::string lastStatus_;
Glib::ustring label_markup_;
std::mutex mutex_;
Glib::RefPtr<Gtk::IconTheme> gtkTheme_;
// Technical functions
void addDevice(UpDevice *);
void removeDevice(const gchar *);
void removeDevices();
void resetDevices();
void setDisplayDevice();
const Glib::ustring getText(const upDevice_output &upDevice_, const std::string &format);
bool queryTooltipCb(int, int, bool, const Glib::RefPtr<Gtk::Tooltip> &);
// DBUS variables
guint watcherID_;
Glib::RefPtr<Gio::DBus::Connection> conn_;
guint subscrID_{0u};
// UPower variables
UpClient *upClient_;
upDevice_output upDevice_; // Device to display
typedef std::unordered_map<std::string, upDevice_output> Devices;
Devices devices_;
bool upRunning_{true};
// DBus callbacks
void getConn_cb(Glib::RefPtr<Gio::AsyncResult> &result);
void onAppear(const Glib::RefPtr<Gio::DBus::Connection> &, const Glib::ustring &,
const Glib::ustring &);
void onVanished(const Glib::RefPtr<Gio::DBus::Connection> &, const Glib::ustring &);
void 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 &parameters);
// UPower callbacks
static void deviceAdded_cb(UpClient *client, UpDevice *device, gpointer data);
static void deviceRemoved_cb(UpClient *client, const gchar *objectPath, gpointer data);
static void deviceNotify_cb(UpDevice *device, GParamSpec *pspec, gpointer user_data);
// UPower secondary functions
void getUpDeviceInfo(upDevice_output &upDevice_);
};
} // namespace waybar::modules

View File

@ -1,82 +0,0 @@
#pragma once
#include <libupower-glib/upower.h>
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>
#include "ALabel.hpp"
#include "glibconfig.h"
#include "gtkmm/box.h"
#include "gtkmm/image.h"
#include "gtkmm/label.h"
#include "modules/upower/upower_tooltip.hpp"
namespace waybar::modules::upower {
class UPower : public AModule {
public:
UPower(const std::string &, const Json::Value &);
virtual ~UPower();
auto update() -> void override;
private:
typedef std::unordered_map<std::string, UpDevice *> Devices;
const std::string DEFAULT_FORMAT = "{percentage}";
const std::string DEFAULT_FORMAT_ALT = "{percentage} {time}";
static void deviceAdded_cb(UpClient *client, UpDevice *device, gpointer data);
static void deviceRemoved_cb(UpClient *client, const gchar *objectPath, gpointer data);
static void deviceNotify_cb(UpDevice *device, GParamSpec *pspec, gpointer user_data);
static void prepareForSleep_cb(GDBusConnection *system_bus, const gchar *sender_name,
const gchar *object_path, const gchar *interface_name,
const gchar *signal_name, GVariant *parameters,
gpointer user_data);
static void upowerAppear(GDBusConnection *conn, const gchar *name, const gchar *name_owner,
gpointer data);
static void upowerDisappear(GDBusConnection *connection, const gchar *name, gpointer user_data);
void removeDevice(const gchar *objectPath);
void addDevice(UpDevice *device);
void setDisplayDevice();
void resetDevices();
void removeDevices();
bool show_tooltip_callback(int, int, bool, const Glib::RefPtr<Gtk::Tooltip> &tooltip);
bool handleToggle(GdkEventButton *const &) override;
std::string timeToString(gint64 time);
const std::string getDeviceStatus(UpDeviceState &state);
Gtk::Box box_;
Gtk::Image icon_;
Gtk::Label label_;
// Config
bool hideIfEmpty = true;
bool tooltip_enabled = true;
uint tooltip_spacing = 4;
uint tooltip_padding = 4;
uint iconSize = 20;
std::string format = DEFAULT_FORMAT;
std::string format_alt = DEFAULT_FORMAT_ALT;
Devices devices;
std::mutex m_Mutex;
UpClient *client;
UpDevice *displayDevice = nullptr;
guint login1_id;
GDBusConnection *login1_connection;
std::unique_ptr<UPowerTooltip> upower_tooltip;
std::string lastStatus;
const char *lastWarningLevel = nullptr;
bool showAltText;
bool showIcon = true;
bool upowerRunning;
guint upowerWatcher_id;
std::string nativePath_;
};
} // namespace waybar::modules::upower

View File

@ -1,33 +0,0 @@
#pragma once
#include <libupower-glib/upower.h>
#include <memory>
#include <unordered_map>
#include "gtkmm/box.h"
#include "gtkmm/label.h"
#include "gtkmm/window.h"
namespace waybar::modules::upower {
class UPowerTooltip : public Gtk::Window {
private:
typedef std::unordered_map<std::string, UpDevice*> Devices;
const std::string getDeviceIcon(UpDeviceKind& kind);
std::unique_ptr<Gtk::Box> contentBox;
uint iconSize;
uint tooltipSpacing;
uint tooltipPadding;
public:
UPowerTooltip(uint iconSize, uint tooltipSpacing, uint tooltipPadding);
virtual ~UPowerTooltip();
uint updateTooltip(Devices& devices);
};
} // namespace waybar::modules::upower

View File

@ -335,10 +335,7 @@ endif
if (upower_glib.found() and not get_option('logind').disabled())
add_project_arguments('-DHAVE_UPOWER', language: 'cpp')
src_files += files(
'src/modules/upower/upower.cpp',
'src/modules/upower/upower_tooltip.cpp',
)
src_files += files('src/modules/upower.cpp')
man_files += files('man/waybar-upower.5.scd')
endif

View File

@ -70,7 +70,7 @@
#include "modules/gamemode.hpp"
#endif
#ifdef HAVE_UPOWER
#include "modules/upower/upower.hpp"
#include "modules/upower.hpp"
#endif
#ifdef HAVE_PIPEWIRE
#include "modules/privacy/privacy.hpp"
@ -130,7 +130,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
#endif
#ifdef HAVE_UPOWER
if (ref == "upower") {
return new waybar::modules::upower::UPower(id, config_[name]);
return new waybar::modules::UPower(id, config_[name]);
}
#endif
#ifdef HAVE_PIPEWIRE

479
src/modules/upower.cpp Normal file
View File

@ -0,0 +1,479 @@
#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) {
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_) {
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 &parameters) {
if (parameters.is_of_type(Glib::VariantType("(b)"))) {
Glib::Variant<bool> sleeping;
parameters.get_child(sleeping, 0);
if (!sleeping.get()) {
resetDevices();
setDisplayDevice();
// Update the widget
dp.emit();
}
}
}
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()) {
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 (0 == std::strcmp(upDevice.nativePath, thisPtr->nativePath_.c_str())) {
// Unref current upDevice
if (thisPtr->upDevice_.upDevice) g_object_unref(thisPtr->upDevice_.upDevice);
// Reassign new upDevice
thisPtr->upDevice_ = upDevice;
}
},
this);
}
if (upDevice_.upDevice)
g_signal_connect(upDevice_.upDevice, "notify", G_CALLBACK(deviceNotify_cb), this);
}
void UPower::getUpDeviceInfo(upDevice_output &upDevice_) {
if (upDevice_.upDevice && 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) {
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

View File

@ -1,418 +0,0 @@
#include "modules/upower/upower.hpp"
#include <fmt/core.h>
#include <cstring>
#include <string>
#include "gtkmm/tooltip.h"
#include "util/gtk_icon.hpp"
static const char* getDeviceWarningLevel(UpDeviceLevel level) {
switch (level) {
case UP_DEVICE_LEVEL_CRITICAL:
return "critical";
case UP_DEVICE_LEVEL_LOW:
return "low";
default:
return nullptr;
}
}
namespace waybar::modules::upower {
UPower::UPower(const std::string& id, const Json::Value& config)
: AModule(config, "upower", id),
box_(Gtk::ORIENTATION_HORIZONTAL, 0),
icon_(),
label_(),
devices(),
m_Mutex(),
client(),
showAltText(false) {
// Show icon only when "show-icon" isn't set to false
if (config_["show-icon"].isBool()) {
showIcon = config_["show-icon"].asBool();
}
if (showIcon) {
box_.pack_start(icon_);
}
box_.pack_start(label_);
box_.set_name(name_);
event_box_.add(box_);
// Device user wants
if (config_["native-path"].isString()) nativePath_ = config_["native-path"].asString();
// Icon Size
if (config_["icon-size"].isUInt()) {
iconSize = config_["icon-size"].asUInt();
}
icon_.set_pixel_size(iconSize);
// Hide If Empty
if (config_["hide-if-empty"].isBool()) {
hideIfEmpty = config_["hide-if-empty"].asBool();
}
// Format
if (config_["format"].isString()) {
format = config_["format"].asString();
}
// Format Alt
if (config_["format-alt"].isString()) {
format_alt = config_["format-alt"].asString();
}
// Tooltip Spacing
if (config_["tooltip-spacing"].isUInt()) {
tooltip_spacing = config_["tooltip-spacing"].asUInt();
}
// Tooltip Padding
if (config_["tooltip-padding"].isUInt()) {
tooltip_padding = config_["tooltip-padding"].asUInt();
}
// Tooltip
if (config_["tooltip"].isBool()) {
tooltip_enabled = config_["tooltip"].asBool();
}
box_.set_has_tooltip(tooltip_enabled);
if (tooltip_enabled) {
// Sets the window to use when showing the tooltip
upower_tooltip = std::make_unique<UPowerTooltip>(iconSize, tooltip_spacing, tooltip_padding);
box_.set_tooltip_window(*upower_tooltip);
box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::show_tooltip_callback));
}
upowerWatcher_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM, "org.freedesktop.UPower",
G_BUS_NAME_WATCHER_FLAGS_AUTO_START, upowerAppear,
upowerDisappear, this, NULL);
client = up_client_new_full(NULL, NULL);
if (client == NULL) {
throw std::runtime_error("Unable to create UPower client!");
}
// Connect to Login1 PrepareForSleep signal
login1_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
if (!login1_connection) {
throw std::runtime_error("Unable to connect to the SYSTEM Bus!...");
} else {
login1_id = g_dbus_connection_signal_subscribe(
login1_connection, "org.freedesktop.login1", "org.freedesktop.login1.Manager",
"PrepareForSleep", "/org/freedesktop/login1", NULL, G_DBUS_SIGNAL_FLAGS_NONE,
prepareForSleep_cb, this, NULL);
}
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &UPower::handleToggle));
g_signal_connect(client, "device-added", G_CALLBACK(deviceAdded_cb), this);
g_signal_connect(client, "device-removed", G_CALLBACK(deviceRemoved_cb), this);
resetDevices();
setDisplayDevice();
}
UPower::~UPower() {
if (displayDevice != NULL) g_object_unref(displayDevice);
if (client != NULL) g_object_unref(client);
if (login1_id > 0) {
g_dbus_connection_signal_unsubscribe(login1_connection, login1_id);
login1_id = 0;
}
g_bus_unwatch_name(upowerWatcher_id);
removeDevices();
}
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::prepareForSleep_cb(GDBusConnection* system_bus, const gchar* sender_name,
const gchar* object_path, const gchar* interface_name,
const gchar* signal_name, GVariant* parameters, gpointer data) {
if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(b)"))) {
gboolean sleeping;
g_variant_get(parameters, "(b)", &sleeping);
if (!sleeping) {
UPower* up = static_cast<UPower*>(data);
up->resetDevices();
up->setDisplayDevice();
}
}
}
void UPower::upowerAppear(GDBusConnection* conn, const gchar* name, const gchar* name_owner,
gpointer data) {
UPower* up = static_cast<UPower*>(data);
up->upowerRunning = true;
up->event_box_.set_visible(true);
}
void UPower::upowerDisappear(GDBusConnection* conn, const gchar* name, gpointer data) {
UPower* up = static_cast<UPower*>(data);
up->upowerRunning = false;
up->event_box_.set_visible(false);
}
void UPower::removeDevice(const gchar* objectPath) {
std::lock_guard<std::mutex> guard(m_Mutex);
if (devices.find(objectPath) != devices.end()) {
UpDevice* device = devices[objectPath];
if (G_IS_OBJECT(device)) {
g_object_unref(device);
}
devices.erase(objectPath);
}
}
void UPower::addDevice(UpDevice* device) {
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
gboolean ret;
device = up_device_new();
ret = up_device_set_object_path_sync(device, objectPath, NULL, NULL);
if (!ret) {
g_object_unref(G_OBJECT(device));
return;
}
std::lock_guard<std::mutex> guard(m_Mutex);
if (devices.find(objectPath) != devices.end()) {
UpDevice* device = devices[objectPath];
if (G_IS_OBJECT(device)) {
g_object_unref(device);
}
devices.erase(objectPath);
}
g_signal_connect(device, "notify", G_CALLBACK(deviceNotify_cb), this);
devices.emplace(Devices::value_type(objectPath, device));
}
}
void UPower::setDisplayDevice() {
std::lock_guard<std::mutex> guard(m_Mutex);
if (nativePath_.empty())
displayDevice = up_client_get_display_device(client);
else {
g_ptr_array_foreach(
up_client_get_devices2(client),
[](gpointer data, gpointer user_data) {
UpDevice* device{static_cast<UpDevice*>(data)};
UPower* thisPtr{static_cast<UPower*>(user_data)};
gchar* nativePath;
if (!thisPtr->displayDevice) {
g_object_get(device, "native-path", &nativePath, NULL);
if (!std::strcmp(nativePath, thisPtr->nativePath_.c_str()))
thisPtr->displayDevice = device;
}
},
this);
}
if (displayDevice) g_signal_connect(displayDevice, "notify", G_CALLBACK(deviceNotify_cb), this);
}
void UPower::removeDevices() {
std::lock_guard<std::mutex> guard(m_Mutex);
if (!devices.empty()) {
auto it = devices.cbegin();
while (it != devices.cend()) {
if (G_IS_OBJECT(it->second)) {
g_object_unref(it->second);
}
devices.erase(it++);
}
}
}
/** Removes all devices and adds the current devices */
void UPower::resetDevices() {
// Removes all devices
removeDevices();
// Adds all devices
GPtrArray* newDevices = up_client_get_devices2(client);
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);
}
// Update the widget
dp.emit();
}
bool UPower::show_tooltip_callback(int, int, bool, const Glib::RefPtr<Gtk::Tooltip>& tooltip) {
return true;
}
const std::string UPower::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";
}
}
bool UPower::handleToggle(GdkEventButton* const& event) {
std::lock_guard<std::mutex> guard(m_Mutex);
showAltText = !showAltText;
return AModule::handleToggle(event);
}
std::string UPower::timeToString(gint64 time) {
if (time == 0) return "";
float hours = (float)time / 3600;
float hours_fixed = static_cast<float>(static_cast<int>(hours * 10)) / 10;
float minutes = static_cast<float>(static_cast<int>(hours * 60 * 10)) / 10;
if (hours_fixed >= 1) {
return fmt::format("{H} h", fmt::arg("H", hours_fixed));
} else {
return fmt::format("{M} min", fmt::arg("M", minutes));
}
}
auto UPower::update() -> void {
std::lock_guard<std::mutex> guard(m_Mutex);
// Don't update widget if the UPower service isn't running
if (!upowerRunning) {
if (hideIfEmpty) {
event_box_.set_visible(false);
}
return;
}
UpDeviceKind kind = UP_DEVICE_KIND_UNKNOWN;
UpDeviceState state = UP_DEVICE_STATE_UNKNOWN;
UpDeviceLevel level = UP_DEVICE_LEVEL_UNKNOWN;
double percentage = 0.0;
gint64 time_empty = 0;
gint64 time_full = 0;
gchar* icon_name{(char*)'\0'};
std::string percentString{""};
std::string time_format{""};
bool displayDeviceValid{false};
if (displayDevice) {
g_object_get(displayDevice, "kind", &kind, "state", &state, "percentage", &percentage,
"icon-name", &icon_name, "time-to-empty", &time_empty, "time-to-full", &time_full,
"warning-level", &level, NULL);
/* 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
*/
displayDeviceValid = (kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN &&
kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER);
}
// CSS status class
const std::string status = getDeviceStatus(state);
// Remove last status if it exists
if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) {
box_.get_style_context()->remove_class(lastStatus);
}
// Add the new status class to the Box
if (!box_.get_style_context()->has_class(status)) {
box_.get_style_context()->add_class(status);
}
lastStatus = status;
const char* warning_level = getDeviceWarningLevel(level);
if (lastWarningLevel && box_.get_style_context()->has_class(lastWarningLevel)) {
box_.get_style_context()->remove_class(lastWarningLevel);
}
if (warning_level && !box_.get_style_context()->has_class(warning_level)) {
box_.get_style_context()->add_class(warning_level);
}
lastWarningLevel = warning_level;
if (devices.size() == 0 && !displayDeviceValid && hideIfEmpty) {
event_box_.set_visible(false);
// Call parent update
AModule::update();
return;
}
event_box_.set_visible(true);
if (displayDeviceValid) {
// Tooltip
if (tooltip_enabled) {
uint tooltipCount = upower_tooltip->updateTooltip(devices);
// Disable the tooltip if there aren't any devices in the tooltip
box_.set_has_tooltip(!devices.empty() && tooltipCount > 0);
}
// Set percentage
percentString = std::to_string(int(percentage + 0.5)) + "%";
// Label format
switch (state) {
case UP_DEVICE_STATE_CHARGING:
case UP_DEVICE_STATE_PENDING_CHARGE:
time_format = timeToString(time_full);
break;
case UP_DEVICE_STATE_DISCHARGING:
case UP_DEVICE_STATE_PENDING_DISCHARGE:
time_format = timeToString(time_empty);
break;
default:
break;
}
}
std::string label_format =
fmt::format(fmt::runtime(showAltText ? format_alt : format),
fmt::arg("percentage", percentString), fmt::arg("time", time_format));
// Only set the label text if it doesn't only contain spaces
bool onlySpaces = true;
for (auto& character : label_format) {
if (character == ' ') continue;
onlySpaces = false;
break;
}
label_.set_markup(onlySpaces ? "" : label_format);
// Set icon
if (icon_name == NULL || !DefaultGtkIconThemeWrapper::has_icon(icon_name)) {
icon_name = (char*)"battery-missing-symbolic";
}
icon_.set_from_icon_name(icon_name, Gtk::ICON_SIZE_INVALID);
// Call parent update
AModule::update();
}
} // namespace waybar::modules::upower

View File

@ -1,160 +0,0 @@
#include "modules/upower/upower_tooltip.hpp"
#include "gtkmm/box.h"
#include "gtkmm/enums.h"
#include "gtkmm/image.h"
#include "gtkmm/label.h"
#include "util/gtk_icon.hpp"
namespace waybar::modules::upower {
UPowerTooltip::UPowerTooltip(uint iconSize_, uint tooltipSpacing_, uint tooltipPadding_)
: Gtk::Window(),
contentBox(std::make_unique<Gtk::Box>(Gtk::ORIENTATION_VERTICAL)),
iconSize(iconSize_),
tooltipSpacing(tooltipSpacing_),
tooltipPadding(tooltipPadding_) {
// Sets the Tooltip Padding
contentBox->set_margin_top(tooltipPadding);
contentBox->set_margin_bottom(tooltipPadding);
contentBox->set_margin_left(tooltipPadding);
contentBox->set_margin_right(tooltipPadding);
add(*contentBox);
contentBox->show();
}
UPowerTooltip::~UPowerTooltip() {}
uint UPowerTooltip::updateTooltip(Devices& devices) {
// Removes all old devices
for (auto child : contentBox->get_children()) {
delete child;
}
uint deviceCount = 0;
// Adds all valid devices
for (auto pair : devices) {
UpDevice* device = pair.second;
std::string objectPath = pair.first;
if (!G_IS_OBJECT(device)) continue;
Gtk::Box* box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, tooltipSpacing);
UpDeviceKind kind;
double percentage;
gchar* native_path;
gchar* model;
gchar* icon_name;
g_object_get(device, "kind", &kind, "percentage", &percentage, "native-path", &native_path,
"model", &model, "icon-name", &icon_name, NULL);
// Skip Line_Power and BAT0 devices
if (kind == UP_DEVICE_KIND_LINE_POWER || native_path == NULL || strlen(native_path) == 0 ||
strcmp(native_path, "BAT0") == 0)
continue;
Gtk::Box* modelBox = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL);
box->add(*modelBox);
// Set device icon
std::string deviceIconName = getDeviceIcon(kind);
Gtk::Image* deviceIcon = new Gtk::Image();
deviceIcon->set_pixel_size(iconSize);
if (!DefaultGtkIconThemeWrapper::has_icon(deviceIconName)) {
deviceIconName = "battery-missing-symbolic";
}
deviceIcon->set_from_icon_name(deviceIconName, Gtk::ICON_SIZE_INVALID);
modelBox->add(*deviceIcon);
// Set model
if (model == NULL) model = (gchar*)"";
Gtk::Label* modelLabel = new Gtk::Label(model);
modelBox->add(*modelLabel);
Gtk::Box* chargeBox = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL);
box->add(*chargeBox);
// Set icon
Gtk::Image* icon = new Gtk::Image();
icon->set_pixel_size(iconSize);
if (icon_name == NULL || !DefaultGtkIconThemeWrapper::has_icon(icon_name)) {
icon_name = (char*)"battery-missing-symbolic";
}
icon->set_from_icon_name(icon_name, Gtk::ICON_SIZE_INVALID);
chargeBox->add(*icon);
// Set percentage
std::string percentString = std::to_string(int(percentage + 0.5)) + "%";
Gtk::Label* percentLabel = new Gtk::Label(percentString);
chargeBox->add(*percentLabel);
contentBox->add(*box);
deviceCount++;
}
contentBox->show_all();
return deviceCount;
}
const std::string UPowerTooltip::getDeviceIcon(UpDeviceKind& kind) {
switch (kind) {
case UP_DEVICE_KIND_LINE_POWER:
return "ac-adapter-symbolic";
case UP_DEVICE_KIND_BATTERY:
return "battery";
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";
}
}
} // namespace waybar::modules::upower