From b7a6fa8c91e3529648cd796ad5e97a62dea9e341 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jul 2026 12:49:01 +0200 Subject: [PATCH] fix(wlr/taskbar): dispatch built-in click actions via doAction, not the shell wlr/taskbar reads on-click* config values (close, minimize, maximize, fullscreen, minimize-raise, activate) directly as internal actions in Task::handle_clicked, but never adopted the eventActionMap_/doAction mechanism. As a result AModule::handleUserEvent additionally forkExec-ed the same value as a shell command, e.g. on-click-middle: "close" ran the action and then failed with "sh: line 1: close: command not found". Register the taskbar built-in action names in eventActionMap_ so they are recognized as module actions, and skip the shell forkExec in handleUserEvent when the configured value is a recognized module action. Non-action values are still run as user shell commands. Fixes #3284. --- include/AModule.hpp | 8 +++++++- src/AModule.cpp | 16 ++++++++++++---- src/modules/wlr/taskbar.cpp | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/include/AModule.hpp b/include/AModule.hpp index e99bec9c..3fea52f6 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -98,6 +98,13 @@ class AModule : public IModule { bool disable_on_sleep_{false}; GObject* menu_ = nullptr; + // Maps a configured event name (e.g. "on-click-middle") to a built-in module + // action name. Populated from the `actions` config section, and by modules + // that interpret on-click* config values as internal actions (e.g. + // wlr/taskbar). Entries here are dispatched through doAction() instead of + // being run as shell commands. + std::map eventActionMap_; + private: bool handleUserEvent(GdkEventButton* const& ev); const bool isTooltip; @@ -106,7 +113,6 @@ class AModule : public IModule { gdouble distance_scrolled_y_; gdouble distance_scrolled_x_; sigc::connection cursor_timeout_conn_; - std::map eventActionMap_; static const inline std::map, std::string> eventMap_{ {std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), "on-click"}, {std::make_pair(1, GdkEventType::GDK_BUTTON_RELEASE), "on-click-release"}, diff --git a/src/AModule.cpp b/src/AModule.cpp index b53f6cee..83bafbcf 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -118,7 +118,7 @@ auto AModule::update() -> void { if (config_["on-update"].isString()) { pid_children_.push_back(util::command::forkExec(config_["on-update"].asString())); } - signal_updated.emit(this); + signal_updated.emit(this); } // Get mapping between event name and module action name // Then call overridden doAction in order to call appropriate module action @@ -222,10 +222,18 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) { } // Second call user scripts if (!format.empty()) { - if (config_[format].isString()) - format = config_[format].asString(); - else + // If the configured value for this event is a recognized built-in module + // action (registered in eventActionMap_), it has already been dispatched + // via doAction() above / handled by the module itself. Don't additionally + // run it as a shell command (issue #3284). Any other value is still treated + // as a user shell command. + const auto actionIt = eventActionMap_.find(format); + const bool isModuleAction = actionIt != eventActionMap_.cend() && config_[format].isString() && + config_[format].asString() == actionIt->second; + if (isModuleAction || !config_[format].isString()) format.clear(); + else + format = config_[format].asString(); } if (!format.empty()) { const int width = gdk_window_get_width(e->window); diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 62371c15..4517aec4 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -696,6 +696,21 @@ Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Valu box_.get_style_context()->add_class("empty"); event_box_.add(box_); + // wlr/taskbar interprets on-click* config values as built-in actions, handled + // per-task in Task::handle_clicked. Register the recognized action names so + // AModule dispatches them via doAction() instead of also running them as shell + // commands (issue #3284). Values that are not built-in actions are left alone + // and still run as user shell commands. + const auto is_builtin_action = [](const std::string& v) { + return v == "activate" || v == "minimize" || v == "minimize-raise" || v == "maximize" || + v == "fullscreen" || v == "close"; + }; + for (const auto* event : {"on-click", "on-click-middle", "on-click-right"}) { + if (config_[event].isString() && is_builtin_action(config_[event].asString())) { + eventActionMap_.insert({event, config_[event].asString()}); + } + } + // Make task buttons distribute evenly across the available width. if (config_["homogeneous"].isBool() && config_["homogeneous"].asBool()) { box_.set_homogeneous(true);