Merge pull request #3247 from benjamin-voisin/menu

Adding the ability to have dropdown menu for modules
This commit is contained in:
Alexis Rouillard 2024-07-01 09:18:13 +02:00 committed by GitHub
commit 9c7a275cd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 611 additions and 2 deletions

View File

@ -27,6 +27,10 @@ class ALabel : public AModule {
bool handleToggle(GdkEventButton *const &e) override;
virtual std::string getState(uint8_t value, bool lesser = false);
std::map<std::string, GtkMenuItem *> submenus_;
std::map<std::string, std::string> menuActionsMap_;
static void handleGtkMenuEvent(GtkMenuItem *menuitem, gpointer data);
};
} // namespace waybar

View File

@ -2,6 +2,7 @@
#include <glibmm/dispatcher.h>
#include <glibmm/markup.h>
#include <gtkmm.h>
#include <gtkmm/eventbox.h>
#include <json/json.h>
@ -44,6 +45,7 @@ class AModule : public IModule {
virtual bool handleMouseLeave(GdkEventCrossing *const &ev);
virtual bool handleScroll(GdkEventScroll *);
virtual bool handleRelease(GdkEventButton *const &ev);
GObject *menu_;
private:
bool handleUserEvent(GdkEventButton *const &ev);

View File

@ -81,6 +81,19 @@ The *backlight* module displays the current backlight level.
default: 1.0 ++
The speed at which to change the brightness when scrolling.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# EXAMPLE:
```

View File

@ -109,6 +109,19 @@ The *battery* module displays the current capacity and state (eg. charging) of y
default: false ++
Option to enable battery compatibility if not detected.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{capacity}*: Capacity in percentage

View File

@ -129,6 +129,19 @@ Addressed by *bluetooth*
typeof: string ++
This format is used to define how each connected device should be displayed within the *device_enumerate* format replacement in the tooltip menu.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{status}*: Status of the bluetooth device.

View File

@ -120,6 +120,18 @@ libcava lives in:
:[ string
:[ /dev/stdout
:[ It's impossible to set it. Waybar sets it to = /dev/stdout for internal needs
|[ *menu*
:[ string
:[
:[ Action that popups the menu.
|[ *menu-file*
:[ string
:[
:[ Location of the menu descriptor file. There need to be an element of type GtkMenu with id *menu*
|[ *menu-actions*
:[ array
:[
:[ The actions corresponding to the buttons of the menu.
Configuration can be provided as:
- The only cava configuration file which is provided through *cava_config*. The rest configuration can be skipped

View File

@ -84,6 +84,18 @@ $XDG_CONFIG_HOME/waybar/config ++
:[ string
:[ same as format
:[ Tooltip on hover
|[ *menu*
:[ string
:[
:[ Action that popups the menu.
|[ *menu-file*
:[ string
:[
:[ Location of the menu descriptor file. There need to be an element of type GtkMenu with id *menu*
|[ *menu-actions*
:[ array
:[
:[ The actions corresponding to the buttons of the menu.
View all valid format options in *strftime(3)* or have a look https://en.cppreference.com/w/cpp/chrono/duration/formatter

View File

@ -121,6 +121,19 @@ Addressed by *custom/<name>*
default: false ++
Option to enable escaping of script output.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# RETURN-TYPE
When *return-type* is set to *json*, Waybar expects the *exec*-script to output its data in JSON format.

View File

@ -93,6 +93,19 @@ Addressed by *disk*
typeof: string ++
Use with specific_free, specific_used, and specific_total to force calculation to always be in a certain unit. Accepts kB, kiB, MB, Mib, GB, GiB, TB, TiB.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{percentage_used}*: Percentage of disk in use.

View File

@ -25,6 +25,19 @@ Addressed by *hyprland/language*
typeof: string ++
Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "at-translated-set..." is recommended.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS

View File

@ -80,6 +80,19 @@ Addressed by *hyprland/submap*
default: Default ++
Option to set the submap name to display when not in an active submap.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# EXAMPLES

View File

@ -89,6 +89,19 @@ screensaver, also known as "presentation mode".
typeof: string ++
This format is used when the inhibit is deactivated.
*menu*: ++
typeof: string ++
Action that popups the menu. Cannot be "on-click".
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{status}*: status (*activated* or *deactivated*)

View File

@ -76,6 +76,19 @@ See *systemd-inhibit*(1) for more information.
default: true ++
Option to disable tooltip on hover.
*menu*: ++
typeof: string ++
Action that popups the menu. Cannot be "on-click".
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{status}*: status (*activated* or *deactivated*)

View File

@ -85,6 +85,19 @@ Addressed by *jack*
typeof: string ++
Command to execute when the module is updated.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{load}*: The current CPU load estimated by JACK.

View File

@ -84,6 +84,19 @@ Addressed by *memory*
default: true ++
Option to disable tooltip on hover.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{percentage}*: Percentage of memory in use.

153
man/waybar-menu.5.scd Normal file
View File

@ -0,0 +1,153 @@
waybar-menu(5)
# NAME
waybar - menu property
# OVERVIEW
Some modules support a 'menu', which allows to have a popup menu whan a defined
click is done over the module.
# PROPERTIES
A module that implements a 'menu' needs 3 properties defined in its config :
*menu*: ++
typeof: string ++
Action that popups the menu. The possibles actions are :
[- *Option*
:- *Description*
|[ *on-click*
:< When you left-click on the module
|[ *on-click-release*
:< When you release left button on the module
|[ *on-double-click*
:< When you double left click on the module
|[ *on-triple-click*
:< When you triple left click on the module
|[ *on-click-middle*
:< When you middle click on the module using mousewheel
|[ *on-click-middle-release*
:< When you release mousewheel button on the module
|[ *on-double-click-middle*
:< When you double middle click on the module
|[ *on-triple-click-middle*
:< When you triple middle click on the module
|[ *on-click-right*
:< When you right click on the module using
|[ *on-click-right-release*
:< When you release right button on the module
|[ *on-double-click-right*
:< When you double right click on the module
|[ *on-triple-click-right*
:< When you triple middle click on the module
|[ *on-click-backward*
:< When you click on the module using mouse backward button
|[ *on-click-backward-release*
:< When you release mouse backward button on the module
|[ *on-double-click-backward*
:< When you double click on the module using mouse backward button
|[ *on-triple-click-backward*
:< When you triple click on the module using mouse backawrd button
|[ *on-click-forward*
:< When you click on the module using mouse forward button
|[ *on-click-forward-release*
:< When you release mouse forward button on the module
|[ *on-double-click-forward*
:< When you double click on the module using mouse forward button
|[ *on-triple-click-forward*
:< When you triple click on the module using mouse forward button
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*.
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu. The identifiers of
each actions needs to exists as an id in the 'menu-file' for it to be linked
properly.
# MENU-FILE
The menu-file is an `.xml` file representing a GtkBuilder. Documentation for it
can be found here : https://docs.gtk.org/gtk4/class.Builder.html
Here, it needs to have an element of type GtkMenu with id "menu". Eeach actions
in *menu-actions* are linked to elements in the *menu-file* file by the id of
the elements.
# EXAMPLE
Module config :
```
"custom/power": {
"format" : "⏻ ",
"tooltip": false,
"menu": "on-click",
"menu-file": "~/.config/waybar/power_menu.xml",
"menu-actions": {
"shutdown": "shutdown",
"reboot": "reboot",
"suspend": "systemctl suspend",
"hibernate": "systemctl hibernate",
},
},
```
~/.config/waybar/power_menu.xml :
```
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkMenu" id="menu">
<child>
<object class="GtkMenuItem" id="suspend">
<property name="label">Suspend</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="hibernat">
<property name="label">Hibernate</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="shutdown">
<property name="label">Shutdown</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="delimiter1"/>
</child>
<child>
<object class="GtkMenuItem" id="reboot">
<property name="label">Reboot</property>
</object>
</child>
</object>
</interface>
```
# STYLING MENUS
- *menu*
Style for the menu
- *menuitem*
Style for items in the menu
# EXAMPLE:
```
menu {
border-radius: 15px;
background: #161320;
color: #B5E8E0;
}
menuitem {
border-radius: 15px;
}
```

View File

@ -162,6 +162,19 @@ Addressed by *mpd*
default: {} ++
Icon to show depending on the "single" option (*{ "on": "...", "off": "..." }*)
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
## WHEN PLAYING/PAUSED

View File

@ -129,6 +129,19 @@ Addressed by *network*
typeof: string ++
This format is used when the displayed interface is disabled.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{ifname}*: Name of the network interface.

View File

@ -113,6 +113,19 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c
typeof: array ++
Sinks in this list will not be shown as active sink by Waybar. Entries should be the sink's description field.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name.

View File

@ -51,6 +51,19 @@ Addressed by *river/layout*
typeof: string ++
Command to execute when you right-click on the module.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# EXAMPLE
```

View File

@ -65,6 +65,19 @@ Addressed by *river/mode*
typeof: double ++
Threshold to be used when scrolling.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# EXAMPLES
```

View File

@ -49,6 +49,19 @@ Addressed by *river/window*
typeof: string ++
Command to execute when you right-click on the module.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# EXAMPLES
```

View File

@ -74,6 +74,19 @@ cursor is over the module, and clicking on the module toggles mute.
typeof: double ++
Threshold to be used when scrolling.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{volume}*: Volume in percentage.

View File

@ -32,6 +32,19 @@ Addressed by *sway/language*
default: true ++
Option to disable tooltip on hover.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{short}*: Short name of layout (e.g. "us"). Equals to {}.

View File

@ -70,6 +70,19 @@ Addressed by *sway/mode*
default: true ++
Option to disable tooltip on hover.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# EXAMPLES
```

View File

@ -36,6 +36,19 @@ Addressed by *sway/scratchpad*
default: {app}: {title} ++
The format, how information in the tooltip should be displayed.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{icon}*: Icon, as defined in *format-icons*.

View File

@ -36,6 +36,19 @@ Addressed by *systemd-failed-units*
default: *true* ++
Option to hide this module when there is no failing units.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{nr_failed_system}*: Number of failed units from systemwide (PID=1) systemd.

View File

@ -111,6 +111,19 @@ Addressed by *temperature*
default: true ++
Option to disable tooltip on hover.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{temperatureC}*: Temperature in Celsius.

View File

@ -62,6 +62,19 @@ compatible devices in the tooltip.
default: true ++
Option to disable battery icon.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{percentage}*: The battery capacity in percentage

View File

@ -87,6 +87,19 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
default: 100 ++
The maximum volume that can be set, in percentage.
*menu*: ++
typeof: string ++
Action that popups the menu.
*menu-file*: ++
typeof: string ++
Location of the menu descriptor file. There need to be an element of type
GtkMenu with id *menu*
*menu-actions*: ++
typeof: array ++
The actions corresponding to the buttons of the menu.
# FORMAT REPLACEMENTS
*{volume}*: Volume in percentage.

View File

@ -192,6 +192,7 @@ man_files = files(
'man/waybar-idle-inhibitor.5.scd',
'man/waybar-image.5.scd',
'man/waybar-states.5.scd',
'man/waybar-menu.5.scd',
'man/waybar-temperature.5.scd',
)

View File

@ -30,7 +30,8 @@
"battery",
"battery#bat2",
"clock",
"tray"
"tray",
"custom/power"
],
// Modules configuration
// "sway/workspaces": {
@ -198,5 +199,17 @@
"escape": true,
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
},
"custom/power": {
"format" : "⏻ ",
"tooltip": false,
"menu": "on-click",
"menu-file": "$HOME/.config/waybar/power_menu.xml", // Menu file in resources folder
"menu-actions": {
"shutdown": "shutdown",
"reboot": "reboot",
"suspend": "systemctl suspend",
"hibernate": "systemctl hibernate"
}
}
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkMenu" id="menu">
<child>
<object class="GtkMenuItem" id="suspend">
<property name="label">Suspend</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="hibernate">
<property name="label">Hibernate</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="shutdown">
<property name="label">Shutdown</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="delimiter1"/>
</child>
<child>
<object class="GtkMenuItem" id="reboot">
<property name="label">Reboot</property>
</object>
</child>
</object>
</interface>

View File

@ -2,6 +2,8 @@
#include <fmt/format.h>
#include <fstream>
#include <iostream>
#include <util/command.hpp>
namespace waybar {
@ -9,7 +11,9 @@ namespace waybar {
ALabel::ALabel(const Json::Value& config, const std::string& name, const std::string& id,
const std::string& format, uint16_t interval, bool ellipsize, bool enable_click,
bool enable_scroll)
: AModule(config, name, id, config["format-alt"].isString() || enable_click, enable_scroll),
: AModule(config, name, id,
config["format-alt"].isString() || config["menu"].isString() || enable_click,
enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(config_["interval"] == "once"
? std::chrono::seconds::max()
@ -51,6 +55,47 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
}
}
// If a GTKMenu is requested in the config
if (config_["menu"].isString()) {
// Create the GTKMenu widget
try {
// Check that the file exists
std::string menuFile = config_["menu-file"].asString();
// Read the menu descriptor file
std::ifstream file(menuFile);
if (!file.is_open()) {
throw std::runtime_error("Failed to open file: " + menuFile);
}
std::stringstream fileContent;
fileContent << file.rdbuf();
GtkBuilder* builder = gtk_builder_new();
// Make the GtkBuilder and check for errors in his parsing
if (!gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr)) {
throw std::runtime_error("Error found in the file " + menuFile);
}
menu_ = gtk_builder_get_object(builder, "menu");
if (!menu_) {
throw std::runtime_error("Failed to get 'menu' object from GtkBuilder");
}
submenus_ = std::map<std::string, GtkMenuItem*>();
menuActionsMap_ = std::map<std::string, std::string>();
// Linking actions to the GTKMenu based on
for (Json::Value::const_iterator it = config_["menu-actions"].begin();
it != config_["menu-actions"].end(); ++it) {
std::string key = it.key().asString();
submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str()));
menuActionsMap_[key] = it->asString();
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
(gpointer)menuActionsMap_[key].c_str());
}
} catch (std::runtime_error& e) {
spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what());
}
}
if (config_["justify"].isString()) {
auto justify_str = config_["justify"].asString();
if (justify_str == "left") {
@ -125,6 +170,10 @@ bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
return AModule::handleToggle(e);
}
void ALabel::handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data) {
waybar::util::command::res res = waybar::util::command::exec((char*)data, "GtkMenu");
}
std::string ALabel::getState(uint8_t value, bool lesser) {
if (!config_["states"].isObject()) {
return "";

View File

@ -133,6 +133,16 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
format = rec->second;
}
// Check that a menu has been configured
if (config_["menu"].isString()) {
// Check if the event is the one specified for the "menu" option
if (rec->second == config_["menu"].asString()) {
// Popup the menu
gtk_widget_show_all(GTK_WIDGET(menu_));
gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast<GdkEvent*>(e));
}
}
// Second call user scripts
if (!format.empty()) {
if (config_[format].isString())