#include "modules/ipc.hpp" #include #include "modules/meta/base.inl" POLYBAR_NS namespace modules { template class module; /** * Load user-defined ipc hooks and * create formatting tags */ ipc_module::ipc_module(const bar_settings& bar, string name_) : module(bar, move(name_)) { m_router->register_action_with_data(EVENT_SEND, [this](const std::string& data) { action_send(data); }); m_router->register_action_with_data(EVENT_HOOK, [this](const std::string& data) { action_hook(data); }); m_router->register_action(EVENT_NEXT, [this]() { action_next(); }); m_router->register_action(EVENT_PREV, [this]() { action_prev(); }); m_router->register_action(EVENT_RESET, [this]() { action_reset(); }); size_t index = 0; for (auto&& command : m_conf.get_list(name(), "hook", {})) { m_hooks.emplace_back(std::make_unique(hook{name() + to_string(++index), command})); } m_log.info("%s: Loaded %d hooks", name(), m_hooks.size()); // Negative initial values should always be -1 m_initial = std::max(-1, m_conf.get(name(), "initial", 0) - 1); if (has_initial()) { if ((size_t)m_initial >= m_hooks.size()) { throw module_error("Initial hook out of bounds: '" + to_string(m_initial + 1) + "'. Defined hooks go from 1 to " + to_string(m_hooks.size()) + ")"); } m_current_hook = m_initial; } else { m_current_hook = -1; } // clang-format off m_actions.emplace(make_pair(mousebtn::LEFT, m_conf.get(name(), "click-left", ""s))); m_actions.emplace(make_pair(mousebtn::MIDDLE, m_conf.get(name(), "click-middle", ""s))); m_actions.emplace(make_pair(mousebtn::RIGHT, m_conf.get(name(), "click-right", ""s))); m_actions.emplace(make_pair(mousebtn::SCROLL_UP, m_conf.get(name(), "scroll-up", ""s))); m_actions.emplace(make_pair(mousebtn::SCROLL_DOWN, m_conf.get(name(), "scroll-down", ""s))); m_actions.emplace(make_pair(mousebtn::DOUBLE_LEFT, m_conf.get(name(), "double-click-left", ""s))); m_actions.emplace(make_pair(mousebtn::DOUBLE_MIDDLE, m_conf.get(name(), "double-click-middle", ""s))); m_actions.emplace(make_pair(mousebtn::DOUBLE_RIGHT, m_conf.get(name(), "double-click-right", ""s))); // clang-format on const auto pid_token = [](const string& s) { return string_util::replace_all(s, "%pid%", to_string(getpid())); }; for (auto& action : m_actions) { action.second = pid_token(action.second); } for (auto& hook : m_hooks) { hook->command = pid_token(hook->command); } m_formatter->add(DEFAULT_FORMAT, TAG_OUTPUT, {TAG_OUTPUT}); for (size_t i = 0; i < m_hooks.size(); i++) { string format_i = "format-" + to_string(i); m_formatter->add_optional(format_i, {TAG_OUTPUT}); } } /** * Start module and run first defined hook if configured to */ void ipc_module::start() { this->module::start(); m_mainthread = thread([&] { m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id())); update(); broadcast(); }); } void ipc_module::update() { if (has_hook()) { exec_hook(); } } /** * Wrap the output with defined mouse actions */ string ipc_module::get_output() { // Get the module output early so that // the format prefix/suffix also gets wrapper // with the cmd handlers string output{module::get_output()}; if (output.empty()) { return ""; } for (auto&& action : m_actions) { if (!action.second.empty()) { m_builder->action(action.first, action.second); } } m_builder->node(output); return m_builder->flush(); } string ipc_module::get_format() const { if (m_current_hook != -1 && (size_t)m_current_hook < m_hooks.size()) { string format_i = "format-" + to_string(m_current_hook); if (m_formatter->has_format(format_i)) { return format_i; } else { return DEFAULT_FORMAT; } } return DEFAULT_FORMAT; } /** * Output content retrieved from hook commands */ bool ipc_module::build(builder* builder, const string& tag) const { if (tag == TAG_OUTPUT) { builder->node(m_output); return true; } else { return false; } } /** * Map received message hook to the ones * configured from the user config and * execute its command * * This code path is deprecated, all messages to ipc modules should go through actions. */ void ipc_module::on_message(const string& message) { for (size_t i = 0; i < m_hooks.size(); i++) { const auto& hook = m_hooks[i]; if (hook->payload == message) { m_log.info("%s: Found matching hook (%s)", name(), hook->payload); set_hook(i); break; } } } void ipc_module::action_send(const string& data) { m_output = data; broadcast(); } void ipc_module::action_hook(const string& data) { try { int hook = std::stoi(data); if (hook < 0 || (size_t)hook >= m_hooks.size()) { m_log.err("%s: Hook action received with an out of bounds hook '%s'. Defined hooks go from 0 to %zu.", name(), data, m_hooks.size() - 1); } else { set_hook(hook); } } catch (const std::invalid_argument& err) { m_log.err( "%s: Hook action received '%s' cannot be converted to a valid hook index. Defined hooks goes from 0 to " "%zu.", name(), data, m_hooks.size() - 1); } } void ipc_module::action_next() { hook_offset(1); } void ipc_module::action_prev() { hook_offset(-1); } void ipc_module::action_reset() { if (has_initial()) { set_hook(m_initial); } else { m_current_hook = -1; m_output.clear(); broadcast(); } } /** * Changes the current hook by the given offset. * * Also deals with the case where the there is no active hook, in which case a positive offset starts from -1 and a * negative from 0. This ensures that 'next' executes the first and 'prev' the last hook if no hook is set. */ void ipc_module::hook_offset(int offset) { int start_hook; if (has_hook()) { start_hook = m_current_hook; } else { if (offset > 0) { start_hook = -1; } else { start_hook = 0; } } set_hook((start_hook + offset + m_hooks.size()) % m_hooks.size()); } bool ipc_module::has_initial() const { return m_initial >= 0; } bool ipc_module::has_hook() const { return m_current_hook >= 0; } void ipc_module::set_hook(int h) { assert(h >= 0 && (size_t)h < m_hooks.size()); m_current_hook = h; exec_hook(); } void ipc_module::exec_hook() { // Clear the output in case the command produces no output m_output.clear(); try { command cmd(m_log, m_hooks[m_current_hook]->command); cmd.exec(false); cmd.tail([this](string line) { m_output = line; }); } catch (const exception& err) { m_log.err("%s: Failed to execute hook command (err: %s)", name(), err.what()); m_output.clear(); } broadcast(); } } // namespace modules POLYBAR_NS_END