Compare commits
1 Commits
2f2e476d74
...
laptop
| Author | SHA1 | Date | |
|---|---|---|---|
| e3bb3fdf31 |
@@ -28,6 +28,7 @@ target_sources(bar_lib
|
|||||||
src/widgets/workspaceIndicator.cpp
|
src/widgets/workspaceIndicator.cpp
|
||||||
src/widgets/volumeWidget.cpp
|
src/widgets/volumeWidget.cpp
|
||||||
src/widgets/webWidget.cpp
|
src/widgets/webWidget.cpp
|
||||||
|
src/widgets/battery.cpp
|
||||||
|
|
||||||
src/services/todo.cpp
|
src/services/todo.cpp
|
||||||
src/services/sqliteTodoAdapter.cpp
|
src/services/sqliteTodoAdapter.cpp
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
#include <gtkmm.h>
|
#include <gtkmm.h>
|
||||||
|
|
||||||
#include "icons.hpp"
|
#include "icons.hpp"
|
||||||
|
#include "widgets/battery.hpp"
|
||||||
#include "widgets/clock.hpp"
|
#include "widgets/clock.hpp"
|
||||||
|
#include "widgets/controlCenter.hpp"
|
||||||
#include "widgets/date.hpp"
|
#include "widgets/date.hpp"
|
||||||
#include "widgets/tray.hpp"
|
#include "widgets/tray.hpp"
|
||||||
#include "widgets/volumeWidget.hpp"
|
#include "widgets/volumeWidget.hpp"
|
||||||
#include "widgets/webWidget.hpp"
|
#include "widgets/webWidget.hpp"
|
||||||
#include "widgets/workspaceIndicator.hpp"
|
#include "widgets/workspaceIndicator.hpp"
|
||||||
#include "widgets/controlCenter.hpp"
|
|
||||||
|
|
||||||
class Bar : public Gtk::Window {
|
class Bar : public Gtk::Window {
|
||||||
public:
|
public:
|
||||||
@@ -32,6 +33,7 @@ class Bar : public Gtk::Window {
|
|||||||
WorkspaceIndicator *workspaceIndicator = nullptr;
|
WorkspaceIndicator *workspaceIndicator = nullptr;
|
||||||
TrayWidget *trayWidget = nullptr;
|
TrayWidget *trayWidget = nullptr;
|
||||||
VolumeWidget *volumeWidget = nullptr;
|
VolumeWidget *volumeWidget = nullptr;
|
||||||
|
BatteryWidget *batteryWidget = nullptr;
|
||||||
|
|
||||||
|
|
||||||
void setup_ui();
|
void setup_ui();
|
||||||
|
|||||||
26
include/widgets/battery.hpp
Normal file
26
include/widgets/battery.hpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <gtkmm/box.h>
|
||||||
|
#include <gtkmm/label.h>
|
||||||
|
#include <sigc++/connection.h>
|
||||||
|
|
||||||
|
class BatteryWidget : public Gtk::Box {
|
||||||
|
public:
|
||||||
|
BatteryWidget();
|
||||||
|
~BatteryWidget();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Gtk::Label iconLabel;
|
||||||
|
Gtk::Label label;
|
||||||
|
sigc::connection timeoutConn;
|
||||||
|
std::filesystem::path batteryPath;
|
||||||
|
std::string currentStateClass;
|
||||||
|
|
||||||
|
void update();
|
||||||
|
bool on_timeout();
|
||||||
|
void find_battery_path();
|
||||||
|
void set_state_class(const std::string &stateClass);
|
||||||
|
std::string build_icon(int capacity, bool hasBattery, bool charging, bool full) const;
|
||||||
|
std::string build_text(int capacity, const std::string &status, bool hasBattery, bool charging, bool full) const;
|
||||||
|
};
|
||||||
@@ -136,3 +136,93 @@ button {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.battery-widget {
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-icon {
|
||||||
|
min-width: 7ch;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-text {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-normal {
|
||||||
|
background-color: rgba(120, 120, 120, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-full {
|
||||||
|
background-color: rgba(76, 129, 76, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-full .battery-widget-icon {
|
||||||
|
color: #bdf5bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-charging {
|
||||||
|
background-color: rgba(76, 129, 76, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-charging .battery-widget-icon {
|
||||||
|
color: #9df19d;
|
||||||
|
animation: battery-charge-glow 1.4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-low {
|
||||||
|
background-color: rgba(148, 61, 61, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-low .battery-widget-icon {
|
||||||
|
color: #ff8585;
|
||||||
|
animation: battery-low-blink 1s steps(2, start) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-external {
|
||||||
|
background-color: rgba(100, 100, 100, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.battery-widget-external .battery-widget-icon {
|
||||||
|
color: #d7d7d7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes battery-charge-glow {
|
||||||
|
0% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes battery-low-blink {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
49% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ Bar::Bar(GdkMonitor *monitor, int monitorId)
|
|||||||
this->volumeWidget = Gtk::make_managed<VolumeWidget>();
|
this->volumeWidget = Gtk::make_managed<VolumeWidget>();
|
||||||
this->workspaceIndicator = Gtk::make_managed<WorkspaceIndicator>(monitorId);
|
this->workspaceIndicator = Gtk::make_managed<WorkspaceIndicator>(monitorId);
|
||||||
this->trayWidget = Gtk::make_managed<TrayWidget>();
|
this->trayWidget = Gtk::make_managed<TrayWidget>();
|
||||||
|
this->batteryWidget = Gtk::make_managed<BatteryWidget>();
|
||||||
|
|
||||||
load_css();
|
load_css();
|
||||||
setup_ui();
|
setup_ui();
|
||||||
@@ -77,6 +78,7 @@ void Bar::setup_center_box() {
|
|||||||
|
|
||||||
void Bar::setup_right_box() {
|
void Bar::setup_right_box() {
|
||||||
right_box.append(*this->trayWidget);
|
right_box.append(*this->trayWidget);
|
||||||
|
right_box.append(*this->batteryWidget);
|
||||||
right_box.append(this->homeAssistant);
|
right_box.append(this->homeAssistant);
|
||||||
right_box.append(this->controlCenter);
|
right_box.append(this->controlCenter);
|
||||||
}
|
}
|
||||||
|
|||||||
219
src/widgets/battery.cpp
Normal file
219
src/widgets/battery.cpp
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
#include "widgets/battery.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <glibmm/main.h>
|
||||||
|
#include <sigc++/functors/mem_fun.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string trim(const std::string &value) {
|
||||||
|
const auto start = value.find_first_not_of(" \t\n\r");
|
||||||
|
if (start == std::string::npos) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto end = value.find_last_not_of(" \t\n\r");
|
||||||
|
return value.substr(start, end - start + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BatteryWidget::BatteryWidget() : Gtk::Box(Gtk::Orientation::HORIZONTAL) {
|
||||||
|
set_valign(Gtk::Align::CENTER);
|
||||||
|
set_halign(Gtk::Align::CENTER);
|
||||||
|
set_spacing(6);
|
||||||
|
add_css_class("battery-widget");
|
||||||
|
|
||||||
|
iconLabel.set_halign(Gtk::Align::CENTER);
|
||||||
|
iconLabel.set_valign(Gtk::Align::CENTER);
|
||||||
|
iconLabel.set_width_chars(7);
|
||||||
|
iconLabel.set_text("[ ]");
|
||||||
|
iconLabel.add_css_class("battery-widget-icon");
|
||||||
|
|
||||||
|
append(iconLabel);
|
||||||
|
|
||||||
|
label.set_halign(Gtk::Align::CENTER);
|
||||||
|
label.set_valign(Gtk::Align::CENTER);
|
||||||
|
label.set_width_chars(5);
|
||||||
|
label.set_text("--%");
|
||||||
|
label.add_css_class("battery-widget-text");
|
||||||
|
|
||||||
|
append(label);
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
timeoutConn = Glib::signal_timeout().connect(
|
||||||
|
sigc::mem_fun(*this, &BatteryWidget::on_timeout), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
BatteryWidget::~BatteryWidget() {
|
||||||
|
if (timeoutConn.connected()) {
|
||||||
|
timeoutConn.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatteryWidget::find_battery_path() {
|
||||||
|
constexpr auto base = "/sys/class/power_supply";
|
||||||
|
|
||||||
|
batteryPath.clear();
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(base)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &entry : std::filesystem::directory_iterator(base)) {
|
||||||
|
if (!entry.is_directory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream typeFile(entry.path() / "type");
|
||||||
|
if (!typeFile) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string type;
|
||||||
|
std::getline(typeFile, type);
|
||||||
|
type = trim(type);
|
||||||
|
std::transform(type.begin(), type.end(), type.begin(), [](unsigned char ch) {
|
||||||
|
return static_cast<char>(std::toupper(ch));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (type == "BATTERY") {
|
||||||
|
batteryPath = entry.path();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatteryWidget::update() {
|
||||||
|
try {
|
||||||
|
if (batteryPath.empty() || !std::filesystem::exists(batteryPath)) {
|
||||||
|
find_battery_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool hasBattery = !batteryPath.empty();
|
||||||
|
|
||||||
|
int capacity = -1;
|
||||||
|
std::string status;
|
||||||
|
if (hasBattery) {
|
||||||
|
std::ifstream capacityFile(batteryPath / "capacity");
|
||||||
|
if (capacityFile) {
|
||||||
|
capacityFile >> capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream statusFile(batteryPath / "status");
|
||||||
|
if (statusFile) {
|
||||||
|
std::getline(statusFile, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = trim(status);
|
||||||
|
|
||||||
|
const bool charging = status == "Charging";
|
||||||
|
const bool full = hasBattery && (status == "Full" || (capacity >= 95));
|
||||||
|
const bool low = (capacity >= 0 && capacity < 20 && !charging);
|
||||||
|
|
||||||
|
std::string stateClass;
|
||||||
|
if (!hasBattery) {
|
||||||
|
stateClass = "battery-widget-external";
|
||||||
|
} else if (charging) {
|
||||||
|
stateClass = "battery-widget-charging";
|
||||||
|
} else if (low) {
|
||||||
|
stateClass = "battery-widget-low";
|
||||||
|
} else if (full) {
|
||||||
|
stateClass = "battery-widget-full";
|
||||||
|
} else {
|
||||||
|
stateClass = "battery-widget-normal";
|
||||||
|
}
|
||||||
|
|
||||||
|
set_state_class(stateClass);
|
||||||
|
|
||||||
|
iconLabel.set_text(build_icon(capacity, hasBattery, charging, full));
|
||||||
|
label.set_text(build_text(capacity, status, hasBattery, charging, full));
|
||||||
|
} catch (...) {
|
||||||
|
set_state_class("battery-widget-normal");
|
||||||
|
iconLabel.set_text("[???]");
|
||||||
|
label.set_text("??%");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BatteryWidget::on_timeout() {
|
||||||
|
update();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BatteryWidget::set_state_class(const std::string &stateClass) {
|
||||||
|
if (currentStateClass == stateClass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentStateClass.empty()) {
|
||||||
|
remove_css_class(currentStateClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStateClass = stateClass;
|
||||||
|
|
||||||
|
if (!currentStateClass.empty()) {
|
||||||
|
add_css_class(currentStateClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BatteryWidget::build_icon(int capacity, bool hasBattery, bool charging, bool full) const {
|
||||||
|
if (!hasBattery) {
|
||||||
|
return "<AC>";
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr int segments = 5;
|
||||||
|
const int clampedCapacity = std::clamp(capacity, 0, 100);
|
||||||
|
int filledSegments = (clampedCapacity * segments + 99) / 100;
|
||||||
|
|
||||||
|
if (full) {
|
||||||
|
filledSegments = segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string icon = "[";
|
||||||
|
for (int i = 0; i < segments; ++i) {
|
||||||
|
icon += (i < filledSegments) ? '=' : ' ';
|
||||||
|
}
|
||||||
|
icon += ']';
|
||||||
|
|
||||||
|
if (charging) {
|
||||||
|
icon += '>';
|
||||||
|
} else if (clampedCapacity <= 5) {
|
||||||
|
icon += '!';
|
||||||
|
} else {
|
||||||
|
icon += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string BatteryWidget::build_text(int capacity, const std::string &status, bool hasBattery, bool charging, bool full) const {
|
||||||
|
if (!hasBattery) {
|
||||||
|
return "AC";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capacity >= 0) {
|
||||||
|
std::string text;
|
||||||
|
if (full && capacity >= 99) {
|
||||||
|
text = "100%";
|
||||||
|
} else {
|
||||||
|
text = std::to_string(std::clamp(capacity, 0, 100)) + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charging && capacity < 100) {
|
||||||
|
text.insert(text.begin(), '+');
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!status.empty()) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "?%";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user