#include "modules/upower.hpp" #include #include #include 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(sec)}; const auto hrs{std::chrono::duration_cast(sec - ds)}; const auto min{std::chrono::duration_cast(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 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 &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 &conn, const Glib::ustring &name, const Glib::ustring &name_owner) { upRunning_ = true; } void UPower::onVanished(const Glib::RefPtr &conn, const Glib::ustring &name) { upRunning_ = false; } void UPower::prepareForSleep_cb(const Glib::RefPtr &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 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(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(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(data)}; // Update the widget up->dp.emit(); } void UPower::addDevice(UpDevice *device) { std::lock_guard 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 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 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 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(user_data)}; upDevice.upDevice = static_cast(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 &tooltip) { std::lock_guard 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