add volume widget
This commit is contained in:
112
src/widgets/volumeWidget.cpp
Normal file
112
src/widgets/volumeWidget.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "widgets/volumeWidget.hpp"
|
||||
|
||||
#include "helpers/systemHelper.hpp"
|
||||
|
||||
#include <sigc++/functors/mem_fun.h>
|
||||
#include <regex>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
|
||||
VolumeWidget::VolumeWidget()
|
||||
: Gtk::Box(Gtk::Orientation::HORIZONTAL)
|
||||
{
|
||||
set_valign(Gtk::Align::CENTER);
|
||||
set_halign(Gtk::Align::CENTER);
|
||||
|
||||
m_label.set_halign(Gtk::Align::CENTER);
|
||||
m_label.set_valign(Gtk::Align::CENTER);
|
||||
m_label.set_text("Vol");
|
||||
|
||||
append(m_label);
|
||||
|
||||
// Click toggles mute using wpctl
|
||||
m_click = Gtk::GestureClick::create();
|
||||
m_click->set_button(GDK_BUTTON_PRIMARY);
|
||||
// signal_released provides (int, double, double) — use lambda to ignore args
|
||||
m_click->signal_released().connect([this](int /*n_press*/, double /*x*/, double /*y*/)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Toggle mute then refresh
|
||||
(void)SystemHelper::get_command_output("wpctl set-mute @DEFAULT_SINK@ toggle");
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
std::cerr << "[VolumeWidget] failed to toggle mute: " << ex.what() << std::endl;
|
||||
}
|
||||
this->update();
|
||||
});
|
||||
add_controller(m_click);
|
||||
|
||||
// Initial read
|
||||
update();
|
||||
|
||||
// Start polling every 1 second to keep the display up to date
|
||||
m_timeoutConn = Glib::signal_timeout().connect(sigc::mem_fun(*this, &VolumeWidget::on_timeout), 100);
|
||||
}
|
||||
|
||||
VolumeWidget::~VolumeWidget()
|
||||
{
|
||||
if (m_timeoutConn.connected())
|
||||
m_timeoutConn.disconnect();
|
||||
}
|
||||
|
||||
void VolumeWidget::update()
|
||||
{
|
||||
try
|
||||
{
|
||||
const std::string out = SystemHelper::get_command_output("wpctl get-volume @DEFAULT_SINK@");
|
||||
|
||||
// Attempt to parse a number (percentage or fraction)
|
||||
std::smatch m;
|
||||
std::regex r_percent(R"((\d+(?:\.\d+)?)%)");
|
||||
std::regex r_number(R"((\d+(?:\.\d+)?))");
|
||||
|
||||
std::string text = out;
|
||||
int percent = -1;
|
||||
|
||||
if (std::regex_search(text, m, r_percent))
|
||||
{
|
||||
percent = static_cast<int>(std::round(std::stod(m[1].str())));
|
||||
}
|
||||
else if (std::regex_search(text, m, r_number))
|
||||
{
|
||||
// If number looks like 0.8 treat as fraction
|
||||
const double v = std::stod(m[1].str());
|
||||
if (v <= 1.0)
|
||||
percent = static_cast<int>(std::round(v * 100.0));
|
||||
else
|
||||
percent = static_cast<int>(std::round(v));
|
||||
}
|
||||
|
||||
if (percent >= 0)
|
||||
{
|
||||
m_label.set_text(std::to_string(percent) + "%");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to raw output (trimmed)
|
||||
auto pos = text.find_first_not_of(" \t\n\r");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
auto end = text.find_last_not_of(" \t\n\r");
|
||||
m_label.set_text(text.substr(pos, end - pos + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_label.set_text("?");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
std::cerr << "[VolumeWidget] failed to read volume: " << ex.what() << std::endl;
|
||||
m_label.set_text("N/A");
|
||||
}
|
||||
}
|
||||
|
||||
bool VolumeWidget::on_timeout()
|
||||
{
|
||||
update();
|
||||
return true; // keep timeout active
|
||||
}
|
||||
Reference in New Issue
Block a user