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