diff --git a/metadata/locker.xml b/metadata/locker.xml index 86e984545..a5d13dc54 100644 --- a/metadata/locker.xml +++ b/metadata/locker.xml @@ -706,6 +706,10 @@ <_short>Always show false + + <_short>Volume Scroll Sensitivity + 0.05 + <_short>Button size 2.0 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..591abda0b 100644 --- a/src/locker/plugin/volume.cpp +++ b/src/locker/plugin/volume.cpp @@ -1,190 +1,17 @@ -#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(box); + box.append(volume); + // volume.set_orientation(Gtk::Orientation::VERTICAL); + box.append(mic); + box.set_orientation(Gtk::Orientation::VERTICAL); + // 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..ce4c76c77 100644 --- a/src/locker/plugin/volume.hpp +++ b/src/locker/plugin/volume.hpp @@ -1,47 +1,37 @@ #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 { - public: + GvcControl volume, mic; Gtk::Box box; - Gtk::Button sink_button; - Gtk::Button source_button; - WayfireLockerVolumePluginWidget(); + + public: + LockerVolumes(const std::string& section); +}; + +class WayfireLockerVolumePluginWidget : public WayfireLockerTimedWidget +{ + public: + 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..2a94cef33 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"}; 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..ae175e106 --- /dev/null +++ b/src/util/widget-utils/battery.cpp @@ -0,0 +1,262 @@ +#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"} +{ + append(overlay); + + // 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(); + } + + 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..72248a58f --- /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 + "/clock_format") +{ + 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_markup(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..b41b962df --- /dev/null +++ b/src/util/widget-utils/pulseaudio/control.cpp @@ -0,0 +1,130 @@ +#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"); + icon.set_from_icon_name(ICON(0)); + 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"); + if (managed_icon) + { + managed_icon->set_from_icon_name(ICON(0)); + } + + return; + } + + remove_css_class("muted"); + 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..84ed5c657 --- /dev/null +++ b/src/util/widget-utils/pulseaudio/gvc-proxy.cpp @@ -0,0 +1,201 @@ +#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); + + instance->role_to_stream[role] = stream; + + 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), data); + instance->notify_sink_volume_signal = + g_signal_connect(stream, "notify::volume", G_CALLBACK(default_stream_volume), data); + + default_stream_muted(instance->gvc_control, 0, data); + default_stream_volume(instance->gvc_control, 0, data); + } else + { + instance->gvc_source_stream = stream; + instance->notify_source_muted_signal = + g_signal_connect(stream, "notify::is-muted", G_CALLBACK(default_stream_muted), data); + instance->notify_source_volume_signal = + g_signal_connect(stream, "notify::volume", G_CALLBACK(default_stream_volume), data); + + default_stream_muted(instance->gvc_control, 0, data); + default_stream_volume(instance->gvc_control, 0, data); + } +} + +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 +#include + +#include + +#include "wayfire/nonstd/json.hpp" +#include "weather.hpp" + + +ShellWeather::ShellWeather(const std::string& section) +{ + set_orientation(Gtk::Orientation::HORIZONTAL); + + label.set_justify(Gtk::Justification::CENTER); + prepend(label); + append(icon); + + weather_data_path = std::string(getenv("HOME")) + "/.local/share/owf/data/data.json"; + + inotify_fd = inotify_init(); + if (inotify_fd < 0) + { + std::cerr << "Error creating inotify instance" << std::endl; + notify_visibility(false); + return; + } + + if (inotify_add_watch(inotify_fd, weather_data_path.c_str(), IN_CLOSE_WRITE) < 0) + { + std::cerr << "Error watching json file " << weather_data_path << std::endl; + notify_visibility(false); + return; + } + + inotify_connection = Glib::signal_io().connect( + sigc::mem_fun(*this, &ShellWeather::handle_inotify_event), + inotify_fd, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP); + + update_weather(); +} + +ShellWeather::~ShellWeather() +{ + inotify_connection.disconnect(); + if (inotify_fd >= 0) + { + close(inotify_fd); + } +} + +void ShellWeather::set_visibility_callback(std::function callback) +{ + visibility_callback = std::move(callback); +} + +void ShellWeather::notify_visibility(bool visible) +{ + if (visibility_callback) + { + visibility_callback(visible); + } +} + +bool ShellWeather::handle_inotify_event(Glib::IOCondition cond) +{ + if (cond == Glib::IOCondition::IO_HUP) + { + notify_visibility(false); + return false; + } + + char buf[1024 * sizeof(inotify_event)]; + read(inotify_fd, buf, sizeof(buf)); + + update_weather(); + return true; +} + +void ShellWeather::update_weather() +{ + std::ifstream input_file(weather_data_path); + if (!input_file) + { + std::cerr << "Error reading json file " << weather_data_path << std::endl; + notify_visibility(false); + 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; + notify_visibility(false); + return; + } + + if (!json_data.has_member("temp") || !json_data.has_member("icon")) + { + std::cerr << "Unexpected weather json data in " << weather_data_path << std::endl; + notify_visibility(false); + return; + } + + label.set_text(json_data["temp"].as_string()); + icon.set(json_data["icon"].as_string()); + notify_visibility(true); +} diff --git a/src/util/widget-utils/weather.hpp b/src/util/widget-utils/weather.hpp new file mode 100644 index 000000000..904e252af --- /dev/null +++ b/src/util/widget-utils/weather.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include