Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions include/ALabel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ 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
7 changes: 6 additions & 1 deletion include/AModule.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <gtkmm.h>
#include <gtkmm/eventbox.h>
#include <json/json.h>
#include <optional>

#include "IModule.hpp"

Expand Down Expand Up @@ -48,7 +49,11 @@ class AModule : public IModule {
virtual bool handleMouseLeave(GdkEventCrossing* const& ev);
virtual bool handleScroll(GdkEventScroll*);
virtual bool handleRelease(GdkEventButton* const& ev);
GObject* menu_ = nullptr;
virtual void handleGtkMenuEvent(std::string command);
Glib::RefPtr<Gtk::Builder> builder_;
Gtk::Menu* menu_ = nullptr;
std::optional<Gdk::Gravity> widget_anchor_;
std::optional<Gdk::Gravity> menu_anchor_;

private:
bool handleUserEvent(GdkEventButton* const& ev);
Expand Down
67 changes: 64 additions & 3 deletions man/waybar-menu.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ waybar - menu property
# OVERVIEW


Some modules support a 'menu', which allows to have a popup menu when a defined
All modules support a 'menu', which allows to have a popup menu when a defined
click is done over the module.

# PROPERTIES
Expand Down Expand Up @@ -72,6 +72,62 @@ A module that implements a 'menu' needs 3 properties defined in its config :
each actions needs to exists as an id in the 'menu-file' for it to be linked
properly.

*widget-anchor*: ++
typeof: string ++
The point on widget to align with menu's anchor point.
Both widget-anchor and menu-anchor must be set and not "none" for this to work.

[- *Option*
:- *Description*
|[ *north-west*
:< The top left corner.
|[ *north*
:< The reference point is in the middle of the top edge.
|[ *north-east*
:< The top right corner.
|[ *west*
:< The middle of the left edge.
|[ *center*
:< The center of the window.
|[ *east*
:< The middle of the right edge.
|[ *south-west*
:< The lower left corner.
|[ *south*
:< The middle of the lower edge.
|[ *south-east*
:< The lower right corner.
|[ *none*
:< Disables the anchor and shows the menu at the cursor position.

*menu-anchor*: ++
typeof: string ++
The point on menu to align with widget's anchor point.
Both menu-anchor and widget-anchor must be set and not "none" for this to work.

[- *Option*
:- *Description*
|[ *north-west*
:< The top left corner.
|[ *north*
:< The reference point is in the middle of the top edge.
|[ *north-east*
:< The top right corner.
|[ *west*
:< The middle of the left edge.
|[ *center*
:< The center of the window.
|[ *east*
:< The middle of the right edge.
|[ *south-west*
:< The lower left corner.
|[ *south*
:< The middle of the lower edge.
|[ *south-east*
:< The lower right corner.
|[ *none*
:< Disables the anchor and shows the menu at the cursor position.

# MENU-FILE

The menu-file is an `.xml` file representing a GtkBuilder. Documentation for it
Expand Down Expand Up @@ -139,15 +195,20 @@ Module config :
- *menuitem*
Style for items in the menu

Use the CSS class selector .custom-<name> to target a specific menu/menuitem.

# EXAMPLE:

```
menu {
menu.custom-power {
border-radius: 15px;
background: #161320;
color: #B5E8E0;
}
menuitem {
menuitem.custom-power {
border-radius: 15px;
}
menuitem.custom-power:hover {
background: #005d96
}
```
69 changes: 1 addition & 68 deletions src/ALabel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@

#include <fmt/format.h>

#include <fstream>
#include <iostream>
#include <util/command.hpp>

#include "config.hpp"

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() || config["menu"].isString() || enable_click,
config["format-alt"].isString() || enable_click,
enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format),

Expand Down Expand Up @@ -66,65 +62,6 @@ 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();

// there might be "~" or "$HOME" in original path, try to expand it.
auto result = Config::tryExpandPath(menuFile, "");
if (result.empty()) {
throw std::runtime_error("Failed to expand file: " + menuFile);
}

menuFile = result.front();
// 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) == 0U) {
g_object_unref(builder);
throw std::runtime_error("Error found in the file " + menuFile);
}

menu_ = gtk_builder_get_object(builder, "menu");
if (menu_ == nullptr) {
g_object_unref(builder);
throw std::runtime_error("Failed to get 'menu' object from GtkBuilder");
}
// Keep the menu alive after dropping the transient GtkBuilder.
g_object_ref(menu_);
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();
auto* item = gtk_builder_get_object(builder, key.c_str());
if (item == nullptr) {
spdlog::warn("Menu item '{}' not found in builder file", key);
continue;
}
submenus_[key] = GTK_MENU_ITEM(item);
menuActionsMap_[key] = it->asString();
g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent),
(gpointer)menuActionsMap_[key].c_str());
}
g_object_unref(builder);
} 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") {
Expand Down Expand Up @@ -201,10 +138,6 @@ bool waybar::ALabel::handleToggle(GdkEventButton* const& e) {
return AModule::handleToggle(e);
}

void ALabel::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) {
waybar::util::command::forkExec((char*)data, "GtkMenu");
}

std::string ALabel::getState(uint8_t value, bool lesser) {
if (!config_["states"].isObject()) {
return "";
Expand Down
103 changes: 96 additions & 7 deletions src/AModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
#include <fmt/format.h>
#include <spdlog/spdlog.h>

#include <fstream>
#include <util/command.hpp>

#include "gdk/gdk.h"
#include "gdkmm/cursor.h"

#include "config.hpp"

namespace waybar {

AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id,
Expand Down Expand Up @@ -45,7 +48,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
config[eventEntry.second].isString();
}) != eventMap_.cend();

if (enable_click || hasUserEvents) {
if (enable_click || hasUserEvents || config["menu"].isString()) {
hasUserEvents_ = true;
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &AModule::handleToggle));
Expand Down Expand Up @@ -80,6 +83,87 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
spdlog::warn("unknown cursor option configured on module {}", name_);
}
}

// 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();

// there might be "~" or "$HOME" in original path, try to expand it.
auto result = Config::tryExpandPath(menuFile, "");
if (result.empty()) {
throw std::runtime_error("Failed to expand file: " + menuFile);
}

menuFile = result.front();
// 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();

// Make the GtkBuilder and check for errors in his parsing
builder_ = Gtk::Builder::create_from_string(fileContent.str());
if (!builder_) {
throw std::runtime_error("Error found in the file " + menuFile);
}

builder_->get_widget("menu", menu_);
if (!menu_) {
throw std::runtime_error("Failed to get 'menu' object from GtkBuilder");
}
auto css_class = id.empty() ? name : id;
menu_->get_style_context()->add_class(css_class);

// 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();
Gtk::MenuItem *item = nullptr;
builder_->get_widget(key.c_str(), item);
if (item == nullptr) {
spdlog::warn("Menu item '{}' not found in builder file", key);
continue;
}
item->get_style_context()->add_class(css_class);
std::string command = it->asString();
item->signal_activate().connect(std::function<void()>([this, command] { handleGtkMenuEvent(command); }));
}

// Check anchors.
auto convert_to_gravity = [] (std::string const& anchor) -> std::optional<Gdk::Gravity>
{
if (anchor == "north-west")
return Gdk::Gravity::GRAVITY_NORTH_WEST;
if (anchor == "north")
return Gdk::Gravity::GRAVITY_NORTH;
if (anchor == "north-east")
return Gdk::Gravity::GRAVITY_NORTH_EAST;
if (anchor == "west")
return Gdk::Gravity::GRAVITY_WEST;
if (anchor == "center")
return Gdk::Gravity::GRAVITY_CENTER;
if (anchor == "east")
return Gdk::Gravity::GRAVITY_EAST;
if (anchor == "south-west")
return Gdk::Gravity::GRAVITY_SOUTH_WEST;
if (anchor == "south")
return Gdk::Gravity::GRAVITY_SOUTH;
if (anchor == "south-east")
return Gdk::Gravity::GRAVITY_SOUTH_EAST;
return {};
};

widget_anchor_ = convert_to_gravity(config_.get("widget-anchor", "none").asString());
menu_anchor_ = convert_to_gravity(config_.get("menu-anchor", "none").asString());
} catch (std::runtime_error& e) {
spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what());
}
}
}

AModule::~AModule() {
Expand All @@ -88,10 +172,6 @@ AModule::~AModule() {
killpg(pid, SIGTERM);
}
}
if (menu_ != nullptr) {
g_object_unref(menu_);
menu_ = nullptr;
}
}

auto AModule::update() -> void {
Expand Down Expand Up @@ -174,8 +254,13 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) {
// Check if the event is the one specified for the "menu" option
if (rec->second == config_["menu"].asString() && menu_ != nullptr) {
// Popup the menu
gtk_widget_show_all(GTK_WIDGET(menu_));
gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast<GdkEvent*>(e));
menu_->show_all();
if (widget_anchor_ && menu_anchor_) {
menu_->popup_at_widget(&event_box_, *widget_anchor_, *menu_anchor_, reinterpret_cast<GdkEvent*>(e));
}
else {
menu_->popup_at_pointer(reinterpret_cast<GdkEvent*>(e));
}
// Manually reset prelight to make sure the module doesn't stay in a hover state
if (auto* module = event_box_.get_child(); module != nullptr) {
module->unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT);
Expand Down Expand Up @@ -281,6 +366,10 @@ bool AModule::handleScroll(GdkEventScroll* e) {
return true;
}

void AModule::handleGtkMenuEvent(std::string command) {
waybar::util::command::forkExec(command, "GtkMenu");
}

bool AModule::tooltipEnabled() const { return isTooltip; }
bool AModule::expandEnabled() const { return isExpand; }

Expand Down