From e3065d0e6ccce3e76b4bc54181ece6a20bd029c5 Mon Sep 17 00:00:00 2001 From: Michael Carlberg Date: Mon, 14 Nov 2016 09:21:18 +0100 Subject: [PATCH] feat(ipc): New ipc module Add a new module that allow users to configure hooks on received ipc messages. The hook will execute the defined shell script and the output of the script will be used as the module content. Ref #84 --- include/components/controller.hpp | 6 +- include/components/ipc.hpp | 31 +++++++---- include/modules/ipc.hpp | 44 +++++++++++++++ include/utils/command.hpp | 3 + src/components/controller.cpp | 43 ++++++++++----- src/components/ipc.cpp | 48 +++++++++++++--- src/modules/ipc.cpp | 91 +++++++++++++++++++++++++++++++ 7 files changed, 230 insertions(+), 36 deletions(-) create mode 100644 include/modules/ipc.hpp create mode 100644 src/modules/ipc.cpp 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