diff --git a/include/components/controller.hpp b/include/components/controller.hpp index 6978a97f..a3a9ad5b 100644 --- a/include/components/controller.hpp +++ b/include/components/controller.hpp @@ -17,13 +17,12 @@ LEMONBUDDY_NS class controller { public: explicit controller(connection& conn, const logger& logger, const config& config, unique_ptr eventloop, - unique_ptr bar, unique_ptr ipc, inotify_util::watch_t& confwatch) + unique_ptr bar, inotify_util::watch_t& confwatch) : m_connection(conn) , m_log(logger) , m_conf(config) , m_eventloop(forward(eventloop)) , m_bar(forward(bar)) - , m_ipc(forward(ipc)) , m_confwatch(confwatch) {} ~controller(); @@ -85,8 +84,7 @@ namespace { configure_logger(), configure_config(), configure_eventloop(), - configure_bar(), - configure_ipc()); + configure_bar()); // clang-format on } } diff --git a/include/components/ipc.hpp b/include/components/ipc.hpp index a675e028..3516567d 100644 --- a/include/components/ipc.hpp +++ b/include/components/ipc.hpp @@ -5,6 +5,18 @@ LEMONBUDDY_NS +/** + * Message types + */ +struct ipc_command { + static constexpr auto prefix{"cmd:"}; + string payload; +}; +struct ipc_hook { + static constexpr auto prefix{"hook:"}; + string payload; +}; + /** * Component used for inter-process communication. * @@ -14,27 +26,24 @@ LEMONBUDDY_NS */ class ipc { public: - struct message_internal { - static constexpr auto prefix{"app:"}; - }; - struct message_command { - static constexpr auto prefix{"cmd:"}; - }; - struct message_custom { - static constexpr auto prefix{"custom:"}; - }; - explicit ipc(const logger& logger) : m_log(logger) {} ~ipc(); + void attach_callback(callback&& cb); + void attach_callback(callback&& cb); void receive_messages(); protected: - void parse(string payload); + void parse(const string& payload) const; + void delegate(const ipc_command& msg) const; + void delegate(const ipc_hook& msg) const; private: const logger& m_log; + vector> m_command_callbacks; + vector> m_hook_callbacks; + stateflag m_running{false}; string m_fifo; diff --git a/include/modules/ipc.hpp b/include/modules/ipc.hpp new file mode 100644 index 00000000..160c291e --- /dev/null +++ b/include/modules/ipc.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "modules/meta.hpp" +#include "utils/command.hpp" + +LEMONBUDDY_NS + +struct ipc_hook; // fwd + +namespace modules { + /** + * Hook structure that will be fired + * when receiving a message with specified id + */ + struct hook { + string payload; + string command; + }; + + /** + * Module that allow users to configure hooks on + * received ipc messages. The hook will execute the defined + * shell script and the resulting output will be used + * as the module content. + */ + class ipc_module : public static_module { + public: + using static_module::static_module; + + void setup(); + string get_output(); + bool build(builder* builder, string tag) const; + void on_message(const ipc_hook& msg); + + private: + static constexpr auto TAG_OUTPUT = ""; + vector> m_hooks; + string m_output; + + map m_actions; + }; +} + +LEMONBUDDY_NS_END diff --git a/include/utils/command.hpp b/include/utils/command.hpp index 6ba4d4e7..d39aa7c5 100644 --- a/include/utils/command.hpp +++ b/include/utils/command.hpp @@ -80,4 +80,7 @@ namespace command_util { } } +using command = command_util::command; +using command_t = command_util::command_t; + LEMONBUDDY_NS_END diff --git a/src/components/controller.cpp b/src/components/controller.cpp index f1629147..8780f4c1 100644 --- a/src/components/controller.cpp +++ b/src/components/controller.cpp @@ -3,12 +3,6 @@ #include "components/controller.hpp" -#include "components/bar.hpp" -#include "components/config.hpp" -#include "components/eventloop.hpp" -#include "components/ipc.hpp" -#include "components/logger.hpp" -#include "components/signals.hpp" #include "modules/backlight.hpp" #include "modules/battery.hpp" #include "modules/bspwm.hpp" @@ -16,6 +10,7 @@ #include "modules/cpu.hpp" #include "modules/date.hpp" #include "modules/fs.hpp" +#include "modules/ipc.hpp" #include "modules/memory.hpp" #include "modules/menu.hpp" #include "modules/script.hpp" @@ -23,6 +18,13 @@ #include "modules/text.hpp" #include "modules/unsupported.hpp" #include "modules/xbacklight.hpp" + +#include "components/bar.hpp" +#include "components/config.hpp" +#include "components/eventloop.hpp" +#include "components/ipc.hpp" +#include "components/logger.hpp" +#include "components/signals.hpp" #include "utils/process.hpp" #include "utils/string.hpp" @@ -62,12 +64,10 @@ controller::~controller() { m_eventloop.reset(); } -#if DEBUG if (m_ipc) { m_log.info("Deconstructing ipc"); m_ipc.reset(); } -#endif if (m_bar) { m_log.info("Deconstructing bar"); @@ -104,6 +104,13 @@ void controller::bootstrap(bool writeback, bool dump_wmname) { m_log.trace("controller: Query X extension data"); m_connection.query_extensions(); + if (m_conf.get(m_conf.bar_section(), "enable-ipc", false)) { + m_log.trace("controller: Create IPC handler"); + m_ipc = configure_ipc().create(); + } else { + m_log.warn("Inter-process communication support disabled"); + } + // Listen for events on the root window to be able to // break the blocking wait call when cleaning up m_log.trace("controller: Listen for events on the root window"); @@ -144,10 +151,10 @@ bool controller::run() { install_sigmask(); install_confwatch(); -#if DEBUG - // Start listening for ipc messages - m_threads.emplace_back(thread(&ipc::receive_messages, m_ipc.get())); -#endif + // Start ipc receiver if its enabled + if (m_conf.get(m_conf.bar_section(), "enable-ipc", false)) { + m_threads.emplace_back(thread(&ipc::receive_messages, m_ipc.get())); + } // Listen for X events in separate thread if (!m_writeback) { @@ -337,6 +344,10 @@ void controller::bootstrap_modules() { } for (auto& module_name : string_util::split(m_conf.get(bs, confkey, ""), ' ')) { + if (module_name.empty()) { + continue; + } + try { auto type = m_conf.get("module/" + module_name, "type"); module_t module; @@ -375,7 +386,13 @@ void controller::bootstrap_modules() { module.reset(new script_module(bar, m_log, m_conf, module_name)); else if (type == "custom/menu") module.reset(new menu_module(bar, m_log, m_conf, module_name)); - else + else if (type == "custom/ipc") { + if (!m_ipc) + throw application_error("Inter-process communication support needs to be enabled"); + module.reset(new ipc_module(bar, m_log, m_conf, module_name)); + m_ipc->attach_callback( + bind(&ipc_module::on_message, dynamic_cast(module.get()), placeholders::_1)); + } else throw application_error("Unknown module: " + module_name); module->set_update_cb( diff --git a/src/components/ipc.cpp b/src/components/ipc.cpp index fb7d851b..6537222d 100644 --- a/src/components/ipc.cpp +++ b/src/components/ipc.cpp @@ -27,6 +27,20 @@ ipc::~ipc() { } } +/** + * Register listener callback for ipc_command messages + */ +void ipc::attach_callback(callback&& cb) { + m_command_callbacks.emplace_back(cb); +} + +/** + * Register listener callback for ipc_hook messages + */ +void ipc::attach_callback(callback&& cb) { + m_hook_callbacks.emplace_back(cb); +} + /** * Start listening for event messages */ @@ -50,18 +64,36 @@ void ipc::receive_messages() { * Process received message and delegate * valid events to the target modules */ -void ipc::parse(string payload) { +void ipc::parse(const string& payload) const { if (payload.empty()) { return; - } else if (payload.find(message_internal::prefix) == 0) { - m_log.info("Received internal message: (payload=%s)", payload); - } else if (payload.find(message_command::prefix) == 0) { - m_log.info("Received command message: (payload=%s)", payload); - } else if (payload.find(message_custom::prefix) == 0) { - m_log.info("Received custom message: (payload=%s)", payload); + } else if (payload.find(ipc_command::prefix) == 0) { + delegate(ipc_command{payload}); + } else if (payload.find(ipc_hook::prefix) == 0) { + delegate(ipc_hook{payload}); } else { - m_log.info("Received unknown message: (payload=%s)", payload); + m_log.warn("Received unknown ipc message: (payload=%s)", payload); } } +/** + * Send ipc message to attached listeners + */ +void ipc::delegate(const ipc_command& message) const { + if (!m_command_callbacks.empty()) + for (auto&& callback : m_command_callbacks) callback(message); + else + m_log.warn("Unhandled message (payload=%s)", message.payload); +} + +/** + * Send ipc message to attached listeners + */ +void ipc::delegate(const ipc_hook& message) const { + if (!m_hook_callbacks.empty()) + for (auto&& callback : m_hook_callbacks) callback(message); + else + m_log.warn("Unhandled message (payload=%s)", message.payload); +} + LEMONBUDDY_NS_END diff --git a/src/modules/ipc.cpp b/src/modules/ipc.cpp new file mode 100644 index 00000000..548b9637 --- /dev/null +++ b/src/modules/ipc.cpp @@ -0,0 +1,91 @@ +#include "modules/ipc.hpp" +#include "components/ipc.hpp" + +LEMONBUDDY_NS + +namespace modules { + /** + * Load user-defined ipc hooks and + * create formatting tags + */ + void ipc_module::setup() { + size_t index = 0; + + for (auto&& command : m_conf.get_list(name(), "hook")) { + m_hooks.emplace_back(new hook{name() + to_string(++index), command}); + } + + if (m_hooks.empty()) { + throw module_error("No ipc hooks defined"); + } + + m_actions[mousebtn::LEFT] = m_conf.get(name(), "click-left", ""); + m_actions[mousebtn::MIDDLE] = m_conf.get(name(), "click-middle", ""); + m_actions[mousebtn::RIGHT] = m_conf.get(name(), "click-right", ""); + m_actions[mousebtn::SCROLL_UP] = m_conf.get(name(), "scroll-up", ""); + m_actions[mousebtn::SCROLL_DOWN] = m_conf.get(name(), "scroll-down", ""); + + m_formatter->add(DEFAULT_FORMAT, TAG_OUTPUT, {TAG_OUTPUT}); + } + + /** + * Wrap the output with defined mouse actions + */ + string ipc_module::get_output() { + if (!m_actions[mousebtn::LEFT].empty()) + m_builder->cmd(mousebtn::LEFT, m_actions[mousebtn::LEFT]); + if (!m_actions[mousebtn::MIDDLE].empty()) + m_builder->cmd(mousebtn::MIDDLE, m_actions[mousebtn::MIDDLE]); + if (!m_actions[mousebtn::RIGHT].empty()) + m_builder->cmd(mousebtn::RIGHT, m_actions[mousebtn::RIGHT]); + if (!m_actions[mousebtn::SCROLL_UP].empty()) + m_builder->cmd(mousebtn::SCROLL_UP, m_actions[mousebtn::SCROLL_UP]); + if (!m_actions[mousebtn::SCROLL_DOWN].empty()) + m_builder->cmd(mousebtn::SCROLL_DOWN, m_actions[mousebtn::SCROLL_DOWN]); + + m_builder->node(module::get_output()); + + return m_builder->flush(); + } + + /** + * Output content retrieved from hook commands + */ + bool ipc_module::build(builder* builder, string tag) const { + if (tag == TAG_OUTPUT) + builder->node(m_output); + else + return false; + return true; + } + + /** + * Map received message hook to the ones + * configured from the user config and + * execute its command + */ + void ipc_module::on_message(const ipc_hook& message) { + bool match = false; + + for (auto&& hook : m_hooks) { + if (ipc_hook::prefix + hook->payload != message.payload) { + continue; + } + + match = true; + + m_log.info("%s: Found matching hook (%s)", name(), hook->payload); + m_output.clear(); + + auto command = command_util::make_command(hook->command); + command->exec(false); + command->tail([this](string line) { m_output = line; }); + } + + if (match) { + broadcast(); + } + } +} + +LEMONBUDDY_NS_END