diff --git a/metadata/panel.xml b/metadata/panel.xml
index 2fbb3598..49dc0572 100644
--- a/metadata/panel.xml
+++ b/metadata/panel.xml
@@ -128,6 +128,11 @@
<_short>Battery Font
default
+
<_short>Network
@@ -231,6 +236,11 @@
<_short>Switch User Button Command
dm-tool switch-to-greeter
+
<_short>Volume
@@ -247,6 +257,11 @@
<_short>Volume Scroll Sensitivity
0.05
+
<_short>Notifications
diff --git a/src/panel/widgets/battery.cpp b/src/panel/widgets/battery.cpp
index 090e1f23..acbae793 100644
--- a/src/panel/widgets/battery.cpp
+++ b/src/panel/widgets/battery.cpp
@@ -2,7 +2,11 @@
#include
#include
#include
+#include
+#include "../../util/gtk-utils.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"
@@ -14,6 +18,10 @@
#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)
@@ -165,6 +173,33 @@ void WayfireBatteryInfo::update_state()
"\n\tWayfireBatteryInfo::update_state()" << std::endl;
}
+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);
+ }
+ }
+ }
+}
+
+/*
+ Change the selected radio button.
+ */
+void WayfireBatteryInfo::set_current_profile(Glib::ustring profile)
+{
+ state_action->set_state(Glib::Variant::create(profile));
+}
+
bool WayfireBatteryInfo::setup_dbus()
{
auto cancellable = Gio::Cancellable::create();
@@ -175,6 +210,30 @@ bool WayfireBatteryInfo::setup_dbus()
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());
+ } else
+ {
+ std::cout << "Unable to conect to Power Profiles. Continuing" << std::endl;
+ }
+ }
+
upower_proxy = Gio::DBus::Proxy::create_sync(connection, UPOWER_NAME,
"/org/freedesktop/UPower",
"org.freedesktop.UPower");
@@ -206,10 +265,36 @@ bool WayfireBatteryInfo::setup_dbus()
return false;
}
+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
+ }
+}
+
// TODO: simplify config loading
void WayfireBatteryInfo::init(Gtk::Box *container)
{
+ profiles_menu = Gio::Menu::create();
+ state_action = Gio::SimpleAction::create_radio_string("set_profile", "");
+ settings_action = Gio::SimpleAction::create("settings");
+
if (!setup_dbus())
{
return;
@@ -231,4 +316,76 @@ void WayfireBatteryInfo::init(Gtk::Box *container)
button.set_child(button_box);
button.property_scale_factor().signal_changed()
.connect(sigc::mem_fun(*this, &WayfireBatteryInfo::update_icon));
+
+ menu = Gio::Menu::create();
+ auto item = Gio::MenuItem::create("Settings", "actions.settings");
+ menu->append_item(item);
+
+ menu->append_section("Profiles", profiles_menu);
+
+ 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,
+ {}
+ );
+ }
+ });
+
+ settings_action->signal_activate().connect([=] (Glib::VariantBase vb)
+ {
+ std::vector battery_programs = {"mate-power-statistics", "xfce4-power-manager"};
+
+ Glib::RefPtr app_info;
+ std::string value = battery_settings;
+ if (value == "")
+ {
+ // Auto guess
+ for(auto program : battery_programs)
+ {
+ if(executable_exists(program))
+ {
+ auto keyfile = Glib::KeyFile::create();
+ keyfile->set_string("Desktop Entry", "Type", "Application");
+ keyfile->set_string("Desktop Entry", "Exec", "/bin/sh -c \"" + program + "\"");
+ app_info = Gio::DesktopAppInfo::create_from_keyfile(keyfile);
+ break;
+ }
+ }
+
+ } else
+ {
+ auto keyfile = Glib::KeyFile::create();
+ keyfile->set_string("Desktop Entry", "Type", "Application");
+ keyfile->set_string("Desktop Entry", "Exec", "/bin/sh -c \"" + value + "\"");
+ app_info = Gio::DesktopAppInfo::create_from_keyfile(keyfile);
+ }
+
+ if (app_info)
+ {
+ auto ctx = Gdk::Display::get_default()->get_app_launch_context();
+ app_info->launch(std::vector>(), ctx);
+ }
+ });
+ actions->add_action(state_action);
+ actions->add_action(settings_action);
+
+ button.insert_action_group("actions", actions);
+
+ button.set_menu_model(menu);
}
diff --git a/src/panel/widgets/battery.hpp b/src/panel/widgets/battery.hpp
index a3366e19..6fb5bdf3 100644
--- a/src/panel/widgets/battery.hpp
+++ b/src/panel/widgets/battery.hpp
@@ -1,13 +1,20 @@
#ifndef WIDGETS_BATTERY_HPP
#define WIDGETS_BATTERY_HPP
-#include
+#include
#include
#include
#include
+#include
+
#include
#include
+#include
+#include
+#include
+#include
+#include
#include "../widget.hpp"
@@ -22,15 +29,23 @@ class wayfire_config;
class WayfireBatteryInfo : public WayfireWidget
{
WfOption status_opt{"panel/battery_status"};
+ WfOption battery_settings{"panel/battery_settings"};
- Gtk::Button button;
+ Gtk::MenuButton button;
Gtk::Label label;
Gtk::Box button_box;
Gtk::Image icon;
+ Gtk::PopoverMenu popover;
+
+ std::shared_ptr menu;
+ std::shared_ptr profiles_menu;
+
+ std::shared_ptr state_action, settings_action;
+
DBusConnection connection;
- DBusProxy upower_proxy, display_device;
+ DBusProxy upower_proxy, powerprofile_proxy, display_device;
bool setup_dbus();
@@ -42,6 +57,14 @@ class WayfireBatteryInfo : public WayfireWidget
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);
+
public:
virtual void init(Gtk::Box *container);
virtual ~WayfireBatteryInfo() = default;
diff --git a/src/panel/widgets/menu.cpp b/src/panel/widgets/menu.cpp
index 705cf91e..8e3c7abd 100644
--- a/src/panel/widgets/menu.cpp
+++ b/src/panel/widgets/menu.cpp
@@ -838,6 +838,26 @@ void WayfireMenu::init(Gtk::Box *container)
return;
}
+ auto click_gesture = Gtk::GestureClick::create();
+ click_gesture->set_button(3); // Only use right-click for this callback
+ click_gesture->signal_pressed().connect([=] (int count, double x, double y)
+ {
+ std::string value = menu_alternative_launch;
+ if (value != "")
+ {
+ auto keyfile = Glib::KeyFile::create();
+ keyfile->set_string("Desktop Entry", "Type", "Application");
+ keyfile->set_string("Desktop Entry", "Exec", "/bin/sh -c \"" + value + "\"");
+ AppInfo app_info = Gio::DesktopAppInfo::create_from_keyfile(keyfile);
+ if (app_info)
+ {
+ auto ctx = Gdk::Display::get_default()->get_app_launch_context();
+ app_info->launch(std::vector>(), ctx);
+ }
+ }
+ });
+ button->add_controller(click_gesture);
+
button->property_scale_factor().signal_changed().connect(
[=] () {update_icon(); });
diff --git a/src/panel/widgets/menu.hpp b/src/panel/widgets/menu.hpp
index 035a445f..ed78c148 100644
--- a/src/panel/widgets/menu.hpp
+++ b/src/panel/widgets/menu.hpp
@@ -172,6 +172,7 @@ class WayfireMenu : public WayfireWidget
WfOption menu_min_category_width{"panel/menu_min_category_width"};
WfOption menu_min_content_height{"panel/menu_min_content_height"};
WfOption menu_show_categories{"panel/menu_show_categories"};
+ WfOption menu_alternative_launch{"panel/menu_right_click"};
void update_popover_layout();
void update_category_width();
void update_content_height();
diff --git a/src/panel/widgets/volume.cpp b/src/panel/widgets/volume.cpp
index c6d4f243..82c364d4 100644
--- a/src/panel/widgets/volume.cpp
+++ b/src/panel/widgets/volume.cpp
@@ -4,6 +4,7 @@
#include "volume.hpp"
#include "launchers.hpp"
#include "gtk-utils.hpp"
+#include "../../util/gtk-utils.hpp"
WayfireVolumeScale::WayfireVolumeScale()
{
@@ -72,6 +73,14 @@ static VolumeLevel get_volume_level(pa_volume_t volume, pa_volume_t max)
return VOLUME_LEVEL_OOR;
}
+void WayfireVolume::update_menu()
+{
+ bool muted_mic = (gvc_stream_mic && gvc_mixer_stream_get_is_muted(gvc_stream_mic));
+ bool muted_speaker = (gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream));
+ microphone_action->set_state(Glib::Variant::create(muted_mic));
+ speaker_action->set_state(Glib::Variant::create(muted_speaker));
+}
+
void WayfireVolume::update_icon()
{
VolumeLevel current =
@@ -154,6 +163,14 @@ static void notify_is_muted(GvcMixerControl *gvc_control,
{
WayfireVolume *wf_volume = (WayfireVolume*)user_data;
wf_volume->update_icon();
+ wf_volume->update_menu();
+}
+
+static void notify_is_mic_muted(GvcMixerControl *gvc_control,
+ guint id, gpointer user_data)
+{
+ WayfireVolume *wf_volume = (WayfireVolume*)user_data;
+ wf_volume->update_menu();
}
void WayfireVolume::disconnect_gvc_stream_signals()
@@ -173,6 +190,15 @@ void WayfireVolume::disconnect_gvc_stream_signals()
notify_is_muted_signal = 0;
}
+void WayfireVolume::disconnect_gvc_stream_signals_mic()
+{
+ if (notify_is_mic_muted_signal)
+ {
+ g_signal_handler_disconnect(gvc_stream_mic, notify_is_mic_muted_signal);
+ }
+ notify_is_mic_muted_signal = 0;
+}
+
void WayfireVolume::on_default_sink_changed()
{
gvc_stream = gvc_mixer_control_get_default_sink(gvc_control);
@@ -198,6 +224,23 @@ void WayfireVolume::on_default_sink_changed()
/* Finally, update the displayed volume. However, do not show the
* popup */
set_volume(gvc_mixer_stream_get_volume(gvc_stream), VOLUME_FLAG_NO_ACTION);
+ update_menu();
+}
+
+void WayfireVolume::on_default_source_changed()
+{
+ gvc_stream_mic = gvc_mixer_control_get_default_source(gvc_control);
+ if (!gvc_stream_mic)
+ {
+ printf("GVC: Failed to get default source\n");
+ return;
+ }
+
+ disconnect_gvc_stream_signals_mic();
+ notify_is_mic_muted_signal = g_signal_connect(gvc_stream_mic, "notify::is-muted",
+ G_CALLBACK(notify_is_mic_muted), this);
+
+ update_menu();
}
static void default_sink_changed(GvcMixerControl *gvc_control,
@@ -207,6 +250,13 @@ static void default_sink_changed(GvcMixerControl *gvc_control,
wf_volume->on_default_sink_changed();
}
+static void default_source_changed(GvcMixerControl *gvc_control,
+ guint id, gpointer user_data)
+{
+ WayfireVolume *wf_volume = (WayfireVolume*)user_data;
+ wf_volume->on_default_source_changed();
+}
+
void WayfireVolume::on_volume_value_changed()
{
/* User manually changed volume */
@@ -231,6 +281,7 @@ void WayfireVolume::init(Gtk::Box *container)
style->add_class("flat");
gtk_widget_set_parent(GTK_WIDGET(popover.gobj()), GTK_WIDGET(button.gobj()));
+ gtk_widget_set_parent(GTK_WIDGET(second_popover.gobj()), GTK_WIDGET(button.gobj()));
auto scroll_gesture = Gtk::EventControllerScroll::create();
scroll_gesture->signal_scroll().connect([=] (double dx, double dy)
@@ -251,7 +302,22 @@ void WayfireVolume::init(Gtk::Box *container)
return true;
}, true);
scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
+
+ auto click_gesture = Gtk::GestureClick::create();
+ click_gesture->set_button(3); // Only use right-click for this callback
+ click_gesture->signal_pressed().connect([=] (int count, double x, double y)
+ {
+ if (!second_popover.is_visible())
+ {
+ second_popover.popup();
+ } else
+ {
+ second_popover.popdown();
+ }
+ });
+
button.add_controller(scroll_gesture);
+ button.add_controller(click_gesture);
/* Setup popover */
popover.set_autohide(false);
@@ -271,8 +337,93 @@ void WayfireVolume::init(Gtk::Box *container)
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);
+ notify_default_source_changed = g_signal_connect(gvc_control,
+ "default-source-changed", G_CALLBACK(default_source_changed), this);
gvc_mixer_control_open(gvc_control);
+ /* Setup Menu */
+ auto menu = Gio::Menu::create();
+ auto menu_item_settings = Gio::MenuItem::create("Settings", "action.settings");
+ auto menu_item_toggle_volume = Gio::MenuItem::create("Mute Speaker", "action.togglespeaker");
+ auto menu_item_toggle_mic = Gio::MenuItem::create("Mute Microphone", "action.togglemicrophone");
+
+ menu->append_item(menu_item_toggle_volume);
+ menu->append_item(menu_item_toggle_mic);
+ menu->append_item(menu_item_settings);
+
+ second_popover.set_menu_model(menu);
+
+ /* Menu Actions*/
+ actions = Gio::SimpleActionGroup::create();
+
+ auto settings_action = Gio::SimpleAction::create("settings");
+ speaker_action = Gio::SimpleAction::create_bool("togglespeaker", false);
+ microphone_action = Gio::SimpleAction::create_bool("togglemicrophone", false);
+
+ settings_action->signal_activate().connect([=] (Glib::VariantBase vb)
+ {
+ std::vector volume_programs = {"pavucontrol", "mate-volume-control"};
+
+ Glib::RefPtr app_info;
+ std::string value = volume_settings;
+ if (value == "")
+ {
+ // Auto guess
+ for(auto program : volume_programs)
+ {
+ if(executable_exists(program))
+ {
+ auto keyfile = Glib::KeyFile::create();
+ keyfile->set_string("Desktop Entry", "Type", "Application");
+ keyfile->set_string("Desktop Entry", "Exec", "/bin/sh -c \"" + program + "\"");
+ app_info = Gio::DesktopAppInfo::create_from_keyfile(keyfile);
+ break;
+ }
+ }
+
+ } else
+ {
+ auto keyfile = Glib::KeyFile::create();
+ keyfile->set_string("Desktop Entry", "Type", "Application");
+ keyfile->set_string("Desktop Entry", "Exec", "/bin/sh -c \"" + value + "\"");
+ app_info = Gio::DesktopAppInfo::create_from_keyfile(keyfile);
+ }
+
+ if (app_info)
+ {
+ auto ctx = Gdk::Display::get_default()->get_app_launch_context();
+ app_info->launch(std::vector>(), ctx);
+ }
+ });
+
+ speaker_action->signal_activate().connect([=] (Glib::VariantBase vb)
+ {
+ if (gvc_stream)
+ {
+ bool muted = !gvc_mixer_stream_get_is_muted(gvc_stream);
+ gvc_mixer_stream_change_is_muted(gvc_stream, muted);
+ gvc_mixer_stream_push_volume(gvc_stream);
+
+ }
+ });
+
+ microphone_action->signal_activate().connect([=] (Glib::VariantBase vb)
+ {
+ if (gvc_stream_mic)
+ {
+ bool muted = !(gvc_stream_mic && gvc_mixer_stream_get_is_muted(gvc_stream_mic));
+ gvc_mixer_stream_change_is_muted(gvc_stream_mic, muted);
+ gvc_mixer_stream_push_volume(gvc_stream_mic);
+
+ }
+ });
+
+ actions->add_action(speaker_action);
+ actions->add_action(microphone_action);
+ actions->add_action(settings_action);
+
+ button.insert_action_group("action", actions);
+
/* Setup layout */
container->append(button);
button.set_child(main_image);
@@ -287,6 +438,10 @@ WayfireVolume::~WayfireVolume()
{
g_signal_handler_disconnect(gvc_control, notify_default_sink_changed);
}
+ if (notify_default_source_changed)
+ {
+ g_signal_handler_disconnect(gvc_control, notify_default_source_changed);
+ }
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 39d3dd25..5f001365 100644
--- a/src/panel/widgets/volume.hpp
+++ b/src/panel/widgets/volume.hpp
@@ -38,9 +38,14 @@ class WayfireVolume : public WayfireWidget
WayfireVolumeScale volume_scale;
Gtk::Button button;
Gtk::Popover popover;
+ Gtk::PopoverMenu second_popover;
WfOption timeout{"panel/volume_display_timeout"};
WfOption scroll_sensitivity{"panel/volume_scroll_sensitivity"};
+ WfOption volume_settings{"panel/volume_settings"};
+
+ std::shared_ptr actions;
+ std::shared_ptr speaker_action, microphone_action;
// void on_volume_scroll(GdkEventScroll *event);
// void on_volume_button_press(GdkEventButton *event);
@@ -49,14 +54,18 @@ class WayfireVolume : public WayfireWidget
GvcMixerControl *gvc_control;
GvcMixerStream *gvc_stream = NULL;
+ GvcMixerStream *gvc_stream_mic = NULL;
gdouble max_norm; // maximal volume for current stream
gulong notify_volume_signal = 0;
gulong notify_is_muted_signal = 0;
+ gulong notify_is_mic_muted_signal = 0;
gulong notify_default_sink_changed = 0;
+ gulong notify_default_source_changed = 0;
sigc::connection popover_timeout;
sigc::connection volume_changed_signal;
void disconnect_gvc_stream_signals();
+ void disconnect_gvc_stream_signals_mic();
enum set_volume_flags_t
{
@@ -86,12 +95,14 @@ class WayfireVolume : public WayfireWidget
/** Update the icon based on volume and muted state */
void update_icon();
+ void update_menu();
/** 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();
+ void on_default_source_changed();
/**
* Check whether the popover should be auto-hidden, and if yes, start
diff --git a/src/util/gtk-utils.cpp b/src/util/gtk-utils.cpp
index 80cec7b5..48ad87ca 100644
--- a/src/util/gtk-utils.cpp
+++ b/src/util/gtk-utils.cpp
@@ -72,3 +72,27 @@ void image_set_icon(Gtk::Image *image, std::string path)
image->set_from_icon_name(path);
}
}
+
+
+/* Check if an executable of this name exists on path */
+bool executable_exists(std::string name)
+{
+ struct stat sb;
+ std::string delimiter = ":";
+ std::string path = std::string(getenv("PATH"));
+ size_t start_pos = 0, end_pos = 0;
+
+ while ((end_pos = path.find(':', start_pos)) != std::string::npos)
+ {
+ std::string current_path =
+ path.substr(start_pos, end_pos - start_pos) + "/"+name;
+
+ if ((stat(current_path.c_str(), &sb) == 0) && (sb.st_mode & S_IXOTH))
+ {
+ return true;
+ }
+
+ start_pos = end_pos + 1;
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/src/util/gtk-utils.hpp b/src/util/gtk-utils.hpp
index 0272f2bf..62344245 100644
--- a/src/util/gtk-utils.hpp
+++ b/src/util/gtk-utils.hpp
@@ -22,4 +22,6 @@ void invert_pixbuf(Glib::RefPtr& pbuff);
void image_set_icon(Gtk::Image *image, std::string path);
+bool executable_exists(std::string name);
+
#endif /* end of include guard: WF_GTK_UTILS */