diff --git a/metadata/panel.xml b/metadata/panel.xml index c24c4b96..334d2bf7 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -189,5 +189,20 @@ 2.5 + + <_short>Customizables + + + + diff --git a/src/panel/meson.build b/src/panel/meson.build index ae20bb5f..3d2ff1e3 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -2,6 +2,7 @@ widget_sources = ['widgets/battery.cpp', 'widgets/menu.cpp', 'widgets/clock.cpp', 'widgets/launchers.cpp', + 'widgets/customizables.cpp', 'widgets/network.cpp', 'widgets/spacing.cpp', 'widgets/window-list/window-list.cpp', diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index 30f26378..2319199b 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -19,6 +19,7 @@ #include "widgets/menu.hpp" #include "widgets/clock.hpp" #include "widgets/launchers.hpp" +#include "widgets/customizables.hpp" #include "widgets/network.hpp" #include "widgets/spacing.hpp" #ifdef HAVE_PULSE @@ -194,6 +195,8 @@ class WayfirePanel::impl return Widget(new WayfireMenu()); if (name == "launchers") return Widget(new WayfireLaunchers()); + if (name == "custom") + return Widget(new WayfireCustomizables()); if (name == "clock") return Widget(new WayfireClock()); if (name == "network") diff --git a/src/panel/widgets/customizables.cpp b/src/panel/widgets/customizables.cpp new file mode 100644 index 00000000..f64be3c3 --- /dev/null +++ b/src/panel/widgets/customizables.cpp @@ -0,0 +1,315 @@ +#include +#include +#include +#include +#include +#include +#include "customizables.hpp" + +// create a customizable widget from label,icon and commands + +bool CustomizableInfo::load(std::string icon, + std::string label, + std::string cmd_btn_1, + std::string cmd_btn_2, + std::string cmd_btn_3, + std::string cmd_scr_up, + std::string cmd_scr_dn, + std::string cmd_tooltip) +{ + this->icon = icon; + this->label = label; + this->cmd_btn_1 = cmd_btn_1; + this->cmd_btn_2 = cmd_btn_2; + this->cmd_btn_3 = cmd_btn_3; + this->cmd_scr_up = cmd_scr_up; + this->cmd_scr_dn = cmd_scr_dn; + this->cmd_tooltip = cmd_tooltip; + return load_icon_pixbuf_safe(icon, 24).get() != nullptr; +} + +Glib::RefPtr CustomizableInfo::get_pixbuf(int32_t size) +{ + return Gdk::Pixbuf::create_from_file(icon, size, size); +} + +std::string CustomizableInfo::get_text() +{ + return label; +} + +// Execute a shell command +void CustomizableInfo::execute(std::string command) +{ + if (command.length() != 0) + { + Glib::spawn_command_line_async("/bin/bash -c \'" + command + "\'"); + } +} + +// Mouse-button command dispatch +void CustomizableInfo::execute(guint button) +{ + std::string command; + switch(button) + { + case GDK_BUTTON_PRIMARY: + command = cmd_btn_1; + break; + case GDK_BUTTON_MIDDLE: + command = cmd_btn_2; + break; + case GDK_BUTTON_SECONDARY: + command = cmd_btn_3; + break; + default: // …? + command = ""; + } + execute(command); +} + +// Mouse-wheel command dispatch +void CustomizableInfo::execute(GdkScrollDirection direction) +{ + std::string command; + switch(direction) + { + case GDK_SCROLL_UP: + command = cmd_scr_up; + break; + case GDK_SCROLL_DOWN: + command = cmd_scr_dn; + break; + default: // GDK_SCROLL_LEFT, GDK_SCROLL_RIGHT, GDK_SCROLL_SMOOTH + command = ""; + break; + } + execute(command); +} + +// Execute a shell command and retrieve its output +void CustomizableInfo::execute(std::string command, std::string *output) +{ + std::string stdout; + std::string stderr; + int exit_status; + if (command.length() != 0) + { + Glib::spawn_command_line_sync("/bin/bash -c \'" + command + "\'", + &stdout, + &stderr, + &exit_status); + if (exit_status == 0) + { + *output = stdout.substr(0, stdout.length() - 1); + } else + { + *output = stderr.substr(0, stderr.length() - 1); + } + } +} + +CustomizableInfo::CustomizableInfo() {} +CustomizableInfo::~CustomizableInfo() {} + +bool WfCustomizableButton::initialize(std::string name, + std::string icon, + std::string label, + std::string cmd_btn_1, + std::string cmd_btn_2, + std::string cmd_btn_3, + std::string cmd_scr_up, + std::string cmd_scr_dn, + std::string cmd_tooltip) +{ + customizable_name = name; + info = new CustomizableInfo(); + if (!info->load(icon, label, cmd_btn_1, cmd_btn_2, cmd_btn_3, cmd_scr_up, cmd_scr_dn, cmd_tooltip)) + { + std::cerr << "Failed to load custom widget " << label << std::endl; + return false; + } + button.add(image); + button.set_events(Gdk::SCROLL_MASK | Gdk::BUTTON_PRESS_MASK); // | Gdk::SMOOTH_SCROLL_MASK + button.signal_button_press_event().connect(sigc::mem_fun(this, &WfCustomizableButton::on_click)); + button.signal_button_release_event().connect(sigc::mem_fun(this, &WfCustomizableButton::on_click)); + button.signal_scroll_event().connect(sigc::mem_fun(this, &WfCustomizableButton::on_scroll)); + button.signal_enter_notify_event().connect(sigc::mem_fun(this, &WfCustomizableButton::on_enter)); + // button.signal_leave_notify_event().connect(sigc::mem_fun(this, &WfCustomizableButton::on_leave)); + button.set_valign(Gtk::ALIGN_CENTER); + button.get_style_context()->add_class("flat"); + image.set_valign(Gtk::ALIGN_CENTER); + button.property_scale_factor().signal_changed() + .connect(sigc::mem_fun(this, &WfCustomizableButton::on_scale)); + button.set_tooltip_text(info->get_text()); + on_scale(); + return true; +} + +bool WfCustomizableButton::on_click(GdkEventButton *ev) +{ + if (ev->type == GDK_BUTTON_RELEASE) + { + info->execute(ev->button); + } + if (ev->button == 1 && ev->type == GDK_BUTTON_PRESS) + { + /* touch will generate button_press, but not enter notify */ + } + return true; +} + +bool WfCustomizableButton::on_scroll(GdkEventScroll *ev) +{ + info->execute(ev->direction); + return true; +} + +bool WfCustomizableButton::on_enter(GdkEventCrossing* ev) +{ + std::string output; + if (info->cmd_tooltip.length() > 0) + { + info->execute(info->cmd_tooltip, &output); + button.set_tooltip_text(output); + } + return true; +} + +bool WfCustomizableButton::on_leave(GdkEventCrossing* ev) +{ + return true; +} + +/* Because icons can have different sizes, we need to use a Gdk:Pixbuf + * to convert them to the appropriate size. However, Gdk::Pixbuf operates + * in absolute pixel size, so this doesn't work nicely with scaled outputs. + * + * To get around the problem, we first create the Pixbuf with a scaled size, + * then convert it to a cairo_surface with the appropriate scale, and use this + * cairo surface as the source for the Gtk::Image */ +void WfCustomizableButton::on_scale() +{ + int scale = image.get_scale_factor(); + + // hold a reference to the RefPtr + auto ptr_pbuff = info->get_pixbuf(icon_size * image.get_scale_factor()); + if (!ptr_pbuff) + { + return; + } + if (icon_invert) + { + invert_pixbuf(ptr_pbuff); + } + set_image_pixbuf(image, ptr_pbuff, scale); +} + +WfCustomizableButton::WfCustomizableButton() +{ + /* I tried to set these as class variables (static), + but got linking error… + */ + icon_size = WfOption {"panel/customizables_size"}; + icon_invert = WfOption {"panel/customizables_invert"}; +} + +WfCustomizableButton::~WfCustomizableButton() +{ + delete info; +} + +static bool begins_with(const std::string& string, const std::string& prefix) +{ + return string.size() >= prefix.size() && + string.substr(0, prefix.size()) == prefix; +} + +customizable_container WayfireCustomizables::get_customizables_from_config() +{ + auto section = WayfireShellApp::get().config.get_section("panel"); + const std::string custom_label_prefix = "custom_label_"; + const std::string custom_icon_prefix = "custom_icon_"; + const std::string custom_btn1_prefix = "custom_btn1_cmd_"; + const std::string custom_btn2_prefix = "custom_btn2_cmd_"; + const std::string custom_btn3_prefix = "custom_btn3_cmd_"; + const std::string custom_scr_up_prefix = "custom_scr_up_cmd_"; + const std::string custom_scr_dn_prefix = "custom_scr_dn_cmd_"; + const std::string custom_tooltip_cmd = "custom_tooltip_cmd_"; + + customizable_container customizables; + auto try_push_customizable = [&customizables] (const std::string name, + const std::string icon, + const std::string label, + const std::string cmd_btn_1, + const std::string cmd_btn_2, + const std::string cmd_btn_3, + const std::string cmd_scr_up, + const std::string cmd_scr_dn, + const std::string cmd_tooltip) + { + auto customizable = new WfCustomizableButton(); + if (customizable->initialize(name, icon, label, cmd_btn_1, cmd_btn_2, cmd_btn_3, + cmd_scr_up, cmd_scr_dn, cmd_tooltip)) + { + customizables.push_back(std::unique_ptr(customizable)); + } else + { + delete customizable; + } + }; + + /* This one needs a label + custom_label_ = + custom_icon_ = + custom_btn[1-3]_cmd_ = + custom_scr_[up|dn]_cmd_ = + custom_tooltip_cmd_ = + */ + for (auto opt : section->get_registered_options()) + { + /* we have a custom widget */ + if (begins_with(opt->get_name(), custom_label_prefix)) + { + /* extract customizable name, i.e the string after the prefix */ + auto customizable_name = opt->get_name().substr(custom_label_prefix.size()); + auto label_option = section->get_option(custom_label_prefix + customizable_name); + /* look for the corresponding icon… */ + auto icon_option = section->get_option_or(custom_icon_prefix + customizable_name); + /* and corresponding commands. */ + auto cmd_btn_1_option = section->get_option_or(custom_btn1_prefix + customizable_name); + auto cmd_btn_2_option = section->get_option_or(custom_btn2_prefix + customizable_name); + auto cmd_btn_3_option = section->get_option_or(custom_btn3_prefix + customizable_name); + auto cmd_scr_up_option = section->get_option_or(custom_scr_up_prefix + customizable_name); + auto cmd_scr_dn_option = section->get_option_or(custom_scr_dn_prefix + customizable_name); + auto cmd_tooltip_option = section->get_option_or(custom_tooltip_cmd + customizable_name); + try_push_customizable(opt->get_value_str(), + (icon_option)?icon_option->get_value_str():"", + label_option->get_value_str(), + (cmd_btn_1_option)?cmd_btn_1_option->get_value_str():"", + (cmd_btn_2_option)?cmd_btn_2_option->get_value_str():"", + (cmd_btn_3_option)?cmd_btn_3_option->get_value_str():"", + (cmd_scr_up_option)?cmd_scr_up_option->get_value_str():"", + (cmd_scr_dn_option)?cmd_scr_dn_option->get_value_str():"", + (cmd_tooltip_option)?cmd_tooltip_option->get_value_str():""); + } + } + return customizables; +} + +void WayfireCustomizables::init(Gtk::HBox *container) +{ + container->pack_start(box, Gtk::PACK_SHRINK); // false, false); + handle_config_reload(); +} + +void WayfireCustomizables::handle_config_reload() +{ + box.set_spacing(WfOption {"panel/customizables_spacing"}); + customizables = get_customizables_from_config(); + for (auto& c : customizables) + { + box.pack_start(c->button, false, false); + } + box.show_all(); +} diff --git a/src/panel/widgets/customizables.hpp b/src/panel/widgets/customizables.hpp new file mode 100644 index 00000000..aa0af75c --- /dev/null +++ b/src/panel/widgets/customizables.hpp @@ -0,0 +1,84 @@ +#ifndef CUSTOMIZABLES_HPP +#define CUSTOMIZABLES_HPP + +#include +#include +#include +#include +#include +#include +#include "../widget.hpp" + +struct CustomizableInfo +{ + std::string icon; + std::string label; + std::string cmd_btn_1; + std::string cmd_btn_2; + std::string cmd_btn_3; + std::string cmd_scr_up; + std::string cmd_scr_dn; + std::string cmd_tooltip; + + bool load(std::string icon, + std::string label, + std::string cmd_btn_1, + std::string cmd_btn_2, + std::string cmd_btn_3, + std::string cmd_scr_up, + std::string cmd_scr_dn, + std::string cmd_tooltip); + Glib::RefPtr get_pixbuf(int32_t size); + void execute(std::string command); + void execute(guint button); + void execute(GdkScrollDirection direction); + void execute(std::string command, std::string *output); + std::string get_text(); + CustomizableInfo(); + ~CustomizableInfo(); +}; + +struct WfCustomizableButton +{ + int icon_size; + bool icon_invert; + std::string customizable_name; + Gtk::Image image; + Gtk::Button button; + CustomizableInfo *info = NULL; + + WfCustomizableButton(); + WfCustomizableButton(const WfCustomizableButton& other) = delete; + WfCustomizableButton& operator = (const WfCustomizableButton&) = delete; + ~WfCustomizableButton(); + + bool initialize(std::string name, + std::string icon, + std::string label, + std::string cmd_btn_1, + std::string cmd_btn_2, + std::string cmd_btn_3, + std::string cmd_scr_up, + std::string cmd_scr_dn, + std::string cmd_tooltip); + bool on_click(GdkEventButton *ev); + bool on_scroll(GdkEventScroll *ev); + bool on_enter(GdkEventCrossing* ev); + bool on_leave(GdkEventCrossing* ev); + void on_scale(); +}; + +using customizable_container = std::vector>; +class WayfireCustomizables: public WayfireWidget +{ + Gtk::HBox box; + customizable_container customizables; + customizable_container get_customizables_from_config(); + +public: + virtual void init(Gtk::HBox *container); + virtual void handle_config_reload(); + virtual ~WayfireCustomizables() {}; +}; + +#endif /* end of include guard: CUSTOMIZABLES_HPP */