diff --git a/metadata/panel.xml b/metadata/panel.xml index 92289222a..cc15aa240 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -228,6 +228,11 @@ <_name>Percentage over Icon + + <_short>Power modes switching + <_long>If on, the clicking the battery will present the same menu for switching power profiles as the power-profiles widget. + false + <_short>Battery Icon Size 0 @@ -242,6 +247,18 @@ default + + <_short>Power profiles + + <_short>Battery Icon Size + 0 + 0 + + + <_short>Battery Font + default + + <_short>Network 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/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 d569ea745..06390da5f 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', @@ -88,4 +89,4 @@ executable( ['panel.cpp', version_h] + 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 f85d09cab..237cfde9a 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,6 +232,11 @@ class WayfirePanel::impl return Widget(new WayfireBatteryInfo()); } + if (name == "power-profiles") + { + return Widget(new WayfirePowerProfiles()); + } + if (name == "volume") { #ifdef HAVE_PULSE @@ -632,6 +637,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"}, @@ -654,6 +660,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 dd230e9f4..fe11a1fb9 100644 --- a/src/panel/widgets/battery.cpp +++ b/src/panel/widgets/battery.cpp @@ -1,296 +1,17 @@ #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() { - if (!box) - { - return; - } - WfOption panel_position{"panel/position"}; 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); } } @@ -299,139 +20,21 @@ 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); + panel_menu = std::make_unique("panel", "battery"); - 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)); + panel_menu->set_child(battery); update_layout(); -} -void WayfireBatteryInfo::on_upower_properties_changed( - const Gio::DBus::Proxy::MapChangedProperties& properties, - const std::vector& invalidated) -{ - for (auto& prop : properties) + if (do_power_modes) { - 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 + panel_menu->open_on(1); + panel_menu->insert_action_group("actions", power_tools.actions); + panel_menu->set_spacing(5); + panel_menu->set_menu_model(power_tools.profiles_menu); } -} -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(); + container->append(*panel_menu); } diff --git a/src/panel/widgets/battery.hpp b/src/panel/widgets/battery.hpp index df85e4a88..d03f03835 100644 --- a/src/panel/widgets/battery.hpp +++ b/src/panel/widgets/battery.hpp @@ -1,70 +1,24 @@ #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; +#include "widget-utils/battery.hpp" +#include "widget-utils/power-profiles.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 const std::string BATTERY_STATUS_OVERLAY = "percentage_overlay"; - -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; + ShellBattery battery; + ShellPowerTools power_tools; + std::unique_ptr panel_menu; - 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(); + WfOption do_power_modes{"panel/battery_power_modes"}; 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..1c2416b89 --- /dev/null +++ b/src/panel/widgets/power-profiles.cpp @@ -0,0 +1,62 @@ +#include +#include + +#include "power-profiles.hpp" + +void WayfirePowerProfiles::update_icon() +{ + icon.set_from_icon_name("power-profile-" + power_tools.power_mode); +} + +void WayfirePowerProfiles::set_current_profile(Glib::ustring profile) +{ + power_tools.power_mode = profile; + power_tools.state_action->set_state(Glib::Variant::create(profile)); + update_icon(); +} + +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); + power_tools.setup_profiles(value.get()); + } + } +} + +void WayfirePowerProfiles::init(Gtk::Box *container) +{ + panel_menu = std::make_unique("panel", "power-profiles"); + + panel_menu->set_child(icon); + icon.add_css_class("widget-icon"); + + panel_menu->open_on(1); + panel_menu->insert_action_group("actions", power_tools.actions); + panel_menu->set_spacing(5); + panel_menu->set_menu_model(power_tools.profiles_menu); + + power_tools.powerprofile_proxy->signal_properties_changed().connect( + sigc::mem_fun(*this, &WayfirePowerProfiles::on_properties_changed)); + + container->append(*panel_menu); + + 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..a1ba1859f --- /dev/null +++ b/src/panel/widgets/power-profiles.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include +#include +#include + +#include "widget.hpp" +#include "wf-popover.hpp" +#include "widget-utils/power-profiles.hpp" + +class WayfirePowerProfiles : public WayfireWidget +{ + ShellPowerTools power_tools; + Gtk::Image icon; + std::unique_ptr panel_menu; + + void update_icon(); + void set_current_profile(Glib::ustring profile); + + 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/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 30611837a..a5834246f 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -1,26 +1,37 @@ +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/power-profiles.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' +] + +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' - ], + sources, dependencies: [ wf_protos, gtklayershell, @@ -33,17 +44,17 @@ util = static_library( ) git_command = [ - 'sh', - '-c', 'printf "@0@ version @1@-%s (%s) branch %s gtk @2@" "$(git rev-parse --short HEAD)" "$(git log -1 --format=%as)" "$(git rev-parse --abbrev-ref HEAD)"'.format(meson.project_name(), meson.project_version(), gtkmm.version()), + 'sh', + '-c', 'printf "@0@ version @1@-%s (%s) branch %s gtk @2@" "$(git rev-parse --short HEAD)" "$(git log -1 --format=%as)" "$(git rev-parse --abbrev-ref HEAD)"'.format(meson.project_name(), meson.project_version(), gtkmm.version()), ] fallback_string = '@0@ version @1@-unknown-commit (unknown-date) branch unknown-branch gtk @2@'.format(meson.project_name(), meson.project_version(), gtkmm.version()) version_h = vcs_tag( - input: 'version.h.in', - output: 'version.h', - command: git_command, - fallback: fallback_string, + input: 'version.h.in', + output: 'version.h', + command: git_command, + fallback: fallback_string, ) 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 b1361311c..4d52294fc 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/power-profiles.cpp b/src/util/widget-utils/power-profiles.cpp new file mode 100644 index 000000000..e8406507b --- /dev/null +++ b/src/util/widget-utils/power-profiles.cpp @@ -0,0 +1,131 @@ +#include + +#include "power-profiles.hpp" +#include "wf-popover.hpp" + +#define POWER_PROFILE_PATH "/org/freedesktop/UPower/PowerProfiles" +#define POWER_PROFILE_NAME "org.freedesktop.UPower.PowerProfiles" + +void ShellPowerTools::set_current_profile(Glib::ustring profile) +{ + power_mode = profile; + state_action->set_state(Glib::Variant::create(profile)); +} + +void ShellPowerTools::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 ShellPowerTools::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 ShellPowerTools::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, &ShellPowerTools::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; +} + +ShellPowerTools::ShellPowerTools() +{ + profiles_menu = Gio::Menu::create(); + state_action = Gio::SimpleAction::create_radio_string("set_profile", ""); + + if (!setup_dbus_power_modes()) + { + return; + } + + 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); +} diff --git a/src/util/widget-utils/power-profiles.hpp b/src/util/widget-utils/power-profiles.hpp new file mode 100644 index 000000000..9f97e60dc --- /dev/null +++ b/src/util/widget-utils/power-profiles.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include +#include + +#include "wf-popover.hpp" + +#define PROFILES "Profiles" +#define ACTIVE_PROFILE "ActiveProfile" + +using DBusConnection = Glib::RefPtr; +using DBusProxy = Glib::RefPtr; + +class ShellPowerTools +{ + public: + std::shared_ptr profiles_menu; + std::shared_ptr state_action; + std::shared_ptr actions; + + DBusConnection connection; + DBusProxy powerprofile_proxy; + std::string power_mode = ""; + + bool setup_dbus_power_modes(); + + 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); + + ShellPowerTools(); + virtual ~ShellPowerTools() = default; +}; 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 +#include +#include + +#include +#include +#include +#include +#include + +class ShellWeather : public Gtk::Box +{ + public: + ShellWeather(const std::string& section); + ~ShellWeather() override; + + void set_visibility_callback(std::function callback); + + private: + Gtk::Label label; + Gtk::Image icon; + + int inotify_fd = -1; + sigc::connection inotify_connection; + std::string weather_data_path; + std::function visibility_callback; + + bool handle_inotify_event(Glib::IOCondition cond); + void update_weather(); + void notify_visibility(bool visible); +};