diff --git a/metadata/panel.xml b/metadata/panel.xml
index 6da3f7202..b7fb81201 100644
--- a/metadata/panel.xml
+++ b/metadata/panel.xml
@@ -235,6 +235,18 @@ If full_span is off, both sides of the panel will take the same amount of space,
+ <_short>Power profiles
+
+ <_short>Battery Icon Size
+ 0
+ 0
+
+
+ <_short>Battery Font
+ default
+
+
+
<_short>Network
<_short>Clock Font
diff --git a/src/dock/dock.cpp b/src/dock/dock.cpp
index ce3c924d8..f5698a80d 100644
--- a/src/dock/dock.cpp
+++ b/src/dock/dock.cpp
@@ -11,13 +11,9 @@
#include "dock.hpp"
#include "../util/gtk-utils.hpp"
#include "gtkmm/flowbox.h"
-#include "network/manager.hpp"
-#include "network/network-widget.hpp"
-#include "network/network.hpp"
#include "wf-popover.hpp"
#include
-
class WfDock::impl
{
WayfireOutput *output;
@@ -25,8 +21,6 @@ class WfDock::impl
wl_surface *_wl_surface;
Gtk::FlowBox box;
std::unique_ptr network_image;
- std::unique_ptr network_control;
- std::shared_ptr network_manager;
WfOption css_path{"dock/css_path"};
WfOption entries_per_line{"dock/max_per_line"};
diff --git a/src/locker/plugin/battery.cpp b/src/locker/plugin/battery.cpp
index e7c4d2d2d..f3f8a53f5 100644
--- a/src/locker/plugin/battery.cpp
+++ b/src/locker/plugin/battery.cpp
@@ -1,285 +1,5 @@
-#include
-#include
-#include
-#include
-#include
-
-#include "lockergrid.hpp"
-#include "timedrevealer.hpp"
#include "battery.hpp"
-
-static const std::string BATTERY_STATUS_ICON = "icon"; // icon
-static const std::string BATTERY_STATUS_PERCENT = "percentage"; // icon + percentage
-static const std::string BATTERY_STATUS_FULL = "full"; // icon + percentage + TimeToFull/TimeToEmpty
-
-static bool is_charging(uint32_t state)
-{
- return (state == 1) || (state == 5);
-}
-
-static bool is_discharging(uint32_t state)
-{
- return (state == 2) || (state == 6);
-}
-
-static std::string state_descriptions[] = {
- "Unknown", // 0
- "Charging", // 1
- "Discharging", // 2
- "Empty", // 3
- "Fully charged", // 4
- "Pending charge", // 5
- "Pending discharge", // 6
-};
-
-static std::string get_device_type_description(uint32_t type)
-{
- if (type == 2)
- {
- return "Battery ";
- }
-
- if (type == 3)
- {
- return "UPS ";
- }
-
- return "";
-}
-
-static std::string format_digit(int digit)
-{
- return digit <= 9 ? ("0" + std::to_string(digit)) :
- std::to_string(digit);
-}
-
-static std::string uint_to_time(int64_t time)
-{
- int hrs = time / 3600;
- int min = (time / 60) % 60;
-
- return format_digit(hrs) + ":" + format_digit(min);
-}
-
-void WayfireLockerBatteryPlugin::update_percentages(std::string text)
-{
- for (auto& it : widgets)
- {
- it.second->label.set_label(text);
- }
-}
-
-void WayfireLockerBatteryPlugin::update_descriptions(std::string text)
-{
- for (auto& it : widgets)
- {
- it.second->subtext.set_label(text);
- }
-}
-
-void WayfireLockerBatteryPlugin::update_images()
-{
- Glib::Variant icon_name;
- display_device->get_cached_property(icon_name, ICON);
- for (auto& it : widgets)
- {
- it.second->image.set_from_icon_name(icon_name.get());
- }
-}
-
-WayfireLockerBatteryPluginWidget::WayfireLockerBatteryPluginWidget() :
- WayfireLockerTimedRevealer("locker/battery_always")
-{
- set_child(grid);
- label.add_css_class("battery-percent");
- subtext.add_css_class("battery-description");
- image.add_css_class("battery-image");
-
- grid.attach(image, 0, 0);
- grid.attach(label, 1, 0);
- grid.attach(subtext, 0, 1, 2, 1);
-}
-
-void WayfireLockerBatteryPlugin::add_output(std::string id, std::shared_ptr grid)
-{
- widgets.emplace(id, new WayfireLockerBatteryPluginWidget());
- auto widget = widgets[id];
-
- if (!show_state)
- {
- widget->hide();
- }
-
- grid->attach(*widget, (std::string)position);
-
- update_details();
-}
-
-void WayfireLockerBatteryPlugin::remove_output(std::string id, std::shared_ptr grid)
-{
- grid->remove(*widgets[id]);
- widgets.erase(id);
-}
-
-void WayfireLockerBatteryPlugin::init()
-{
- if (!setup_dbus())
- {
- hide();
- }
-}
-
-void WayfireLockerBatteryPlugin::deinit()
-{
- if (signal)
- {
- signal.disconnect();
- }
-
- display_device = nullptr;
- upower_proxy = nullptr;
- connection = nullptr;
-}
-
-bool WayfireLockerBatteryPlugin::setup_dbus()
-{
- auto cancellable = Gio::Cancellable::create();
- connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable);
- if (!connection)
- {
- std::cerr << "Failed to connect to dbus" << std::endl;
- return false;
- }
-
- upower_proxy = Gio::DBus::Proxy::create_sync(connection, UPOWER_NAME,
- "/org/freedesktop/UPower",
- "org.freedesktop.UPower");
- if (!upower_proxy)
- {
- std::cerr << "Failed to connect to UPower" << std::endl;
- return false;
- }
-
- display_device = Gio::DBus::Proxy::create_sync(connection,
- UPOWER_NAME,
- DISPLAY_DEVICE,
- "org.freedesktop.UPower.Device");
- if (!display_device)
- {
- return false;
- }
-
- Glib::Variant present;
- display_device->get_cached_property(present, SHOULD_DISPLAY);
- if (present.get())
- {
- signal = display_device->signal_properties_changed().connect(
- sigc::mem_fun(*this, &WayfireLockerBatteryPlugin::on_properties_changed));
-
- return true;
- }
-
- return false;
-}
-
-void WayfireLockerBatteryPlugin::on_properties_changed(
- const Gio::DBus::Proxy::MapChangedProperties& properties,
- const std::vector& invalidated)
-{
- bool invalid_icon = false, invalid_details = false;
- for (auto& prop : properties)
- {
- if (prop.first == ICON)
- {
- invalid_icon = true;
- }
-
- if ((prop.first == TYPE) || (prop.first == STATE) || (prop.first == PERCENTAGE) ||
- (prop.first == TIMETOFULL) || (prop.first == TIMETOEMPTY))
- {
- invalid_details = true;
- }
-
- if (prop.first == SHOULD_DISPLAY)
- {}
- }
-
- if (invalid_icon)
- {
- update_images();
- }
-
- if (invalid_details)
- {
- update_details();
- }
-}
-
-void WayfireLockerBatteryPlugin::update_details()
-{
- if (display_device == nullptr)
- {
- std::cout << "No battery proxy!" << std::endl;
- return;
- }
-
- Glib::Variant type;
- display_device->get_cached_property(type, TYPE);
-
- Glib::Variant vstate;
- display_device->get_cached_property(vstate, STATE);
- uint32_t state = vstate.get();
-
- Glib::Variant vpercentage;
- display_device->get_cached_property(vpercentage, PERCENTAGE);
- auto percentage_string = std::to_string((int)vpercentage.get()) + "%";
-
- Glib::Variant time_to_full;
- display_device->get_cached_property(time_to_full, TIMETOFULL);
-
- Glib::Variant time_to_empty;
- display_device->get_cached_property(time_to_empty, TIMETOEMPTY);
-
- std::string description = state_descriptions[state];
- if (is_charging(state))
- {
- description += ", " + uint_to_time(time_to_full.get()) + " until full";
- } else if (is_discharging(state))
- {
- description += ", " + uint_to_time(time_to_empty.get()) + " remaining";
- }
-
- if (state == 0) /* Unknown */
- {
- hide();
- return;
- }
-
- show();
- update_descriptions(get_device_type_description(type.get()) + description);
- update_percentages(percentage_string);
- update_images();
-}
-
-void WayfireLockerBatteryPlugin::hide()
-{
- show_state = false;
- for (auto& it : widgets)
- {
- it.second->hide();
- }
-}
-
-void WayfireLockerBatteryPlugin::show()
-{
- show_state = true;
- for (auto& it : widgets)
- {
- it.second->show();
- }
-}
-
WayfireLockerBatteryPlugin::WayfireLockerBatteryPlugin() :
- WayfireLockerPlugin("locker/battery")
+ WayfireLockerMultiOutputPlugin("locker/battery")
{}
diff --git a/src/locker/plugin/battery.hpp b/src/locker/plugin/battery.hpp
index fadb7d682..5e531799d 100644
--- a/src/locker/plugin/battery.hpp
+++ b/src/locker/plugin/battery.hpp
@@ -1,65 +1,28 @@
#pragma once
-#include
-#include
-#include
-#include
-#include
-#include
-#include "plugin.hpp"
-#include "lockergrid.hpp"
-#include "timedrevealer.hpp"
+#include "multi-output-timed-plugin.hpp"
+#include "widget-utils/battery.hpp"
-using DBusConnection = Glib::RefPtr;
-using DBusProxy = Glib::RefPtr;
-/* Shamelessly copied in from battery in panel */
-
-#define UPOWER_NAME "org.freedesktop.UPower"
-#define DISPLAY_DEVICE "/org/freedesktop/UPower/devices/DisplayDevice"
-
-#define ICON "IconName"
-#define TYPE "Type"
-#define STATE "State"
-#define PERCENTAGE "Percentage"
-#define TIMETOFULL "TimeToFull"
-#define TIMETOEMPTY "TimeToEmpty"
-#define SHOULD_DISPLAY "IsPresent"
-
-class WayfireLockerBatteryPluginWidget : public WayfireLockerTimedRevealer
+class WayfireLockerBatteryPluginWidget : public WayfireLockerTimedWidget
{
public:
- Gtk::Grid grid;
- Gtk::Image image;
- Gtk::Label label, subtext;
- WayfireLockerBatteryPluginWidget();
+ WayfireLockerBatteryPluginWidget() :
+ WayfireLockerTimedWidget("locker/battery_always")
+ {}
};
-class WayfireLockerBatteryPlugin : public WayfireLockerPlugin
+class WayfireLockerBatteryPlugin :
+ public WayfireLockerMultiOutputPlugin
{
private:
- DBusConnection connection;
- DBusProxy upower_proxy, display_device;
- void on_properties_changed(
- const Gio::DBus::Proxy::MapChangedProperties& properties,
- const std::vector& invalidated);
- bool setup_dbus();
sigc::connection signal;
+ protected:
+ std::shared_ptr create_widget() override
+ {
+ return std::make_shared();
+ }
+
public:
WayfireLockerBatteryPlugin();
- void add_output(std::string id, std::shared_ptr grid) override;
- void remove_output(std::string id, std::shared_ptr grid) override;
- void init() override;
- void deinit() override;
- void hide();
- void show();
- bool show_state = true;
-
- void update_percentages(std::string text);
- void update_descriptions(std::string text);
- void update_images();
- void update_details();
-
-
- std::map> widgets;
};
diff --git a/src/locker/plugin/clock.cpp b/src/locker/plugin/clock.cpp
index 7b0e71985..6dd318e98 100644
--- a/src/locker/plugin/clock.cpp
+++ b/src/locker/plugin/clock.cpp
@@ -1,80 +1,5 @@
-#include
-#include
-#include
-
-#include "lockergrid.hpp"
-#include "timedrevealer.hpp"
#include "clock.hpp"
-
-void WayfireLockerClockPlugin::update_labels(std::string text)
-{
- for (auto& it : widgets)
- {
- it.second->label.set_markup(text);
- }
-
- label_contents = text;
-}
-
-WayfireLockerClockPluginWidget::WayfireLockerClockPluginWidget(std::string contents) :
- WayfireLockerTimedRevealer("locker/clock_always")
-{
- set_child(label);
- label.add_css_class("clock");
- label.set_markup(contents);
- label.set_justify(Gtk::Justification::CENTER);
-}
-
-void WayfireLockerClockPlugin::add_output(std::string id, std::shared_ptr grid)
-{
- widgets.emplace(id, new WayfireLockerClockPluginWidget(label_contents));
- auto widget = widgets[id];
-
- grid->attach(*widget, position);
-}
-
-void WayfireLockerClockPlugin::remove_output(std::string id, std::shared_ptr grid)
-{
- grid->remove(*widgets[id]);
- widgets.erase(id);
-}
-
-void WayfireLockerClockPlugin::update_time()
-{
- auto time = Glib::DateTime::create_now_local();
- auto text = time.format((std::string)format);
-
- /* Sometimes GLib::DateTime will add leading spaces. This results in
- * unevenly balanced padding around the text, which looks quite bad.
- *
- * This could be circumvented with the modifiers the user passes to the
- * format string, * but to remove the requirement that the user does
- * something fancy, we just remove any leading spaces. */
- int i = 0;
- while (i < (int)text.length() && text[i] == ' ')
- {
- i++;
- }
-
- this->update_labels(text.substr(i));
-}
-
WayfireLockerClockPlugin::WayfireLockerClockPlugin() :
- WayfireLockerPlugin("locker/clock")
+ WayfireLockerMultiOutputPlugin("locker/clock")
{}
-
-void WayfireLockerClockPlugin::init()
-{
- timeout = Glib::signal_timeout().connect_seconds(
- [this] ()
- {
- this->update_time();
- return G_SOURCE_CONTINUE;
- }, 1);
-}
-
-void WayfireLockerClockPlugin::deinit()
-{
- timeout.disconnect();
-}
diff --git a/src/locker/plugin/clock.hpp b/src/locker/plugin/clock.hpp
index 4f27fd40d..88a184683 100644
--- a/src/locker/plugin/clock.hpp
+++ b/src/locker/plugin/clock.hpp
@@ -1,34 +1,25 @@
#pragma once
-#include
-#include
-#include "plugin.hpp"
-#include "timedrevealer.hpp"
-#include "wf-option-wrap.hpp"
-#include "lockergrid.hpp"
+#include "multi-output-timed-plugin.hpp"
+#include "widget-utils/clock.hpp"
-class WayfireLockerClockPluginWidget : public WayfireLockerTimedRevealer
+class WayfireLockerClockPluginWidget : public WayfireLockerTimedWidget
{
public:
- Gtk::Label label;
- WayfireLockerClockPluginWidget(std::string contents);
+ WayfireLockerClockPluginWidget() :
+ WayfireLockerTimedWidget("locker/clock_always", "clock")
+ {}
};
-class WayfireLockerClockPlugin : public WayfireLockerPlugin
+class WayfireLockerClockPlugin :
+ public WayfireLockerMultiOutputPlugin
{
+ protected:
+ std::shared_ptr create_widget() override
+ {
+ return std::make_shared();
+ }
+
public:
WayfireLockerClockPlugin();
- void add_output(std::string id, std::shared_ptr grid) override;
- void remove_output(std::string id, std::shared_ptr grid) override;
- void init() override;
- void deinit() override;
-
- WfOption format{"locker/clock_format"};
-
- sigc::connection timeout;
- void update_labels(std::string text);
- void update_time();
-
- std::unordered_map> widgets;
- std::string label_contents = "";
};
diff --git a/src/locker/plugin/multi-output-timed-plugin.hpp b/src/locker/plugin/multi-output-timed-plugin.hpp
new file mode 100644
index 000000000..837826cce
--- /dev/null
+++ b/src/locker/plugin/multi-output-timed-plugin.hpp
@@ -0,0 +1,89 @@
+#pragma once
+
+#include
+#include
+
+#include "plugin.hpp"
+#include "timedrevealer.hpp"
+#include "lockergrid.hpp"
+
+template
+class WayfireLockerMultiOutputPlugin : public WayfireLockerPlugin
+{
+ protected:
+ std::unordered_map> widgets;
+ bool shown = true;
+
+ virtual std::shared_ptr create_widget() = 0;
+
+ public:
+ WayfireLockerMultiOutputPlugin(std::string prefix) :
+ WayfireLockerPlugin(prefix)
+ {}
+
+ void add_output(std::string id, std::shared_ptr grid) override
+ {
+ auto widget = create_widget();
+ widgets.emplace(id, widget);
+
+ if (!shown)
+ {
+ widget->hide();
+ }
+
+ grid->attach(*widget, position);
+ }
+
+ void remove_output(std::string id, std::shared_ptr grid) override
+ {
+ auto it = widgets.find(id);
+ if (it == widgets.end())
+ {
+ return;
+ }
+
+ grid->remove(*it->second);
+ widgets.erase(it);
+ }
+
+ void hide()
+ {
+ shown = false;
+ for (auto& it : widgets)
+ {
+ it.second->hide();
+ }
+ }
+
+ void show()
+ {
+ shown = true;
+ for (auto& it : widgets)
+ {
+ it.second->show();
+ }
+ }
+
+ void init() override
+ {}
+ void deinit() override
+ {}
+};
+
+template
+class WayfireLockerTimedWidget : public WayfireLockerTimedRevealer
+{
+ public:
+ InnerWidgetT inner;
+
+ WayfireLockerTimedWidget(std::string always_option, const char *css_class = nullptr) :
+ WayfireLockerTimedRevealer(always_option),
+ inner("locker")
+ {
+ set_child(inner);
+ if (css_class != nullptr)
+ {
+ inner.add_css_class(css_class);
+ }
+ }
+};
diff --git a/src/locker/plugin/network.hpp b/src/locker/plugin/network.hpp
index 567a305d4..acdef424f 100644
--- a/src/locker/plugin/network.hpp
+++ b/src/locker/plugin/network.hpp
@@ -4,11 +4,9 @@
#include
#include
#include "lockergrid.hpp"
-#include "network/network.hpp"
+#include "widget-utils/network/network.hpp"
#include "plugin.hpp"
-#include "../../util/network/manager.hpp"
-
-
+#include "widget-utils/network/manager.hpp"
class WayfireLockerNetworkPluginWidget : public WayfireLockerTimedRevealer
{
diff --git a/src/locker/plugin/volume.cpp b/src/locker/plugin/volume.cpp
index bff91817b..cc6eaff97 100644
--- a/src/locker/plugin/volume.cpp
+++ b/src/locker/plugin/volume.cpp
@@ -1,190 +1,15 @@
-#include
-#include
-#include
-
-#include "lockergrid.hpp"
#include "volume.hpp"
-#include "plugin.hpp"
-#include "timedrevealer.hpp"
-
-static void default_sink_changed(GvcMixerControl *gvc_control,
- guint id, gpointer user_data)
-{
- WayfireLockerVolumePlugin *plugin = (WayfireLockerVolumePlugin*)user_data;
- plugin->on_default_sink_changed();
-}
-
-static void default_source_changed(GvcMixerControl *gvc_control,
- guint id, gpointer user_data)
-{
- WayfireLockerVolumePlugin *plugin = (WayfireLockerVolumePlugin*)user_data;
- plugin->on_default_source_changed();
-}
-
-static void notify_sink_muted(GvcMixerControl *gvc_control,
- guint id, gpointer user_data)
-{
- WayfireLockerVolumePlugin *plugin = (WayfireLockerVolumePlugin*)user_data;
- plugin->update_button_images();
-}
-
-static void notify_source_muted(GvcMixerControl *gvc_control,
- guint id, gpointer user_data)
-{
- WayfireLockerVolumePlugin *plugin = (WayfireLockerVolumePlugin*)user_data;
- plugin->update_button_images();
-}
-void WayfireLockerVolumePlugin::update_button_images()
+LockerVolumes::LockerVolumes(const std::string& section) :
+ volume(StreamRole::Sink, section),
+ mic(StreamRole::Source, section)
{
- for (auto & it : widgets)
- {
- if (gvc_sink_stream)
- {
- it.second->sink_button.set_icon_name(gvc_mixer_stream_get_is_muted(
- gvc_sink_stream) ? "audio-volume-muted-symbolic" : "audio-volume-high-symbolic");
- }
-
- if (gvc_source_stream)
- {
- it.second->source_button.set_icon_name(gvc_mixer_stream_get_is_muted(
- gvc_source_stream) ? "microphone-sensitivity-muted-symbolic" : "microphone-sensitivity-high-symbolic");
- }
- }
+ append(volume);
+ volume.set_orientation(Gtk::Orientation::VERTICAL);
+ append(mic);
+ mic.set_orientation(Gtk::Orientation::VERTICAL);
}
WayfireLockerVolumePlugin::WayfireLockerVolumePlugin() :
- WayfireLockerPlugin("locker/volume")
+ WayfireLockerMultiOutputPlugin("locker/volume")
{}
-
-WayfireLockerVolumePluginWidget::WayfireLockerVolumePluginWidget() :
- WayfireLockerTimedRevealer("locker/volume_always")
-{
- set_child(box);
- box.add_css_class("volume");
- sink_button.add_css_class("volume-button");
- source_button.add_css_class("mic-button");
-
- box.append(source_button);
- box.append(sink_button);
-}
-
-void WayfireLockerVolumePlugin::add_output(std::string id, std::shared_ptr grid)
-{
- widgets.emplace(id, new WayfireLockerVolumePluginWidget());
- auto widget = widgets[id];
- grid->attach(*widget, position);
-
- widget->sink_button.signal_clicked().connect(
- [=] ()
- {
- if (!gvc_sink_stream)
- {
- return;
- }
-
- bool muted = gvc_mixer_stream_get_is_muted(gvc_sink_stream);
- gvc_mixer_stream_change_is_muted(gvc_sink_stream, !muted);
- gvc_mixer_stream_push_volume(gvc_sink_stream);
- });
- widget->source_button.signal_clicked().connect(
- [=] ()
- {
- if (!gvc_source_stream)
- {
- return;
- }
-
- bool muted = gvc_mixer_stream_get_is_muted(gvc_source_stream);
- gvc_mixer_stream_change_is_muted(gvc_source_stream, !muted);
- gvc_mixer_stream_push_volume(gvc_source_stream);
- });
- update_button_images();
-}
-
-void WayfireLockerVolumePlugin::remove_output(std::string id, std::shared_ptr grid)
-{
- grid->remove(*widgets[id]);
- widgets.erase(id);
-}
-
-void WayfireLockerVolumePlugin::init()
-{
- /* Setup gvc control */
- gvc_control = gvc_mixer_control_new("Wayfire Volume Control");
- notify_sink_changed = g_signal_connect(gvc_control,
- "default-sink-changed", G_CALLBACK(default_sink_changed), this);
- notify_source_changed = g_signal_connect(gvc_control,
- "default-source-changed", G_CALLBACK(default_source_changed), this);
- gvc_mixer_control_open(gvc_control);
-}
-
-void WayfireLockerVolumePlugin::deinit()
-{
- disconnect_gvc_stream_sink_signals();
- disconnect_gvc_stream_source_signals();
- if (notify_sink_muted_signal)
- {
- g_signal_handler_disconnect(gvc_sink_stream, notify_sink_muted_signal);
- }
-
- notify_sink_changed = 0;
- if (notify_sink_muted_signal)
- {
- g_signal_handler_disconnect(gvc_sink_stream, notify_sink_muted_signal);
- }
-
- notify_source_changed = 0;
-}
-
-void WayfireLockerVolumePlugin::disconnect_gvc_stream_sink_signals()
-{
- if (notify_sink_muted_signal)
- {
- g_signal_handler_disconnect(gvc_sink_stream, notify_sink_muted_signal);
- }
-
- notify_sink_muted_signal = 0;
-}
-
-void WayfireLockerVolumePlugin::disconnect_gvc_stream_source_signals()
-{
- if (notify_source_muted_signal)
- {
- g_signal_handler_disconnect(gvc_source_stream, notify_source_muted_signal);
- }
-
- notify_source_muted_signal = 0;
-}
-
-void WayfireLockerVolumePlugin::on_default_sink_changed()
-{
- gvc_sink_stream = gvc_mixer_control_get_default_sink(gvc_control);
- if (!gvc_sink_stream)
- {
- printf("GVC: Failed to get default sink\n");
- return;
- }
-
- /* Reconnect signals to new sink */
- disconnect_gvc_stream_sink_signals();
- notify_sink_muted_signal = g_signal_connect(gvc_sink_stream, "notify::is-muted",
- G_CALLBACK(notify_sink_muted), this);
- update_button_images();
-}
-
-void WayfireLockerVolumePlugin::on_default_source_changed()
-{
- gvc_source_stream = gvc_mixer_control_get_default_source(gvc_control);
- if (!gvc_source_stream)
- {
- printf("GVC: Failed to get default source\n");
- return;
- }
-
- /* Reconnect signals to new source */
- disconnect_gvc_stream_source_signals();
- notify_source_muted_signal = g_signal_connect(gvc_source_stream, "notify::is-muted",
- G_CALLBACK(notify_source_muted), this);
- update_button_images();
-}
diff --git a/src/locker/plugin/volume.hpp b/src/locker/plugin/volume.hpp
index 23ece11d6..0ce07e499 100644
--- a/src/locker/plugin/volume.hpp
+++ b/src/locker/plugin/volume.hpp
@@ -1,47 +1,36 @@
#pragma once
-#include
-#include
-#include
-#include
-#include "gvc-mixer-control.h"
-#include "plugin.hpp"
-#include "lockergrid.hpp"
-#include "timedrevealer.hpp"
+#include "multi-output-timed-plugin.hpp"
+#include "widget-utils/pulseaudio/control.hpp"
-class WayfireLockerVolumePluginWidget : public WayfireLockerTimedRevealer
+class LockerVolumes : public Gtk::Box
+{
+ GvcControl volume, mic;
+
+ public:
+ LockerVolumes(const std::string& section);
+};
+
+class WayfireLockerVolumePluginWidget : public WayfireLockerTimedWidget
{
public:
- Gtk::Box box;
- Gtk::Button sink_button;
- Gtk::Button source_button;
- WayfireLockerVolumePluginWidget();
+ WayfireLockerVolumePluginWidget() :
+ WayfireLockerTimedWidget("locker/volume_always")
+ {}
};
-class WayfireLockerVolumePlugin : public WayfireLockerPlugin
+class WayfireLockerVolumePlugin :
+ public WayfireLockerMultiOutputPlugin
{
private:
- GvcMixerControl *gvc_control;
- GvcMixerStream *gvc_sink_stream = NULL;
- GvcMixerStream *gvc_source_stream = NULL;
- gulong notify_sink_muted_signal = 0;
- gulong notify_source_muted_signal = 0;
- gulong notify_sink_changed = 0;
- gulong notify_source_changed = 0;
- void disconnect_gvc_stream_sink_signals();
- void disconnect_gvc_stream_source_signals();
+ sigc::connection signal;
+
+ protected:
+ std::shared_ptr create_widget() override
+ {
+ return std::make_shared();
+ }
public:
WayfireLockerVolumePlugin();
- void add_output(std::string id, std::shared_ptr grid) override;
- void remove_output(std::string id, std::shared_ptr grid) override;
- void init() override;
- void deinit() override;
- void update_button_images();
-
- /** Called when the default sink changes */
- void on_default_sink_changed();
- void on_default_source_changed();
-
- std::map> widgets;
};
diff --git a/src/locker/plugin/weather.cpp b/src/locker/plugin/weather.cpp
index f87873d89..6750a2b6c 100644
--- a/src/locker/plugin/weather.cpp
+++ b/src/locker/plugin/weather.cpp
@@ -1,165 +1,5 @@
-#include
-#include
-#include
-#include
-
-#include "gtkmm/enums.h"
-#include "lockergrid.hpp"
#include "weather.hpp"
-#include "timedrevealer.hpp"
-#include "wayfire/nonstd/json.hpp"
-
-
-void WayfireLockerWeatherPlugin::update_labels(std::string text)
-{
- for (auto& it : weather_widgets)
- {
- it.second->label.set_markup(text);
- }
-
- label_contents = text;
-}
-
-void WayfireLockerWeatherPlugin::update_icons(std::string path)
-{
- for (auto& it : weather_widgets)
- {
- it.second->image.set(path);
- }
-
- icon_path = path;
-}
-
-WayfireLockerWeatherPluginWidget::WayfireLockerWeatherPluginWidget(std::string contents,
- std::string icon_path) :
- WayfireLockerTimedRevealer("locker/weather_always")
-{
- set_child(box);
- label.add_css_class("weather");
- label.set_markup(contents);
- label.set_justify(Gtk::Justification::CENTER);
- box.prepend(label);
-
- image.add_css_class("weather");
- image.set(icon_path);
- box.append(image);
-}
-
-void WayfireLockerWeatherPlugin::add_output(std::string id, std::shared_ptr grid)
-{
- weather_widgets.emplace(id, new WayfireLockerWeatherPluginWidget(label_contents, icon_path));
-
- auto weather_widget = weather_widgets[id];
- if (!shown)
- {
- weather_widget->hide();
- }
-
- grid->attach(*weather_widget, position);
-}
-
-void WayfireLockerWeatherPlugin::remove_output(std::string id, std::shared_ptr grid)
-{
- grid->remove(*weather_widgets[id]);
- weather_widgets.erase(id);
-}
-
-bool WayfireLockerWeatherPlugin::handle_inotify_event(Glib::IOCondition cond)
-{
- if (cond == Glib::IOCondition::IO_HUP)
- {
- return false;
- }
-
- char buf[1024 * sizeof(inotify_event)];
- read(inotify_fd, buf, sizeof(buf));
-
- update_weather();
-
- return true;
-}
-
-void WayfireLockerWeatherPlugin::update_weather()
-{
- std::ifstream input_file(weather_data_path);
-
- if (!input_file)
- {
- std::cerr << "Error reading json file " << weather_data_path << std::endl;
- hide();
- return;
- }
-
- std::stringstream buf;
- buf << input_file.rdbuf();
-
- wf::json_t json_data;
-
- auto err = wf::json_t::parse_string(buf.str(), json_data);
-
- if (err.has_value())
- {
- std::cerr << "Error parsing json data " << weather_data_path << ": " << *err << std::endl;
- hide();
- return;
- }
-
- if (!json_data.has_member("temp") || !json_data.has_member("icon"))
- {
- std::cerr << "Unexpected weather json data in " << weather_data_path << std::endl;
- hide();
- return;
- }
-
- update_labels(json_data["temp"]);
- update_icons(json_data["icon"]);
-
- show();
-}
WayfireLockerWeatherPlugin::WayfireLockerWeatherPlugin() :
- WayfireLockerPlugin("locker/weather")
+ WayfireLockerMultiOutputPlugin("locker/weather")
{}
-
-void WayfireLockerWeatherPlugin::init()
-{
- weather_data_path = std::string(getenv("HOME")) + "/.local/share/owf/data/data.json";
-
- inotify_fd = inotify_init();
-
- inotify_add_watch(inotify_fd,
- weather_data_path.c_str(),
- IN_CLOSE_WRITE);
-
- inotify_connection = Glib::signal_io().connect(
- sigc::mem_fun(*this, &WayfireLockerWeatherPlugin::handle_inotify_event),
- inotify_fd, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP);
-
- update_weather();
-}
-
-void WayfireLockerWeatherPlugin::deinit()
-{
- inotify_connection.disconnect();
- close(inotify_fd);
-}
-
-void WayfireLockerWeatherPlugin::hide()
-{
- for (auto& it : weather_widgets)
- {
- it.second->hide();
- }
-
- shown = false;
-}
-
-void WayfireLockerWeatherPlugin::show()
-{
- for (auto& it : weather_widgets)
- {
- it.second->show();
- }
-
- shown = true;
-}
diff --git a/src/locker/plugin/weather.hpp b/src/locker/plugin/weather.hpp
index 863e2fa21..031b42a6f 100644
--- a/src/locker/plugin/weather.hpp
+++ b/src/locker/plugin/weather.hpp
@@ -1,47 +1,25 @@
#pragma once
-#include
-#include
-#include
-#include
-#include
-#include "plugin.hpp"
-#include "timedrevealer.hpp"
-#include "wf-option-wrap.hpp"
-#include "lockergrid.hpp"
+#include "multi-output-timed-plugin.hpp"
+#include "widget-utils/weather.hpp"
-class WayfireLockerWeatherPluginWidget : public WayfireLockerTimedRevealer
+class WayfireLockerWeatherPluginWidget : public WayfireLockerTimedWidget
{
public:
- Gtk::Box box;
- Gtk::Label label;
- Gtk::Image image;
- WayfireLockerWeatherPluginWidget(std::string contents, std::string icon_path);
+ WayfireLockerWeatherPluginWidget() :
+ WayfireLockerTimedWidget("locker/weather_always", "weather")
+ {}
};
-class WayfireLockerWeatherPlugin : public WayfireLockerPlugin
+class WayfireLockerWeatherPlugin :
+ public WayfireLockerMultiOutputPlugin
{
+ protected:
+ std::shared_ptr create_widget() override
+ {
+ return std::make_shared();
+ }
+
public:
WayfireLockerWeatherPlugin();
- void add_output(std::string id, std::shared_ptr grid) override;
- void remove_output(std::string id, std::shared_ptr grid) override;
- void init() override;
- void deinit() override;
-
-
- int inotify_fd;
- sigc::connection inotify_connection;
- std::string weather_data_path;
- bool handle_inotify_event(Glib::IOCondition cond);
- void update_weather();
- void update_labels(std::string text);
- void update_icons(std::string path);
-
- void hide();
- void show();
-
- std::unordered_map> weather_widgets;
- std::string label_contents = "";
- std::string icon_path = "";
- bool shown;
};
diff --git a/src/panel/meson.build b/src/panel/meson.build
index ee51c0a0a..f8a238640 100644
--- a/src/panel/meson.build
+++ b/src/panel/meson.build
@@ -1,5 +1,6 @@
widget_sources = [
'widgets/battery.cpp',
+ 'widgets/power-profiles.cpp',
'widgets/menu/menu.cpp',
'widgets/menu/layout.cpp',
'widgets/menu/logout.cpp',
@@ -92,4 +93,4 @@ executable(
['panel.cpp'] + widget_sources,
dependencies: deps,
install: true,
-)
\ No newline at end of file
+)
diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp
index eef0910ee..644d09769 100644
--- a/src/panel/panel.cpp
+++ b/src/panel/panel.cpp
@@ -14,8 +14,8 @@
#include
#include "panel.hpp"
-#include "network/manager.hpp"
#include "widgets/battery.hpp"
+#include "widgets/power-profiles.hpp"
#include "widgets/command-output.hpp"
#include "widgets/language.hpp"
#include "widgets/menu/menu.hpp"
@@ -232,10 +232,25 @@ class WayfirePanel::impl
return Widget(new WayfireBatteryInfo());
}
+ if (name == "power-profiles")
+ {
+ return Widget(new WayfirePowerProfiles());
+ }
+
if (name == "volume")
{
#ifdef HAVE_PULSE
- return Widget(new WayfireVolume());
+ return Widget(new WayfireVolume(StreamRole::Sink));
+#else
+ std::cerr << "Built without pulse support, volume widget "
+ " is not available." << std::endl;
+#endif
+ }
+
+ if (name == "microphone")
+ {
+#ifdef HAVE_PULSE
+ return Widget(new WayfireVolume(StreamRole::Source));
#else
std::cerr << "Built without pulse support, volume widget "
" is not available." << std::endl;
@@ -606,6 +621,7 @@ void WayfirePanelApp::on_activate()
{"panel/menu_category_icon_size", ".app-category .default-icon"},
{"panel/launchers_size", ".launcher.widget-icon"},
{"panel/battery_icon_size", ".battery image.widget-icon"},
+ {"panel/power_profiles_icon_size", ".power-profiles image.widget-icon"},
{"panel/network_icon_size", ".network .widget-icon"},
{"panel/volume_icon_size", ".volume .widget-icon"},
{"panel/mixer_icon_size", ".mixer .widget-icon"},
@@ -628,6 +644,7 @@ void WayfirePanelApp::on_activate()
new CssFromConfigBool("panel/network_icon_invert_color", ".network-icon{filter:invert(100%);}", "");
new CssFromConfigFont("panel/battery_font", ".battery {", "}");
+ new CssFromConfigFont("panel/power_profiles_font", ".power_profiles {", "}");
new CssFromConfigFont("panel/clock_font", ".clock {", "}");
new CssFromConfigFont("panel/weather_font", ".weather {", "}");
}
diff --git a/src/panel/widgets/battery.cpp b/src/panel/widgets/battery.cpp
index 41f56be71..e2684ef75 100644
--- a/src/panel/widgets/battery.cpp
+++ b/src/panel/widgets/battery.cpp
@@ -1,280 +1,6 @@
#include
-#include
#include "battery.hpp"
-#define POWER_PROFILE_PATH "/org/freedesktop/UPower/PowerProfiles"
-#define POWER_PROFILE_NAME "org.freedesktop.UPower.PowerProfiles"
-#define UPOWER_NAME "org.freedesktop.UPower"
-#define DISPLAY_DEVICE "/org/freedesktop/UPower/devices/DisplayDevice"
-
-#define ICON "IconName"
-#define TYPE "Type"
-#define STATE "State"
-#define PERCENTAGE "Percentage"
-#define TIMETOFULL "TimeToFull"
-#define TIMETOEMPTY "TimeToEmpty"
-#define SHOULD_DISPLAY "IsPresent"
-
-#define DEGRADED "PerformanceDegraded"
-#define PROFILES "Profiles"
-#define ACTIVE_PROFILE "ActiveProfile"
-
-static std::string get_device_type_description(uint32_t type)
-{
- if (type == 2)
- {
- return "Battery ";
- }
-
- if (type == 3)
- {
- return "UPS ";
- }
-
- return "";
-}
-
-void WayfireBatteryInfo::on_properties_changed(
- const Gio::DBus::Proxy::MapChangedProperties& properties,
- const std::vector& invalidated)
-{
- bool invalid_icon = false, invalid_details = false;
- bool invalid_state = false;
- for (auto& prop : properties)
- {
- if (prop.first == ICON)
- {
- invalid_icon = true;
- }
-
- if ((prop.first == TYPE) || (prop.first == STATE) || (prop.first == PERCENTAGE) ||
- (prop.first == TIMETOFULL) || (prop.first == TIMETOEMPTY))
- {
- invalid_details = true;
- }
-
- if (prop.first == SHOULD_DISPLAY)
- {
- invalid_state = true;
- }
- }
-
- if (invalid_icon)
- {
- update_icon();
- }
-
- if (invalid_details)
- {
- update_details();
- }
-
- if (invalid_state)
- {
- update_state();
- }
-}
-
-void WayfireBatteryInfo::update_icon()
-{
- if (!feat_bat && feat_modes)
- {
- icon.set_from_icon_name("power-profile-" + power_mode);
- return;
- }
-
- Glib::Variant icon_name;
- display_device->get_cached_property(icon_name, ICON);
- icon.set_from_icon_name(icon_name.get());
-}
-
-static std::string state_descriptions[] = {
- "Unknown", // 0
- "Charging", // 1
- "Discharging", // 2
- "Empty", // 3
- "Fully charged", // 4
- "Pending charge", // 5
- "Pending discharge", // 6
-};
-
-static bool is_charging(uint32_t state)
-{
- return (state == 1) || (state == 5);
-}
-
-static bool is_discharging(uint32_t state)
-{
- return (state == 2) || (state == 6);
-}
-
-static std::string format_digit(int digit)
-{
- return digit <= 9 ? ("0" + std::to_string(digit)) :
- std::to_string(digit);
-}
-
-static std::string uint_to_time(int64_t time)
-{
- int hrs = time / 3600;
- int min = (time / 60) % 60;
-
- return format_digit(hrs) + ":" + format_digit(min);
-}
-
-void WayfireBatteryInfo::update_details()
-{
- Glib::Variant type;
- display_device->get_cached_property(type, TYPE);
-
- Glib::Variant vstate;
- display_device->get_cached_property(vstate, STATE);
- uint32_t state = vstate.get();
-
- Glib::Variant vpercentage;
- display_device->get_cached_property(vpercentage, PERCENTAGE);
- auto percentage_string = std::to_string((int)vpercentage.get()) + "%";
-
- Glib::Variant time_to_full;
- display_device->get_cached_property(time_to_full, TIMETOFULL);
-
- Glib::Variant time_to_empty;
- display_device->get_cached_property(time_to_empty, TIMETOEMPTY);
-
- std::string description = percentage_string + ", " + state_descriptions[state];
- if (is_charging(state))
- {
- description += ", " + uint_to_time(time_to_full.get()) + " until full";
- } else if (is_discharging(state))
- {
- description += ", " + uint_to_time(time_to_empty.get()) + " remaining";
- }
-
- box->set_tooltip_text(
- get_device_type_description(type.get()) + description);
-
- if (status_opt.value() == BATTERY_STATUS_PERCENT)
- {
- label.set_text(percentage_string);
- overlay.remove_overlay(label);
- box->set_child(label);
- } else if (status_opt.value() == BATTERY_STATUS_FULL)
- {
- label.set_text(description);
- auto children = overlay.get_children();
- if (std::count(children.begin(), children.end(), &label))
- {
- overlay.remove_overlay(label);
- }
-
- box->set_child(label);
- } else if (status_opt.value() == BATTERY_STATUS_OVERLAY)
- {
- label.set_text(percentage_string);
- auto children = box->get_children();
- if (std::count(children.begin(), children.end(), &label))
- {
- box->remove(label);
- }
-
- overlay.add_overlay(label);
- }
-
- if (status_opt.value() == BATTERY_STATUS_ICON)
- {
- label.hide();
- } else
- {
- label.show();
- }
-}
-
-void WayfireBatteryInfo::update_state()
-{
- std::cout << "unimplemented reached, in battery.cpp: "
- "\n\tWayfireBatteryInfo::update_state()" << std::endl;
-}
-
-bool WayfireBatteryInfo::setup_dbus_power_modes()
-{
- auto cancellable = Gio::Cancellable::create();
- connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable);
- if (!connection)
- {
- std::cerr << "Failed to connect to dbus" << std::endl;
- return false;
- }
-
- powerprofile_proxy = Gio::DBus::Proxy::create_sync(connection, POWER_PROFILE_NAME,
- POWER_PROFILE_PATH,
- POWER_PROFILE_NAME);
- if (!powerprofile_proxy)
- {
- std::cout << "Unable to conect to Power Profiles. Continuing" << std::endl;
- } else
- {
- powerprofile_proxy->signal_properties_changed().connect(
- sigc::mem_fun(*this, &WayfireBatteryInfo::on_upower_properties_changed));
- Glib::Variant current_profile;
- Glib::Variant>> profiles;
- powerprofile_proxy->get_cached_property(current_profile, ACTIVE_PROFILE);
- powerprofile_proxy->get_cached_property(profiles, PROFILES);
-
- if (profiles && current_profile)
- {
- setup_profiles(profiles.get());
- set_current_profile(current_profile.get());
- return true;
- } else
- {
- std::cout << "Unable to conect to Power Profiles. Continuing" << std::endl;
- return false;
- }
- }
-
- return false;
-}
-
-bool WayfireBatteryInfo::setup_dbus_battery()
-{
- auto cancellable = Gio::Cancellable::create();
- connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable);
- if (!connection)
- {
- std::cerr << "Failed to connect to dbus" << std::endl;
- return false;
- }
-
- upower_proxy = Gio::DBus::Proxy::create_sync(connection, UPOWER_NAME,
- "/org/freedesktop/UPower",
- "org.freedesktop.UPower");
- if (!upower_proxy)
- {
- std::cerr << "Failed to connect to UPower" << std::endl;
- return false;
- }
-
- display_device = Gio::DBus::Proxy::create_sync(connection,
- UPOWER_NAME,
- DISPLAY_DEVICE,
- "org.freedesktop.UPower.Device");
- if (!display_device)
- {
- return false;
- }
-
- Glib::Variant present;
- display_device->get_cached_property(present, SHOULD_DISPLAY);
- if (present.get())
- {
- disp_dev_sig = display_device->signal_properties_changed().connect(
- sigc::mem_fun(*this, &WayfireBatteryInfo::on_properties_changed));
-
- return true;
- }
-
- return false;
-}
void WayfireBatteryInfo::update_layout()
{
@@ -282,10 +8,10 @@ void WayfireBatteryInfo::update_layout()
if (panel_position.value() == PANEL_POSITION_LEFT or panel_position.value() == PANEL_POSITION_RIGHT)
{
- box->set_orientation(Gtk::Orientation::VERTICAL);
+ battery.set_orientation(Gtk::Orientation::VERTICAL);
} else
{
- box->set_orientation(Gtk::Orientation::HORIZONTAL);
+ battery.set_orientation(Gtk::Orientation::HORIZONTAL);
}
}
@@ -294,139 +20,8 @@ void WayfireBatteryInfo::handle_config_reload()
update_layout();
}
-// TODO: simplify config loading
-
void WayfireBatteryInfo::init(Gtk::Box *container)
{
- profiles_menu = Gio::Menu::create();
- state_action = Gio::SimpleAction::create_radio_string("set_profile", "");
-
- // the two features are battery displaying and power modes
- feat_bat = setup_dbus_battery();
- feat_modes = setup_dbus_power_modes();
-
- // ignore if we can’t do either
- if (!feat_bat && !feat_modes)
- {
- return;
- }
-
- box = std::make_unique("panel", "battery");
-
- box->set_child(overlay);
- overlay.set_child(icon);
- icon.add_css_class("widget-icon");
-
- if (feat_bat)
- {
- status_opt.set_callback([=] () { update_details(); });
- update_details();
- }
-
- update_icon();
-
- if (feat_modes)
- {
- auto actions = Gio::SimpleActionGroup::create();
-
- state_action->signal_activate().connect([=] (Glib::VariantBase vb)
- {
- // User has requested a change of state. Don't change the UI choice,
- // let the dbus roundtrip happen to be sure.
- if (vb.is_of_type(Glib::VariantType("s")))
- {
- // Couldn't seem to make proxy send property back, so this will have to do
- Glib::VariantContainerBase params = Glib::Variant>::create({POWER_PROFILE_NAME, ACTIVE_PROFILE, vb});
-
- connection->call_sync(
- POWER_PROFILE_PATH,
- "org.freedesktop.DBus.Properties",
- "Set",
- params,
- NULL,
- POWER_PROFILE_NAME,
- -1,
- Gio::DBus::CallFlags::NONE,
- {});
- }
- });
-
- actions->add_action(state_action);
-
- box->open_on(1);
- box->insert_action_group("actions", actions);
- box->set_spacing(5);
- box->set_menu_model(profiles_menu);
- }
-
- container->append(*box);
-
- icon.property_scale_factor().signal_changed()
- .connect(sigc::mem_fun(*this, &WayfireBatteryInfo::update_icon));
-
+ container->append(battery);
update_layout();
}
-
-void WayfireBatteryInfo::on_upower_properties_changed(
- const Gio::DBus::Proxy::MapChangedProperties& properties,
- const std::vector& invalidated)
-{
- for (auto& prop : properties)
- {
- if (prop.first == ACTIVE_PROFILE)
- {
- if (prop.second.is_of_type(Glib::VariantType("s")))
- {
- auto value_string =
- Glib::VariantBase::cast_dynamic>(prop.second).get();
- set_current_profile(value_string);
- }
- } else if (prop.first == PROFILES)
- {
- // I've been unable to find a way to change possible profiles on the fly, so cannot confirm this
- // works at all.
- auto value = Glib::VariantBase::cast_dynamic>>>(prop.second);
- setup_profiles(value.get());
- }
-
- // TODO Consider watching for "Performance Degraded" events too, but we currently have no way to
- // output this additional information
- }
-}
-
-void WayfireBatteryInfo::set_current_profile(Glib::ustring profile)
-{
- power_mode = profile;
- state_action->set_state(Glib::Variant::create(profile));
- update_icon();
-}
-
-void WayfireBatteryInfo::setup_profiles(std::vector> profiles)
-{
- profiles_menu->remove_all();
- for (auto profile : profiles)
- {
- if (profile.count("Profile") == 1)
- {
- Glib::VariantBase value = profile.at("Profile");
- if (value.is_of_type(Glib::VariantType("s")))
- {
- auto value_string =
- Glib::VariantBase::cast_dynamic>(value).get();
- auto item = Gio::MenuItem::create(value_string, "noactionyet");
-
- item->set_action_and_target("actions.set_profile",
- Glib::Variant::create(value_string));
- profiles_menu->append_item(item);
- }
- }
- }
-}
-
-WayfireBatteryInfo::~WayfireBatteryInfo()
-{
- btn_sig.disconnect();
- disp_dev_sig.disconnect();
-}
diff --git a/src/panel/widgets/battery.hpp b/src/panel/widgets/battery.hpp
index df85e4a88..cdd5d3888 100644
--- a/src/panel/widgets/battery.hpp
+++ b/src/panel/widgets/battery.hpp
@@ -1,70 +1,19 @@
#pragma once
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-
#include
#include "widget.hpp"
-#include "wf-popover.hpp"
-
-using DBusConnection = Glib::RefPtr;
-using DBusProxy = Glib::RefPtr;
-
-static const std::string BATTERY_STATUS_ICON = "icon"; // icon
-static const std::string BATTERY_STATUS_PERCENT = "percentage"; // icon + percentage
-static const std::string BATTERY_STATUS_FULL = "full"; // icon + percentage + TimeToFull/TimeToEmpty
-static const std::string BATTERY_STATUS_OVERLAY = "percentage_overlay";
+#include "widget-utils/battery.hpp"
-class wayfire_config;
class WayfireBatteryInfo : public WayfireWidget
{
- WfOption status_opt{"panel/battery_status"};
-
- sigc::connection btn_sig, disp_dev_sig;
-
- Gtk::Label label;
- std::unique_ptr box;
- Gtk::Overlay overlay;
- std::shared_ptr profiles_menu;
- Gtk::Image icon;
-
- DBusConnection connection;
- DBusProxy upower_proxy, powerprofile_proxy, display_device;
- std::string power_mode = "";
-
- bool feat_bat, feat_modes; // the available features when running
- bool setup_dbus_battery();
- bool setup_dbus_power_modes();
-
- void update_icon();
- void update_details();
- void update_state();
+ ShellBattery battery;
void update_layout();
void handle_config_reload();
- void on_properties_changed(
- const Gio::DBus::Proxy::MapChangedProperties& properties,
- const std::vector& invalidated);
-
- void on_upower_properties_changed(
- const Gio::DBus::Proxy::MapChangedProperties& properties,
- const std::vector& invalidated);
-
- void set_current_profile(Glib::ustring profile);
- void setup_profiles(std::vector> profiles);
-
- std::shared_ptr state_action;
-
public:
+ WayfireBatteryInfo() : battery("panel")
+ {}
virtual void init(Gtk::Box *container);
- virtual ~WayfireBatteryInfo();
};
diff --git a/src/panel/widgets/clock.cpp b/src/panel/widgets/clock.cpp
index 2ea16546f..c983b4b29 100644
--- a/src/panel/widgets/clock.cpp
+++ b/src/panel/widgets/clock.cpp
@@ -1,26 +1,21 @@
#include
#include "clock.hpp"
+
void WayfireClock::init(Gtk::Box *container)
{
button = std::make_unique("panel", "clock");
button->add_css_class("clock");
button->set_child(label);
button->open_on(1);
- label.set_justify(Gtk::Justification::CENTER);
label.show();
- update_label();
-
calendar.show();
button->set_popup_child(calendar);
btn_sig = button->signal_popup().connect(
sigc::mem_fun(*this, &WayfireClock::on_calendar_shown));
container->append(*button);
-
- timeout = Glib::signal_timeout().connect_seconds(
- sigc::mem_fun(*this, &WayfireClock::update_label), 1);
}
void WayfireClock::on_calendar_shown()
@@ -32,29 +27,7 @@ void WayfireClock::on_calendar_shown()
calendar.select_day(now);
}
-bool WayfireClock::update_label()
-{
- auto time = Glib::DateTime::create_now_local();
- auto text = time.format(format.value());
-
- /* Sometimes GLib::DateTime will add leading spaces. This results in
- * unevenly balanced padding around the text, which looks quite bad.
- *
- * This could be circumvented with the modifiers the user passes to the
- * format string, * but to remove the requirement that the user does
- * something fancy, we just remove any leading spaces. */
- size_t i = 0;
- while (i < text.length() && text[i] == ' ')
- {
- i++;
- }
-
- label.set_text(text.substr(i));
- return 1;
-}
-
WayfireClock::~WayfireClock()
{
btn_sig.disconnect();
- timeout.disconnect();
}
diff --git a/src/panel/widgets/clock.hpp b/src/panel/widgets/clock.hpp
index 858f842f8..70b8dc79b 100644
--- a/src/panel/widgets/clock.hpp
+++ b/src/panel/widgets/clock.hpp
@@ -1,23 +1,21 @@
#pragma once
+#include
+
#include "../widget.hpp"
#include "wf-popover.hpp"
-#include
-#include
+#include "widget-utils/clock.hpp"
class WayfireClock : public WayfireWidget
{
- Gtk::Label label;
+ ShellClock label{"panel/clock_format"};
Gtk::Calendar calendar;
std::unique_ptr button;
- sigc::connection timeout, btn_sig;
- WfOption format{"panel/clock_format"};
-
+ sigc::connection btn_sig;
void on_calendar_shown();
public:
void init(Gtk::Box *container) override;
- bool update_label();
~WayfireClock();
};
diff --git a/src/panel/widgets/network.cpp b/src/panel/widgets/network.cpp
index 68eb7126e..909e3a82f 100644
--- a/src/panel/widgets/network.cpp
+++ b/src/panel/widgets/network.cpp
@@ -2,7 +2,6 @@
#include "gtkmm/gesture.h"
#include "gtkmm/gestureclick.h"
#include "gtkmm/gesturelongpress.h"
-#include "network/network.hpp"
#include "wf-popover.hpp"
#include
#include
diff --git a/src/panel/widgets/network.hpp b/src/panel/widgets/network.hpp
index 70e6b3792..bb930fdc6 100644
--- a/src/panel/widgets/network.hpp
+++ b/src/panel/widgets/network.hpp
@@ -10,8 +10,8 @@
#include "wf-popover.hpp"
#include "../widget.hpp"
-#include "../../util/network/manager.hpp"
-#include "network/network-widget.hpp"
+#include "widget-utils/network/manager.hpp"
+#include "widget-utils/network/network-widget.hpp"
class WayfireNetworkInfo : public WayfireWidget
{
diff --git a/src/panel/widgets/power-profiles.cpp b/src/panel/widgets/power-profiles.cpp
new file mode 100644
index 000000000..843670e7b
--- /dev/null
+++ b/src/panel/widgets/power-profiles.cpp
@@ -0,0 +1,157 @@
+#include
+#include
+
+#include "power-profiles.hpp"
+
+#define POWER_PROFILE_PATH "/org/freedesktop/UPower/PowerProfiles"
+#define POWER_PROFILE_NAME "org.freedesktop.UPower.PowerProfiles"
+
+#define PROFILES "Profiles"
+#define ACTIVE_PROFILE "ActiveProfile"
+
+void WayfirePowerProfiles::update_icon()
+{
+ icon.set_from_icon_name("power-profile-" + power_mode);
+}
+
+void WayfirePowerProfiles::set_current_profile(Glib::ustring profile)
+{
+ power_mode = profile;
+ state_action->set_state(Glib::Variant::create(profile));
+ update_icon();
+}
+
+void WayfirePowerProfiles::setup_profiles(std::vector> profiles)
+{
+ profiles_menu->remove_all();
+ for (auto profile : profiles)
+ {
+ if (profile.count("Profile") == 1)
+ {
+ Glib::VariantBase value = profile.at("Profile");
+ if (value.is_of_type(Glib::VariantType("s")))
+ {
+ auto value_string =
+ Glib::VariantBase::cast_dynamic>(value).get();
+ auto item = Gio::MenuItem::create(value_string, "noactionyet");
+
+ item->set_action_and_target("actions.set_profile",
+ Glib::Variant::create(value_string));
+ profiles_menu->append_item(item);
+ }
+ }
+ }
+}
+
+void WayfirePowerProfiles::on_properties_changed(
+ const Gio::DBus::Proxy::MapChangedProperties& properties,
+ const std::vector& invalidated)
+{
+ for (auto& prop : properties)
+ {
+ if (prop.first == ACTIVE_PROFILE)
+ {
+ if (prop.second.is_of_type(Glib::VariantType("s")))
+ {
+ auto value_string =
+ Glib::VariantBase::cast_dynamic>(prop.second).get();
+ set_current_profile(value_string);
+ }
+ } else if (prop.first == PROFILES)
+ {
+ auto value = Glib::VariantBase::cast_dynamic>>>(prop.second);
+ setup_profiles(value.get());
+ }
+ }
+}
+
+bool WayfirePowerProfiles::setup_dbus_power_modes()
+{
+ auto cancellable = Gio::Cancellable::create();
+ connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable);
+ if (!connection)
+ {
+ std::cerr << "Failed to connect to dbus" << std::endl;
+ return false;
+ }
+
+ powerprofile_proxy = Gio::DBus::Proxy::create_sync(connection, POWER_PROFILE_NAME,
+ POWER_PROFILE_PATH,
+ POWER_PROFILE_NAME);
+ if (!powerprofile_proxy)
+ {
+ std::cout << "Unable to conect to Power Profiles. Continuing" << std::endl;
+ return false;
+ }
+
+ powerprofile_proxy->signal_properties_changed().connect(
+ sigc::mem_fun(*this, &WayfirePowerProfiles::on_properties_changed));
+
+ Glib::Variant current_profile;
+ Glib::Variant>> profiles;
+ powerprofile_proxy->get_cached_property(current_profile, ACTIVE_PROFILE);
+ powerprofile_proxy->get_cached_property(profiles, PROFILES);
+
+ if (profiles && current_profile)
+ {
+ setup_profiles(profiles.get());
+ set_current_profile(current_profile.get());
+ return true;
+ }
+
+ std::cout << "Unable to conect to Power Profiles. Continuing" << std::endl;
+ return false;
+}
+
+void WayfirePowerProfiles::init(Gtk::Box *container)
+{
+ profiles_menu = Gio::Menu::create();
+ state_action = Gio::SimpleAction::create_radio_string("set_profile", "");
+
+ if (!setup_dbus_power_modes())
+ {
+ return;
+ }
+
+ box = std::make_unique("panel", "power-profiles");
+
+ box->set_child(icon);
+ icon.add_css_class("widget-icon");
+
+ auto actions = Gio::SimpleActionGroup::create();
+
+ state_action->signal_activate().connect([=] (Glib::VariantBase vb)
+ {
+ if (vb.is_of_type(Glib::VariantType("s")))
+ {
+ Glib::VariantContainerBase params = Glib::Variant>::create({POWER_PROFILE_NAME, ACTIVE_PROFILE, vb});
+
+ connection->call_sync(
+ POWER_PROFILE_PATH,
+ "org.freedesktop.DBus.Properties",
+ "Set",
+ params,
+ NULL,
+ POWER_PROFILE_NAME,
+ -1,
+ Gio::DBus::CallFlags::NONE,
+ {});
+ }
+ });
+
+ actions->add_action(state_action);
+
+ box->open_on(1);
+ box->insert_action_group("actions", actions);
+ box->set_spacing(5);
+ box->set_menu_model(profiles_menu);
+
+ container->append(*box);
+
+ icon.property_scale_factor().signal_changed()
+ .connect(sigc::mem_fun(*this, &WayfirePowerProfiles::update_icon));
+
+ update_icon();
+}
diff --git a/src/panel/widgets/power-profiles.hpp b/src/panel/widgets/power-profiles.hpp
new file mode 100644
index 000000000..225a63181
--- /dev/null
+++ b/src/panel/widgets/power-profiles.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+
+#include "widget.hpp"
+#include "wf-popover.hpp"
+
+using DBusConnection = Glib::RefPtr;
+using DBusProxy = Glib::RefPtr;
+
+class WayfirePowerProfiles : public WayfireWidget
+{
+ Gtk::Image icon;
+ std::unique_ptr box;
+ std::shared_ptr profiles_menu;
+ std::shared_ptr state_action;
+
+ DBusConnection connection;
+ DBusProxy powerprofile_proxy;
+ std::string power_mode = "";
+
+ bool setup_dbus_power_modes();
+
+ void update_icon();
+ void set_current_profile(Glib::ustring profile);
+ void setup_profiles(std::vector> profiles);
+
+ void on_properties_changed(
+ const Gio::DBus::Proxy::MapChangedProperties& properties,
+ const std::vector& invalidated);
+
+ public:
+ virtual void init(Gtk::Box *container);
+ virtual ~WayfirePowerProfiles() = default;
+};
diff --git a/src/panel/widgets/volume.cpp b/src/panel/widgets/volume.cpp
index 98fa34ba2..77c34dc6d 100644
--- a/src/panel/widgets/volume.cpp
+++ b/src/panel/widgets/volume.cpp
@@ -2,121 +2,26 @@
#include
#include "volume.hpp"
-#include "icon-select.hpp"
#include "wf-popover.hpp"
-#define ICON(volume) icon_from_range(volume_icons, volume)
+VolCtrl::VolCtrl(WayfireVolume *widget, StreamRole role, std::string section) :
+ GvcControl(role, section),
+ widget(widget)
+{}
-void WayfireVolume::update_icon()
+void VolCtrl::on_volume(pa_volume_t newstate)
{
- if (gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream))
- {
- main_image.set_from_icon_name(ICON(0)); // mute
- return;
- }
-
- main_image.set_from_icon_name(ICON(volume_scale.get_target_value() / (double)max_norm));
-}
-
-void WayfireVolume::set_volume(pa_volume_t volume, set_volume_flags_t flags)
-{
- volume_scale.set_target_value(volume);
- if (gvc_stream && (flags & VOLUME_FLAG_UPDATE_GVC))
- {
- gvc_mixer_stream_set_volume(gvc_stream, volume);
- gvc_mixer_stream_push_volume(gvc_stream);
- }
-
- update_icon();
-}
-
-void WayfireVolume::on_volume_changed_external()
-{
- auto volume = gvc_mixer_stream_get_volume(gvc_stream);
- if (volume != (pa_volume_t)this->volume_scale.get_target_value())
- {
- set_volume(volume, VOLUME_FLAG_SHOW_POPOVER);
- }
-
- Glib::signal_idle().connect([=] ()
- {
- button->popup_timed(timeout * 1000);
- return G_SOURCE_REMOVE;
- });
-}
-
-static void notify_volume(GvcMixerControl *gvc_control,
- guint id, gpointer user_data)
-{
- WayfireVolume *wf_volume = (WayfireVolume*)user_data;
- wf_volume->on_volume_changed_external();
+ GvcControl::on_volume(newstate);
+ widget->button->popup_timed(timeout * 1000);
}
-static void notify_is_muted(GvcMixerControl *gvc_control,
- guint id, gpointer user_data)
-{
- WayfireVolume *wf_volume = (WayfireVolume*)user_data;
- wf_volume->update_icon();
-}
-
-void WayfireVolume::disconnect_gvc_stream_signals()
-{
- if (notify_volume_signal)
- {
- g_signal_handler_disconnect(gvc_stream, notify_volume_signal);
- }
-
- notify_volume_signal = 0;
-
- if (notify_is_muted_signal)
- {
- g_signal_handler_disconnect(gvc_stream, notify_is_muted_signal);
- }
-
- notify_is_muted_signal = 0;
-}
-
-void WayfireVolume::on_default_sink_changed()
-{
- gvc_stream = gvc_mixer_control_get_default_sink(gvc_control);
- if (!gvc_stream)
- {
- printf("GVC: Failed to get default sink\n");
- return;
- }
-
- /* Reconnect signals to new sink */
- disconnect_gvc_stream_signals();
- notify_volume_signal = g_signal_connect(gvc_stream, "notify::volume",
- G_CALLBACK(notify_volume), this);
- notify_is_muted_signal = g_signal_connect(gvc_stream, "notify::is-muted",
- G_CALLBACK(notify_is_muted), this);
-
- /* Update the scale attributes */
- max_norm = gvc_mixer_control_get_vol_max_norm(gvc_control);
- volume_scale.set_range(0.0, max_norm);
- volume_scale.set_increments(max_norm * scroll_sensitivity,
- max_norm * scroll_sensitivity * 2);
-
- /* Finally, update the displayed volume. However, do not show the popup */
- set_volume(gvc_mixer_stream_get_volume(gvc_stream), VOLUME_FLAG_NO_ACTION);
-}
-
-static void default_sink_changed(GvcMixerControl *gvc_control,
- guint id, gpointer user_data)
-{
- WayfireVolume *wf_volume = (WayfireVolume*)user_data;
- wf_volume->on_default_sink_changed();
-}
-
-void WayfireVolume::on_volume_value_changed()
-{
- /* User manually changed volume */
- set_volume(volume_scale.get_target_value());
-}
+WayfireVolume::WayfireVolume(StreamRole role) : control{this, role, "panel"}
+{}
void WayfireVolume::init(Gtk::Box *container)
{
+ control.managed_icon = &main_image;
+ control.update_icon();
main_image.add_css_class("widget-icon");
button = std::make_unique("panel", "volume");
button->set_keyboard_interactive(false);
@@ -124,62 +29,33 @@ void WayfireVolume::init(Gtk::Box *container)
auto middle_click_gesture = Gtk::GestureClick::create();
auto long_press = Gtk::GestureLongPress::create();
auto scroll_gesture = Gtk::EventControllerScroll::create();
- auto scroll_gesture2 = Gtk::EventControllerScroll::create();
scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
scroll_gesture->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
- scroll_gesture2->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
- scroll_gesture2->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
long_press->set_touch_only(true);
middle_click_gesture->set_button(2);
- /* Setup gvc control */
- gvc_control = gvc_mixer_control_new("Wayfire Volume Control");
- notify_default_sink_changed = g_signal_connect(gvc_control,
- "default-sink-changed", G_CALLBACK(default_sink_changed), this);
- gvc_mixer_control_open(gvc_control);
-
signals.push_back(scroll_gesture->signal_scroll().connect([=] (double dx, double dy)
{
int change = 0;
if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL)
{
// +- number of clicks.
- change = dy * max_norm * scroll_sensitivity;
+ change = dy * GvcCommon::get().get_max(control.get_role()) * scroll_sensitivity;
} else
{
// Number of pixels expected to have scrolled. usually in 100s
- change = (dy / 100.0) * max_norm * scroll_sensitivity;
+ change = (dy / 100.0) * GvcCommon::get().get_max(control.get_role()) * scroll_sensitivity;
}
- set_volume(std::clamp(volume_scale.get_target_value() - change,
- 0.0, max_norm));
+ GvcCommon::get().set_volume(control.get_role(), control.get_scale_target_value() - change);
return true;
}, true));
- signals.push_back(scroll_gesture2->signal_scroll().connect([=] (double dx, double dy)
- {
- int change = 0;
- if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL)
- {
- // +- number of clicks.
- change = dy * max_norm * scroll_sensitivity;
- } else
- {
- // Number of pixels expected to have scrolled. usually in 100s
- change = (dy / 100.0) * max_norm * scroll_sensitivity;
- }
-
- set_volume(std::clamp(volume_scale.get_target_value() - change,
- 0.0, max_norm));
- return true;
- }, false));
signals.push_back(long_press->signal_pressed().connect(
[=] (double x, double y)
{
- bool muted = !(gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream));
- gvc_mixer_stream_change_is_muted(gvc_stream, muted);
- gvc_mixer_stream_push_volume(gvc_stream);
+ GvcCommon::get().set_muted(control.get_role(), !(GvcCommon::get().get_muted(control.get_role())));
long_press->set_state(Gtk::EventSequenceState::CLAIMED);
middle_click_gesture->set_state(Gtk::EventSequenceState::DENIED);
}));
@@ -189,15 +65,10 @@ void WayfireVolume::init(Gtk::Box *container)
}));
signals.push_back(middle_click_gesture->signal_released().connect([=] (int count, double x, double y)
{
- bool muted = !(gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream));
- gvc_mixer_stream_change_is_muted(gvc_stream, muted);
- gvc_mixer_stream_push_volume(gvc_stream);
+ auto muted = GvcCommon::get().get_muted(control.get_role());
+ GvcCommon::get().set_muted(control.get_role(), !muted);
}));
- volume_scale.set_draw_value(false);
- volume_scale.set_size_request(300, 0);
- volume_scale.set_user_changed_callback([=] () { on_volume_value_changed(); });
- volume_scale.add_controller(scroll_gesture2);
button->add_controller(scroll_gesture);
button->add_controller(long_press);
button->add_controller(middle_click_gesture);
@@ -206,20 +77,13 @@ void WayfireVolume::init(Gtk::Box *container)
/* Setup layout */
container->append(*button);
button->set_child(main_image);
- button->set_popup_child(volume_scale);
- /* Override scroll bar as it isn't needed here */
- button->get_scroll().set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::NEVER);
+ button->set_popup_child(control);
}
WayfireVolume::~WayfireVolume()
{
- disconnect_gvc_stream_signals();
-
- if (notify_default_sink_changed)
+ for (auto signal : signals)
{
- g_signal_handler_disconnect(gvc_control, notify_default_sink_changed);
+ signal.disconnect();
}
-
- gvc_mixer_control_close(gvc_control);
- g_object_unref(gvc_control);
}
diff --git a/src/panel/widgets/volume.hpp b/src/panel/widgets/volume.hpp
index 536be8843..5facf2d40 100644
--- a/src/panel/widgets/volume.hpp
+++ b/src/panel/widgets/volume.hpp
@@ -1,66 +1,37 @@
#pragma once
-#include "../widget.hpp"
-#include "wf-popover.hpp"
#include
#include
-#include "../../util/animated-scale.hpp"
-#include
-#include
+
#include
+#include "../widget.hpp"
+#include "wf-popover.hpp"
+#include "widget-utils/pulseaudio/control.hpp"
+
+class WayfireVolume;
+
+class VolCtrl : public GvcControl
+{
+ public:
+ VolCtrl(WayfireVolume *widget, StreamRole role, std::string section);
+ void on_volume(pa_volume_t newstate) override;
+ WayfireVolume *widget;
+ WfOption timeout{"panel/volume_display_timeout"};
+};
+
class WayfireVolume : public WayfireWidget
{
Gtk::Image main_image;
- WayfireAnimatedScale volume_scale;
- std::unique_ptr button;
+ VolCtrl control;
- WfOption timeout{"panel/volume_display_timeout"};
WfOption scroll_sensitivity{"panel/volume_scroll_sensitivity"};
- void on_volume_value_changed();
-
- GvcMixerControl *gvc_control;
- GvcMixerStream *gvc_stream = NULL;
- gdouble max_norm; // maximal volume for current stream
-
- gulong notify_volume_signal = 0;
- gulong notify_is_muted_signal = 0;
- gulong notify_default_sink_changed = 0;
std::vector signals;
- void disconnect_gvc_stream_signals();
-
- enum set_volume_flags_t
- {
- /* Neither show popover nor update volume */
- VOLUME_FLAG_NO_ACTION = 0,
- /* Show volume popover */
- VOLUME_FLAG_SHOW_POPOVER = 1,
- /* Update real volume with GVC */
- VOLUME_FLAG_UPDATE_GVC = 2,
- /* Both of the above */
- VOLUME_FLAG_FULL = 3,
- };
-
- /**
- * Set the current volume level to volume_level. This updates both the popover scale and the real
- * pulseaudio volume, depending on the passed flags.
- *
- * Precondition: volume_level should be between 0 and max_norm
- */
- void set_volume(pa_volume_t volume_level,
- set_volume_flags_t flags = VOLUME_FLAG_FULL);
public:
+ WayfireVolume(StreamRole role);
void init(Gtk::Box *container) override;
+ std::unique_ptr button;
virtual ~WayfireVolume();
-
- /** Update the icon based on volume and muted state */
- void update_icon();
-
- /** Called when the volume changed from outside of the widget */
- void on_volume_changed_external();
-
- /** Called when the default sink changes */
- void on_default_sink_changed();
};
diff --git a/src/panel/widgets/weather.cpp b/src/panel/widgets/weather.cpp
index 369777114..1acdeed78 100644
--- a/src/panel/widgets/weather.cpp
+++ b/src/panel/widgets/weather.cpp
@@ -1,93 +1,9 @@
-#include
-#include
-#include
-#include
#include "weather.hpp"
-#include "wayfire/nonstd/json.hpp"
void WayfireWeather::init(Gtk::Box *container)
{
- label.set_justify(Gtk::Justification::CENTER);
- box.get_style_context()->add_class("weather");
- box.prepend(label);
- box.append(icon);
- container->append(box);
-
- weather_data_path = std::string(getenv("HOME")) + "/.local/share/owf/data/data.json";
-
- inotify_fd = inotify_init();
-
- inotify_add_watch(inotify_fd,
- weather_data_path.c_str(),
- IN_CLOSE_WRITE);
-
- inotify_connection = Glib::signal_io().connect(
- sigc::mem_fun(*this, &WayfireWeather::handle_inotify_event),
- inotify_fd, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP);
-
- update_weather();
+ weather.add_css_class("weather");
+ container->append(weather);
}
-bool WayfireWeather::handle_inotify_event(Glib::IOCondition cond)
-{
- if (cond == Glib::IOCondition::IO_HUP)
- {
- return false;
- }
-
- char buf[1024 * sizeof(inotify_event)];
- read(inotify_fd, buf, sizeof(buf));
-
- update_weather();
-
- return true;
-}
-
-void WayfireWeather::update_weather()
-{
- std::ifstream input_file(weather_data_path);
-
- if (!input_file)
- {
- std::cerr << "Error reading json file " << weather_data_path << std::endl;
- return;
- }
-
- std::stringstream buffer;
- buffer << input_file.rdbuf();
-
- wf::json_t json_data;
-
- auto err = wf::json_t::parse_string(buffer.str(), json_data);
-
- if (err.has_value())
- {
- std::cerr << "Error parsing json data " << weather_data_path << ": " << *err << std::endl;
- return;
- }
-
- if (!json_data.has_member("temp") || !json_data.has_member("icon"))
- {
- std::cerr << "Unexpected weather json data in " << weather_data_path << std::endl;
- return;
- }
-
- update_label(json_data["temp"]);
- update_icon(json_data["icon"]);
-}
-
-void WayfireWeather::update_label(std::string temperature)
-{
- label.set_text(temperature);
-}
-
-void WayfireWeather::update_icon(std::string path)
-{
- icon.set(path);
-}
-
-WayfireWeather::~WayfireWeather()
-{
- inotify_connection.disconnect();
- close(inotify_fd);
-}
+WayfireWeather::~WayfireWeather() = default;
diff --git a/src/panel/widgets/weather.hpp b/src/panel/widgets/weather.hpp
index f71267e07..7aa8170b4 100644
--- a/src/panel/widgets/weather.hpp
+++ b/src/panel/widgets/weather.hpp
@@ -1,25 +1,13 @@
#pragma once
#include "../widget.hpp"
-#include "wf-popover.hpp"
-#include
-#include
+#include "widget-utils/weather.hpp"
class WayfireWeather : public WayfireWidget
{
- Gtk::Label label;
- Gtk::Image icon;
- Gtk::Box box;
-
- int inotify_fd;
- sigc::connection inotify_connection;
- std::string weather_data_path;
+ ShellWeather weather{"panel"};
public:
void init(Gtk::Box *container) override;
- bool handle_inotify_event(Glib::IOCondition cond);
- void update_weather();
- void update_label(std::string);
- void update_icon(std::string);
~WayfireWeather();
};
diff --git a/src/util/meson.build b/src/util/meson.build
index d53e3680e..ad18fa493 100644
--- a/src/util/meson.build
+++ b/src/util/meson.build
@@ -1,35 +1,57 @@
+sources = [
+ 'gtk-utils.cpp',
+ 'icon-select.cpp',
+ 'wf-shell-app.cpp',
+ 'wf-autohide-window.cpp',
+ 'wf-popover.cpp',
+ 'css-config.cpp',
+ 'wf-ipc.cpp',
+ 'animated-scale.cpp',
+ 'widget-utils/battery.cpp',
+ 'widget-utils/clock.cpp',
+ 'widget-utils/network/manager.cpp',
+ 'widget-utils/network/wifi.cpp',
+ 'widget-utils/network/wired.cpp',
+ 'widget-utils/network/network-widget.cpp',
+ 'widget-utils/network/wifi-ap.cpp',
+ 'widget-utils/network/network.cpp',
+ 'widget-utils/network/modem.cpp',
+ 'widget-utils/network/settings.cpp',
+ 'widget-utils/network/connection.cpp',
+ 'background-gl.cpp',
+ 'icon-select.cpp'
+]
+
+deps = [
+ wf_protos,
+ gtklayershell,
+ wayland_client,
+ gtkmm,
+ wfconfig,
+ libinotify,
+ json,
+]
+
+if libpulse.found()
+ sources += [
+ 'widget-utils/pulseaudio/control.cpp',
+ 'widget-utils/pulseaudio/gvc-proxy.cpp',
+ ]
+ deps += [libpulse, libgvc]
+else
+ message('Pulseaudio not found, volume widget will not be available.')
+endif
+
+if get_option('weather') == true
+ sources += [
+ 'widget-utils/weather.cpp',
+ ]
+endif
+
util = static_library(
'util',
- [
- 'gtk-utils.cpp',
- 'icon-select.cpp',
- 'wf-shell-app.cpp',
- 'wf-autohide-window.cpp',
- 'wf-popover.cpp',
- 'css-config.cpp',
- 'wf-ipc.cpp',
- 'animated-scale.cpp',
- 'network/manager.cpp',
- 'network/wifi.cpp',
- 'network/wired.cpp',
- 'network/network-widget.cpp',
- 'network/wifi-ap.cpp',
- 'network/network.cpp',
- 'network/modem.cpp',
- 'network/settings.cpp',
- 'network/connection.cpp',
- 'background-gl.cpp',
- 'icon-select.cpp'
- ],
- dependencies: [
- wf_protos,
- gtklayershell,
- wayland_client,
- gtkmm,
- wfconfig,
- libinotify,
- json,
- ],
+ sources,
+ dependencies: deps
)
util_includes = include_directories('.')
diff --git a/src/util/widget-utils/battery.cpp b/src/util/widget-utils/battery.cpp
new file mode 100644
index 000000000..b44644773
--- /dev/null
+++ b/src/util/widget-utils/battery.cpp
@@ -0,0 +1,261 @@
+#include
+#include
+
+#include "battery.hpp"
+#define UPOWER_NAME "org.freedesktop.UPower"
+#define DISPLAY_DEVICE "/org/freedesktop/UPower/devices/DisplayDevice"
+
+#define ICON "IconName"
+#define TYPE "Type"
+#define STATE "State"
+#define PERCENTAGE "Percentage"
+#define TIMETOFULL "TimeToFull"
+#define TIMETOEMPTY "TimeToEmpty"
+#define SHOULD_DISPLAY "IsPresent"
+
+static std::string get_device_type_description(uint32_t type)
+{
+ if (type == 2)
+ {
+ return "Battery ";
+ }
+
+ if (type == 3)
+ {
+ return "UPS ";
+ }
+
+ return "";
+}
+
+ShellBattery::ShellBattery(const std::string& section) :
+ status_opt{section + "/battery_status"}
+{
+ // replace by a generic power icon if unavailable
+ if (!setup())
+ {
+ upower_avail = false;
+ } else
+ {
+ upower_avail = true;
+ status_opt.set_callback([=] () { update_details(); });
+ update_details();
+ }
+
+ append(overlay);
+ overlay.set_child(icon);
+ icon.add_css_class("widget-icon");
+ add_css_class("battery");
+
+ update_icon();
+
+ icon.property_scale_factor().signal_changed()
+ .connect(sigc::mem_fun(*this, &ShellBattery::update_icon));
+}
+
+void ShellBattery::on_properties_changed(
+ const Gio::DBus::Proxy::MapChangedProperties& properties,
+ const std::vector& invalidated)
+{
+ bool invalid_icon = false, invalid_details = false;
+ bool invalid_state = false;
+ for (auto& prop : properties)
+ {
+ if (prop.first == ICON)
+ {
+ invalid_icon = true;
+ }
+
+ if ((prop.first == TYPE) || (prop.first == STATE) || (prop.first == PERCENTAGE) ||
+ (prop.first == TIMETOFULL) || (prop.first == TIMETOEMPTY))
+ {
+ invalid_details = true;
+ }
+
+ if (prop.first == SHOULD_DISPLAY)
+ {
+ invalid_state = true;
+ }
+ }
+
+ if (invalid_icon)
+ {
+ update_icon();
+ }
+
+ if (invalid_details)
+ {
+ update_details();
+ }
+
+ if (invalid_state)
+ {
+ update_state();
+ }
+}
+
+void ShellBattery::update_icon()
+{
+ if (!upower_avail)
+ {
+ icon.set_from_icon_name("power-profile-");
+ return;
+ }
+
+ Glib::Variant icon_name;
+ display_device->get_cached_property(icon_name, ICON);
+ icon.set_from_icon_name(icon_name.get());
+}
+
+static std::string state_descriptions[] = {
+ "Unknown", // 0
+ "Charging", // 1
+ "Discharging", // 2
+ "Empty", // 3
+ "Fully charged", // 4
+ "Pending charge", // 5
+ "Pending discharge", // 6
+};
+
+static bool is_charging(uint32_t state)
+{
+ return (state == 1) || (state == 5);
+}
+
+static bool is_discharging(uint32_t state)
+{
+ return (state == 2) || (state == 6);
+}
+
+static std::string format_digit(int digit)
+{
+ return digit <= 9 ? ("0" + std::to_string(digit)) :
+ std::to_string(digit);
+}
+
+static std::string uint_to_time(int64_t time)
+{
+ int hrs = time / 3600;
+ int min = (time / 60) % 60;
+
+ return format_digit(hrs) + ":" + format_digit(min);
+}
+
+void ShellBattery::update_details()
+{
+ Glib::Variant type;
+ display_device->get_cached_property(type, TYPE);
+
+ Glib::Variant vstate;
+ display_device->get_cached_property(vstate, STATE);
+ uint32_t state = vstate.get();
+
+ Glib::Variant vpercentage;
+ display_device->get_cached_property(vpercentage, PERCENTAGE);
+ auto percentage_string = std::to_string((int)vpercentage.get()) + "%";
+
+ Glib::Variant time_to_full;
+ display_device->get_cached_property(time_to_full, TIMETOFULL);
+
+ Glib::Variant time_to_empty;
+ display_device->get_cached_property(time_to_empty, TIMETOEMPTY);
+
+ std::string description = percentage_string + ", " + state_descriptions[state];
+ if (is_charging(state))
+ {
+ description += ", " + uint_to_time(time_to_full.get()) + " until full";
+ } else if (is_discharging(state))
+ {
+ description += ", " + uint_to_time(time_to_empty.get()) + " remaining";
+ }
+
+ set_tooltip_text(
+ get_device_type_description(type.get()) + description);
+
+ if (status_opt.value() == BATTERY_STATUS_PERCENT)
+ {
+ label.set_text(percentage_string);
+ overlay.remove_overlay(label);
+ append(label);
+ } else if (status_opt.value() == BATTERY_STATUS_FULL)
+ {
+ label.set_text(description);
+ auto children = overlay.get_children();
+ if (std::count(children.begin(), children.end(), &label))
+ {
+ overlay.remove_overlay(label);
+ }
+
+ append(label);
+ } else if (status_opt.value() == BATTERY_STATUS_OVERLAY)
+ {
+ label.set_text(percentage_string);
+ auto children = get_children();
+ if (std::count(children.begin(), children.end(), &label))
+ {
+ remove(label);
+ }
+
+ overlay.add_overlay(label);
+ }
+
+ if (status_opt.value() == BATTERY_STATUS_ICON)
+ {
+ label.hide();
+ } else
+ {
+ label.show();
+ }
+}
+
+void ShellBattery::update_state()
+{
+ std::cout << "unimplemented reached, in battery.cpp: "
+ "\n\tWayfireBatteryInfo::update_state()" << std::endl;
+}
+
+bool ShellBattery::setup()
+{
+ auto cancellable = Gio::Cancellable::create();
+ connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable);
+ if (!connection)
+ {
+ std::cerr << "Failed to connect to dbus" << std::endl;
+ return false;
+ }
+
+ upower_proxy = Gio::DBus::Proxy::create_sync(connection, UPOWER_NAME,
+ "/org/freedesktop/UPower",
+ "org.freedesktop.UPower");
+ if (!upower_proxy)
+ {
+ std::cerr << "Failed to connect to UPower" << std::endl;
+ return false;
+ }
+
+ display_device = Gio::DBus::Proxy::create_sync(connection,
+ UPOWER_NAME,
+ DISPLAY_DEVICE,
+ "org.freedesktop.UPower.Device");
+ if (!display_device)
+ {
+ return false;
+ }
+
+ Glib::Variant present;
+ display_device->get_cached_property(present, SHOULD_DISPLAY);
+ if (present.get())
+ {
+ disp_dev_sig = display_device->signal_properties_changed().connect(
+ sigc::mem_fun(*this, &ShellBattery::on_properties_changed));
+
+ return true;
+ }
+
+ return false;
+}
+
+ShellBattery::~ShellBattery()
+{
+ disp_dev_sig.disconnect();
+}
diff --git a/src/util/widget-utils/battery.hpp b/src/util/widget-utils/battery.hpp
new file mode 100644
index 000000000..f1a227441
--- /dev/null
+++ b/src/util/widget-utils/battery.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+#include
+
+using DBusConnection = Glib::RefPtr;
+using DBusProxy = Glib::RefPtr;
+
+static const std::string BATTERY_STATUS_ICON = "icon"; // icon
+static const std::string BATTERY_STATUS_PERCENT = "percentage"; // icon + percentage
+static const std::string BATTERY_STATUS_FULL = "full"; // icon + percentage + TimeToFull/TimeToEmpty
+static const std::string BATTERY_STATUS_OVERLAY = "percentage_overlay";
+
+class ShellBattery : public Gtk::Box
+{
+ WfOption status_opt;
+
+ sigc::connection disp_dev_sig;
+
+ Gtk::Label label;
+ Gtk::Overlay overlay;
+ Gtk::Image icon;
+
+ DBusConnection connection;
+ DBusProxy upower_proxy, display_device;
+
+ bool upower_avail;
+ bool setup();
+
+ void update_icon();
+ void update_details();
+ void update_state();
+
+ void on_properties_changed(
+ const Gio::DBus::Proxy::MapChangedProperties& properties,
+ const std::vector& invalidated);
+
+ public:
+ ShellBattery(const std::string& section);
+
+ bool get_upower_avail();
+
+ ~ShellBattery();
+};
diff --git a/src/util/widget-utils/clock.cpp b/src/util/widget-utils/clock.cpp
new file mode 100644
index 000000000..28511c329
--- /dev/null
+++ b/src/util/widget-utils/clock.cpp
@@ -0,0 +1,40 @@
+#include "clock.hpp"
+
+ShellClock::ShellClock(const std::string& section) :
+ format_opt(section)
+{
+ set_justify(Gtk::Justification::CENTER);
+ update_time();
+
+ timeout = Glib::signal_timeout().connect_seconds(
+ [this] ()
+ {
+ update_time();
+ return G_SOURCE_CONTINUE;
+ }, 1);
+}
+
+ShellClock::~ShellClock()
+{
+ timeout.disconnect();
+}
+
+void ShellClock::update_time()
+{
+ auto time = Glib::DateTime::create_now_local();
+ auto text = time.format(format_opt.value());
+
+ /* Sometimes GLib::DateTime will add leading spaces. This results in
+ * unevenly balanced padding around the text, which looks quite bad.
+ *
+ * This could be circumvented with the modifiers the user passes to the
+ * format string, but to remove the requirement that the user does
+ * something fancy, we just remove any leading spaces. */
+ size_t i = 0;
+ while (i < text.length() && text[i] == ' ')
+ {
+ i++;
+ }
+
+ set_text(text.substr(i));
+}
diff --git a/src/util/widget-utils/clock.hpp b/src/util/widget-utils/clock.hpp
new file mode 100644
index 000000000..61176e547
--- /dev/null
+++ b/src/util/widget-utils/clock.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "wf-option-wrap.hpp"
+
+class ShellClock : public Gtk::Label
+{
+ public:
+ ShellClock(const std::string& section);
+ ~ShellClock();
+
+ void update_time();
+
+ private:
+ WfOption format_opt;
+ sigc::connection timeout;
+};
diff --git a/src/util/network/bluetooth.hpp b/src/util/widget-utils/network/bluetooth.hpp
similarity index 100%
rename from src/util/network/bluetooth.hpp
rename to src/util/widget-utils/network/bluetooth.hpp
diff --git a/src/util/network/connection.cpp b/src/util/widget-utils/network/connection.cpp
similarity index 98%
rename from src/util/network/connection.cpp
rename to src/util/widget-utils/network/connection.cpp
index 81bb9d3d8..1f7fd4e60 100644
--- a/src/util/network/connection.cpp
+++ b/src/util/widget-utils/network/connection.cpp
@@ -1,7 +1,7 @@
#include
#include
#include "connection.hpp"
-#include "network/manager.hpp"
+#include "widget-utils/network/manager.hpp"
Connection::Connection() :
Network("/", nullptr), connection_proxy(nullptr), devices({})
diff --git a/src/util/network/connection.hpp b/src/util/widget-utils/network/connection.hpp
similarity index 100%
rename from src/util/network/connection.hpp
rename to src/util/widget-utils/network/connection.hpp
diff --git a/src/util/network/manager.cpp b/src/util/widget-utils/network/manager.cpp
similarity index 99%
rename from src/util/network/manager.cpp
rename to src/util/widget-utils/network/manager.cpp
index 2be92a40c..6c5784014 100644
--- a/src/util/network/manager.cpp
+++ b/src/util/widget-utils/network/manager.cpp
@@ -10,7 +10,7 @@
#include "connection.hpp"
#include "gtkmm/enums.h"
#include "network.hpp"
-#include "network/settings.hpp"
+#include "widget-utils/network/settings.hpp"
#include "sigc++/functors/mem_fun.h"
#include "vpn.hpp"
#include "wifi.hpp"
diff --git a/src/util/network/manager.hpp b/src/util/widget-utils/network/manager.hpp
similarity index 100%
rename from src/util/network/manager.hpp
rename to src/util/widget-utils/network/manager.hpp
diff --git a/src/util/network/modem.cpp b/src/util/widget-utils/network/modem.cpp
similarity index 100%
rename from src/util/network/modem.cpp
rename to src/util/widget-utils/network/modem.cpp
diff --git a/src/util/network/modem.hpp b/src/util/widget-utils/network/modem.hpp
similarity index 100%
rename from src/util/network/modem.hpp
rename to src/util/widget-utils/network/modem.hpp
diff --git a/src/util/network/network-widget.cpp b/src/util/widget-utils/network/network-widget.cpp
similarity index 100%
rename from src/util/network/network-widget.cpp
rename to src/util/widget-utils/network/network-widget.cpp
diff --git a/src/util/network/network-widget.hpp b/src/util/widget-utils/network/network-widget.hpp
similarity index 100%
rename from src/util/network/network-widget.hpp
rename to src/util/widget-utils/network/network-widget.hpp
diff --git a/src/util/network/network.cpp b/src/util/widget-utils/network/network.cpp
similarity index 100%
rename from src/util/network/network.cpp
rename to src/util/widget-utils/network/network.cpp
diff --git a/src/util/network/network.hpp b/src/util/widget-utils/network/network.hpp
similarity index 100%
rename from src/util/network/network.hpp
rename to src/util/widget-utils/network/network.hpp
diff --git a/src/util/network/null.hpp b/src/util/widget-utils/network/null.hpp
similarity index 100%
rename from src/util/network/null.hpp
rename to src/util/widget-utils/network/null.hpp
diff --git a/src/util/network/settings.cpp b/src/util/widget-utils/network/settings.cpp
similarity index 100%
rename from src/util/network/settings.cpp
rename to src/util/widget-utils/network/settings.cpp
diff --git a/src/util/network/settings.hpp b/src/util/widget-utils/network/settings.hpp
similarity index 100%
rename from src/util/network/settings.hpp
rename to src/util/widget-utils/network/settings.hpp
diff --git a/src/util/network/vpn.hpp b/src/util/widget-utils/network/vpn.hpp
similarity index 100%
rename from src/util/network/vpn.hpp
rename to src/util/widget-utils/network/vpn.hpp
diff --git a/src/util/network/wifi-ap.cpp b/src/util/widget-utils/network/wifi-ap.cpp
similarity index 100%
rename from src/util/network/wifi-ap.cpp
rename to src/util/widget-utils/network/wifi-ap.cpp
diff --git a/src/util/network/wifi-ap.hpp b/src/util/widget-utils/network/wifi-ap.hpp
similarity index 92%
rename from src/util/network/wifi-ap.hpp
rename to src/util/widget-utils/network/wifi-ap.hpp
index e53ffe780..da0396324 100644
--- a/src/util/network/wifi-ap.hpp
+++ b/src/util/widget-utils/network/wifi-ap.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "network/network.hpp"
-#include "network/settings.hpp"
+#include "widget-utils/network/network.hpp"
+#include "widget-utils/network/settings.hpp"
#include "sigc++/connection.h"
#include
#include
diff --git a/src/util/network/wifi.cpp b/src/util/widget-utils/network/wifi.cpp
similarity index 100%
rename from src/util/network/wifi.cpp
rename to src/util/widget-utils/network/wifi.cpp
diff --git a/src/util/network/wifi.hpp b/src/util/widget-utils/network/wifi.hpp
similarity index 100%
rename from src/util/network/wifi.hpp
rename to src/util/widget-utils/network/wifi.hpp
diff --git a/src/util/network/wired.cpp b/src/util/widget-utils/network/wired.cpp
similarity index 100%
rename from src/util/network/wired.cpp
rename to src/util/widget-utils/network/wired.cpp
diff --git a/src/util/network/wired.hpp b/src/util/widget-utils/network/wired.hpp
similarity index 100%
rename from src/util/network/wired.hpp
rename to src/util/widget-utils/network/wired.hpp
diff --git a/src/util/widget-utils/pulseaudio/control.cpp b/src/util/widget-utils/pulseaudio/control.cpp
new file mode 100644
index 000000000..e7b56fc14
--- /dev/null
+++ b/src/util/widget-utils/pulseaudio/control.cpp
@@ -0,0 +1,131 @@
+#include "control.hpp"
+
+#include "icon-select.hpp"
+
+#define ICON(volume) icon_from_range(icon_map, volume)
+
+GvcControl::GvcControl(StreamRole role, std::string section) :
+ role(role),
+ scroll_sensitivity{section + "/volume_scroll_sensitivity"}
+{
+ if (role == StreamRole::Sink)
+ {
+ icon_map = volume_icons;
+ } else
+ {
+ icon_map = mic_volume_icons;
+ }
+
+ add_css_class("volume-control");
+ mute_button.add_css_class("mute-toggle");
+ mute_button.add_css_class("flat");
+ icon.add_css_class("default-icon");
+ mute_button.set_child(icon);
+ scale.set_draw_value(false);
+ scale.set_size_request(300, 0);
+ scale.set_range(0, GvcCommon::get().get_max(role));
+
+ auto scroll_gesture = Gtk::EventControllerScroll::create();
+ scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
+ scroll_gesture->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
+ signals.push_back(scroll_gesture->signal_scroll().connect([=] (double dx, double dy)
+ {
+ int change = 0;
+ if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL)
+ {
+ // +- number of clicks.
+ change = dy * GvcCommon::get().get_max(role) * scroll_sensitivity;
+ } else
+ {
+ // Number of pixels expected to have scrolled. usually in 100s
+ change = (dy / 100.0) * GvcCommon::get().get_max(role) * scroll_sensitivity;
+ }
+
+ GvcCommon::get().set_volume(role, scale.get_target_value() - change);
+ return true;
+ }, false));
+
+ scale.add_controller(scroll_gesture);
+
+ append(scale);
+ append(mute_button);
+
+ scale.set_range(0.0, GvcCommon::get().get_max(role));
+
+ mute_conn = mute_button.signal_toggled().connect(
+ [this, role] ()
+ {
+ GvcCommon::get().set_muted(role, mute_button.get_active());
+ });
+
+ scale.set_user_changed_callback(
+ [this, role] ()
+ {
+ GvcCommon::get().set_volume(role, scale.get_target_value());
+ });
+
+ GvcCommon::get().reg_ctrl(this, role);
+}
+
+void GvcControl::on_mute(bool newstate)
+{
+ mute_conn.block(true);
+ mute_button.set_active(newstate);
+ mute_conn.block(false);
+ update_icon();
+}
+
+void GvcControl::on_volume(pa_volume_t newstate)
+{
+ scale.set_target_value(newstate);
+ update_icon();
+}
+
+double GvcControl::get_scale_target_value()
+{
+ return scale.get_target_value();
+}
+
+void GvcControl::update_icon()
+{
+ if (GvcCommon::get().get_muted(role))
+ {
+ add_css_class("muted");
+ icon.set_from_icon_name(ICON(0)); // mute
+ if (managed_icon)
+ {
+ managed_icon->set_from_icon_name(ICON(0));
+ }
+
+ return;
+ }
+
+ remove_css_class("muted");
+ icon.set_from_icon_name(ICON(scale.get_target_value() / (double)GvcCommon::get().get_max(role)));
+ if (managed_icon)
+ {
+ managed_icon->set_from_icon_name(ICON(scale.get_target_value() /
+ (double)GvcCommon::get().get_max(role)));
+ }
+}
+
+void GvcControl::set_orientation(Gtk::Orientation orientation)
+{
+ Box::set_orientation(orientation);
+ scale.set_orientation(orientation);
+}
+
+StreamRole GvcControl::get_role()
+{
+ return role;
+}
+
+GvcControl::~GvcControl()
+{
+ GvcCommon::get().unreg_ctrl(this, role);
+ mute_conn.disconnect();
+ for (auto signal : signals)
+ {
+ signal.disconnect();
+ }
+}
diff --git a/src/util/widget-utils/pulseaudio/control.hpp b/src/util/widget-utils/pulseaudio/control.hpp
new file mode 100644
index 000000000..8330a96af
--- /dev/null
+++ b/src/util/widget-utils/pulseaudio/control.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "animated-scale.hpp"
+#include "gvc-proxy.hpp"
+
+enum class StreamRole;
+
+class GvcControl : public Gtk::Box
+{
+ public:
+ GvcControl(StreamRole role, std::string section);
+ virtual void on_mute(bool newstate);
+ virtual void on_volume(pa_volume_t newstate);
+ void set_btn_status_no_callbk(bool state);
+ void set_scale_target_value(double volume);
+ double get_scale_target_value();
+ void update_icon();
+ void set_orientation(Gtk::Orientation orientation);
+ Gtk::Image *managed_icon = nullptr; // icon given by something else that we manage
+ StreamRole get_role();
+ ~GvcControl();
+
+ private:
+ StreamRole role;
+ std::map> icon_map;
+ WayfireAnimatedScale scale;
+ Gtk::ToggleButton mute_button;
+ Gtk::Image icon;
+ sigc::connection mute_conn;
+ std::vector signals;
+ WfOption scroll_sensitivity;
+};
diff --git a/src/util/widget-utils/pulseaudio/gvc-proxy.cpp b/src/util/widget-utils/pulseaudio/gvc-proxy.cpp
new file mode 100644
index 000000000..dd8026fc6
--- /dev/null
+++ b/src/util/widget-utils/pulseaudio/gvc-proxy.cpp
@@ -0,0 +1,199 @@
+#include
+#include
+
+#include "gvc-proxy.hpp"
+
+GvcCommon::GvcCommon()
+{
+ gvc_control = gvc_mixer_control_new("Wf-shell volume control");
+ gvc_sink_stream = NULL;
+ gvc_source_stream = NULL;
+
+ notify_default_sink_changed = g_signal_connect(
+ gvc_control, "default-sink-changed", G_CALLBACK(default_stream_changed),
+ GINT_TO_POINTER(static_cast(StreamRole::Sink)));
+
+ notify_default_source_changed = g_signal_connect(
+ gvc_control, "default-source-changed", G_CALLBACK(default_stream_changed),
+ GINT_TO_POINTER(static_cast(StreamRole::Source)));
+
+ gvc_mixer_control_open(gvc_control);
+
+ if (auto *sink = gvc_mixer_control_get_default_sink(gvc_control))
+ {
+ default_stream_changed(gvc_control, gvc_mixer_stream_get_id(sink),
+ GINT_TO_POINTER(static_cast(StreamRole::Sink)));
+ }
+
+ if (auto *source = gvc_mixer_control_get_default_source(gvc_control))
+ {
+ default_stream_changed(gvc_control, gvc_mixer_stream_get_id(source),
+ GINT_TO_POINTER(static_cast(StreamRole::Source)));
+ }
+}
+
+void GvcCommon::default_stream_muted(GvcMixerControl *gvc_control, guint id, gpointer data)
+{
+ StreamRole role = static_cast(GPOINTER_TO_INT(data));
+ bool muted = instance->get_muted(role);
+ for (auto control : *instance->role_to_controls[role])
+ {
+ control->on_mute(muted);
+ }
+}
+
+void GvcCommon::default_stream_volume(GvcMixerControl *gvc_control, guint id, gpointer data)
+{
+ StreamRole role = static_cast(GPOINTER_TO_INT(data));
+ pa_volume_t volume = instance->get_volume(role);
+ for (auto control : *instance->role_to_controls[role])
+ {
+ control->on_volume(volume);
+ }
+}
+
+void GvcCommon::disconnect_stream_signals(StreamRole role)
+{
+ if ((role == StreamRole::Sink) && gvc_sink_stream)
+ {
+ if (notify_sink_muted_signal)
+ {
+ g_signal_handler_disconnect(G_OBJECT(gvc_sink_stream),
+ notify_sink_muted_signal);
+ notify_sink_muted_signal = 0;
+ }
+
+ if (notify_sink_volume_signal)
+ {
+ g_signal_handler_disconnect(G_OBJECT(gvc_sink_stream),
+ notify_sink_volume_signal);
+ notify_sink_volume_signal = 0;
+ }
+
+ g_object_unref(gvc_sink_stream);
+ gvc_sink_stream = nullptr;
+ }
+
+ if ((role == StreamRole::Source) && gvc_source_stream)
+ {
+ if (notify_source_muted_signal)
+ {
+ g_signal_handler_disconnect(G_OBJECT(gvc_source_stream),
+ notify_source_muted_signal);
+ notify_source_muted_signal = 0;
+ }
+
+ if (notify_source_volume_signal)
+ {
+ g_signal_handler_disconnect(G_OBJECT(gvc_source_stream),
+ notify_source_volume_signal);
+ notify_source_volume_signal = 0;
+ }
+
+ g_object_unref(gvc_source_stream);
+ gvc_source_stream = nullptr;
+ }
+}
+
+void GvcCommon::default_stream_changed(GvcMixerControl *control, guint id, gpointer data)
+{
+ StreamRole role = static_cast(GPOINTER_TO_INT(data));
+
+ GvcMixerStream *stream = GVC_MIXER_STREAM(gvc_mixer_control_lookup_stream_id(control, id));
+ if (!stream)
+ {
+ return;
+ }
+
+ g_object_ref(stream);
+
+ instance->disconnect_stream_signals(role);
+
+ if (role == StreamRole::Sink)
+ {
+ instance->gvc_sink_stream = stream;
+ instance->notify_sink_muted_signal =
+ g_signal_connect(stream, "notify::is-muted", G_CALLBACK(default_stream_muted),
+ GINT_TO_POINTER(static_cast(StreamRole::Sink)));
+ instance->notify_sink_volume_signal =
+ g_signal_connect(stream, "notify::volume", G_CALLBACK(default_stream_volume),
+ GINT_TO_POINTER(static_cast(StreamRole::Sink)));
+ } else
+ {
+ instance->gvc_source_stream = stream;
+ instance->notify_source_muted_signal =
+ g_signal_connect(stream, "notify::is-muted", G_CALLBACK(default_stream_muted),
+ GINT_TO_POINTER(static_cast(StreamRole::Source)));
+ instance->notify_source_volume_signal =
+ g_signal_connect(stream, "notify::volume", G_CALLBACK(default_stream_volume),
+ GINT_TO_POINTER(static_cast(StreamRole::Source)));
+ }
+
+ instance->role_to_stream[role] = stream;
+}
+
+void GvcCommon::reg_ctrl(GvcControl *ctrl, StreamRole role)
+{
+ role_to_controls[role]->push_back(ctrl);
+}
+
+void GvcCommon::unreg_ctrl(GvcControl *ctrl, StreamRole role)
+{
+ auto & vec = *role_to_controls[role];
+ auto it = std::find(vec.begin(), vec.end(), ctrl);
+ if (it != vec.end())
+ {
+ vec.erase(it);
+ }
+}
+
+bool GvcCommon::get_muted(StreamRole role)
+{
+ GvcMixerStream *stream = role_to_stream[role];
+ return stream ? gvc_mixer_stream_get_is_muted(stream) : true;
+}
+
+pa_volume_t GvcCommon::get_volume(StreamRole role)
+{
+ GvcMixerStream *stream = role_to_stream[role];
+ return stream ? gvc_mixer_stream_get_volume(stream) : 0;
+}
+
+pa_volume_t GvcCommon::get_max(StreamRole role)
+{
+ return gvc_mixer_control_get_vol_max_norm(gvc_control);
+}
+
+void GvcCommon::set_muted(StreamRole role, bool muted)
+{
+ GvcMixerStream *stream = role_to_stream[role];
+ if (!stream)
+ {
+ return;
+ }
+
+ gvc_mixer_stream_change_is_muted(stream, muted);
+ gvc_mixer_stream_push_volume(stream);
+}
+
+void GvcCommon::set_volume(StreamRole role, pa_volume_t volume)
+{
+ GvcMixerStream *stream = role_to_stream[role];
+ if (!stream)
+ {
+ return;
+ }
+
+ gvc_mixer_stream_set_volume(stream, volume);
+ gvc_mixer_stream_push_volume(stream);
+}
+
+GvcCommon& GvcCommon::get()
+{
+ if (!instance)
+ {
+ instance = std::unique_ptr(new GvcCommon());
+ }
+
+ return *instance;
+}
diff --git a/src/util/widget-utils/pulseaudio/gvc-proxy.hpp b/src/util/widget-utils/pulseaudio/gvc-proxy.hpp
new file mode 100644
index 000000000..42ef923eb
--- /dev/null
+++ b/src/util/widget-utils/pulseaudio/gvc-proxy.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "control.hpp"
+
+enum class StreamRole
+{
+ Sink,
+ Source,
+};
+
+class GvcControl;
+
+class GvcCommon
+{
+ private:
+ GvcCommon();
+
+ static inline std::unique_ptr instance;
+
+ GvcMixerControl *gvc_control = NULL;
+ GvcMixerStream *gvc_sink_stream = NULL;
+ GvcMixerStream *gvc_source_stream = NULL;
+ std::map role_to_stream;
+
+ gulong notify_default_sink_changed = 0;
+ gulong notify_default_source_changed = 0;
+ gulong notify_sink_muted_signal = 0;
+ gulong notify_source_muted_signal = 0;
+ gulong notify_sink_volume_signal = 0;
+ gulong notify_source_volume_signal = 0;
+
+ static void default_stream_changed(GvcMixerControl *vc_control, guint id, gpointer data);
+ static void default_stream_muted(GvcMixerControl *gvc_control, guint id, gpointer data);
+ static void default_stream_volume(GvcMixerControl *gvc_control, guint id, gpointer data);
+
+ std::vector sink_controls, source_controls;
+ std::map*> role_to_controls =
+ {
+ {StreamRole::Sink, &sink_controls},
+ {StreamRole::Source, &source_controls}
+ };
+
+ void disconnect_stream_signals(StreamRole role);
+ void on_default_stream_changed(StreamRole role);
+
+ public:
+ void reg_ctrl(GvcControl *ctrl, StreamRole role);
+ void unreg_ctrl(GvcControl *ctrl, StreamRole role);
+
+ bool get_muted(StreamRole role);
+ pa_volume_t get_volume(StreamRole role);
+ pa_volume_t get_max(StreamRole role);
+
+ void set_muted(StreamRole role, bool muted);
+ void set_volume(StreamRole role, pa_volume_t volume);
+
+ static GvcCommon& get();
+};
diff --git a/src/util/widget-utils/weather.cpp b/src/util/widget-utils/weather.cpp
new file mode 100644
index 000000000..6ca47a0d4
--- /dev/null
+++ b/src/util/widget-utils/weather.cpp
@@ -0,0 +1,113 @@
+#include
+#include
+#include