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
This commit is contained in:
parent
04fac96d78
commit
e3065d0e6c
@ -17,13 +17,12 @@ LEMONBUDDY_NS
|
||||
class controller {
|
||||
public:
|
||||
explicit controller(connection& conn, const logger& logger, const config& config, unique_ptr<eventloop> eventloop,
|
||||
unique_ptr<bar> bar, unique_ptr<ipc> ipc, inotify_util::watch_t& confwatch)
|
||||
unique_ptr<bar> bar, inotify_util::watch_t& confwatch)
|
||||
: m_connection(conn)
|
||||
, m_log(logger)
|
||||
, m_conf(config)
|
||||
, m_eventloop(forward<decltype(eventloop)>(eventloop))
|
||||
, m_bar(forward<decltype(bar)>(bar))
|
||||
, m_ipc(forward<decltype(ipc)>(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
|
||||
}
|
||||
}
|
||||
|
@ -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<const ipc_command&>&& cb);
|
||||
void attach_callback(callback<const ipc_hook&>&& 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<callback<const ipc_command&>> m_command_callbacks;
|
||||
vector<callback<const ipc_hook&>> m_hook_callbacks;
|
||||
|
||||
stateflag m_running{false};
|
||||
|
||||
string m_fifo;
|
||||
|
44
include/modules/ipc.hpp
Normal file
44
include/modules/ipc.hpp
Normal file
@ -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<ipc_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 = "<output>";
|
||||
vector<unique_ptr<hook>> m_hooks;
|
||||
string m_output;
|
||||
|
||||
map<mousebtn, string> m_actions;
|
||||
};
|
||||
}
|
||||
|
||||
LEMONBUDDY_NS_END
|
@ -80,4 +80,7 @@ namespace command_util {
|
||||
}
|
||||
}
|
||||
|
||||
using command = command_util::command;
|
||||
using command_t = command_util::command_t;
|
||||
|
||||
LEMONBUDDY_NS_END
|
||||
|
@ -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<bool>(m_conf.bar_section(), "enable-ipc", false)) {
|
||||
m_log.trace("controller: Create IPC handler");
|
||||
m_ipc = configure_ipc().create<decltype(m_ipc)>();
|
||||
} 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<bool>(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<string>(bs, confkey, ""), ' ')) {
|
||||
if (module_name.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
auto type = m_conf.get<string>("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<ipc_module*>(module.get()), placeholders::_1));
|
||||
} else
|
||||
throw application_error("Unknown module: " + module_name);
|
||||
|
||||
module->set_update_cb(
|
||||
|
@ -27,6 +27,20 @@ ipc::~ipc() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register listener callback for ipc_command messages
|
||||
*/
|
||||
void ipc::attach_callback(callback<const ipc_command&>&& cb) {
|
||||
m_command_callbacks.emplace_back(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register listener callback for ipc_hook messages
|
||||
*/
|
||||
void ipc::attach_callback(callback<const ipc_hook&>&& 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
|
||||
|
91
src/modules/ipc.cpp
Normal file
91
src/modules/ipc.cpp
Normal file
@ -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<string>(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<string>(name(), "click-left", "");
|
||||
m_actions[mousebtn::MIDDLE] = m_conf.get<string>(name(), "click-middle", "");
|
||||
m_actions[mousebtn::RIGHT] = m_conf.get<string>(name(), "click-right", "");
|
||||
m_actions[mousebtn::SCROLL_UP] = m_conf.get<string>(name(), "scroll-up", "");
|
||||
m_actions[mousebtn::SCROLL_DOWN] = m_conf.get<string>(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
|
Loading…
Reference in New Issue
Block a user