diff --git a/CHANGELOG.md b/CHANGELOG.md index b07d6614..47f88c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([`#1441`](https://github.com/polybar/polybar/issues/1441)) - `internal/xkeyboard`: Allow configuring icons using variant ([`#2414`](https://github.com/polybar/polybar/issues/2414)) +- `custom/ipc`: Add `hook`, `next`, `prev`, `reset` actions to the ipc module + ([`#2464`](https://github.com/polybar/polybar/issues/2464)) ### Changed - Polybar now also reads `config.ini` when searching for config files. diff --git a/doc/user/actions.rst b/doc/user/actions.rst index cc6145d9..95ae2a78 100644 --- a/doc/user/actions.rst +++ b/doc/user/actions.rst @@ -261,9 +261,16 @@ custom/menu custom/ipc ^^^^^^^^^^ -:``send``: *(Has Data)* Replace the contents of the module with the data passed in this action. +.. versionadded:: 3.6.0 + +:``send``: *(Has Data)* Replace the contents of the module with the data passed in this action. +:``hook``: *(Has Data)* Trigger the given hook. + + The data is the 0-based index of the hook to trigger. +:``next``: Switches to the next hook and wrap around when the last hook was displayed. +:``prev``: Switches to the previous hook and wrap around when the first hook was displayed. +:``reset``: Reset the module to its startup state: either empty or according to the ``initial`` setting. - .. versionadded:: 3.6.0 Deprecated Action Names ----------------------- diff --git a/include/modules/ipc.hpp b/include/modules/ipc.hpp index 5100736d..977847c8 100644 --- a/include/modules/ipc.hpp +++ b/include/modules/ipc.hpp @@ -35,9 +35,17 @@ namespace modules { static constexpr auto TYPE = "custom/ipc"; static constexpr auto EVENT_SEND = "send"; + static constexpr auto EVENT_HOOK = "hook"; + static constexpr auto EVENT_NEXT = "next"; + static constexpr auto EVENT_PREV = "prev"; + static constexpr auto EVENT_RESET = "reset"; protected: void action_send(const string& data); + void action_hook(const string& data); + void action_next(); + void action_prev(); + void action_reset(); private: static constexpr const char* TAG_OUTPUT{""}; @@ -45,6 +53,8 @@ namespace modules { map m_actions; string m_output; size_t m_initial; + size_t m_current_hook; + void exec_hook(); }; } // namespace modules diff --git a/src/modules/ipc.cpp b/src/modules/ipc.cpp index 0e2a0995..76bcaa6c 100644 --- a/src/modules/ipc.cpp +++ b/src/modules/ipc.cpp @@ -14,8 +14,12 @@ namespace modules { * Load user-defined ipc hooks and * create formatting tags */ - ipc_module::ipc_module(const bar_settings& bar, string name_) : module(bar, move(name_)) { + ipc_module::ipc_module(const bar_settings& bar, string name_) : module(bar, move(name_)), m_initial(0) { 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; @@ -25,8 +29,16 @@ namespace modules { m_log.info("%s: Loaded %d hooks", name(), m_hooks.size()); - if ((m_initial = m_conf.get(name(), "initial", 0_z)) && m_initial > m_hooks.size()) { - throw module_error("Initial hook out of bounds (defined: " + to_string(m_hooks.size()) + ")"); + m_initial = m_conf.get(name(), "initial", 0_z); + if (m_conf.has(name(), "initial") && m_initial != 0) { + if (m_initial <= m_hooks.size()) { + m_current_hook = m_initial - 1; + } else { + throw module_error("Initial hook out of bounds '" + to_string(m_initial) + "'. Defined hooks goes from 1 to " + + to_string(m_hooks.size()) + ")"); + } + } else { + m_current_hook = m_hooks.size(); } // clang-format off @@ -62,11 +74,9 @@ namespace modules { broadcast(); }); - if (m_initial) { + if (m_initial != 0) { // TODO do this in a thread. - auto command = command_util::make_command(m_hooks.at(m_initial - 1)->command); - command->exec(false); - command->tail([this](string line) { m_output = line; }); + exec_hook(); } } @@ -107,25 +117,14 @@ namespace modules { * execute its command */ void ipc_module::on_message(const string& message) { - for (auto&& hook : m_hooks) { - if (hook->payload != message) { - continue; + 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); + m_current_hook = i; + this->exec_hook(); + break; } - - m_log.info("%s: Found matching hook (%s)", name(), hook->payload); - - try { - // Clear the output in case the command produces no output - m_output.clear(); - auto command = command_util::make_command(hook->command); - command->exec(false); - command->tail([this](string line) { m_output = line; }); - } catch (const exception& err) { - m_log.err("%s: Failed to execute hook command (err: %s)", err.what()); - m_output.clear(); - } - - broadcast(); } } @@ -133,6 +132,69 @@ namespace modules { 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 goes from 0 to %zu.", name(), + data, m_hooks.size() - 1); + } else { + m_current_hook = hook; + this->exec_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() { + // this is the case where initial is not defined on first 'next' + if (m_current_hook == m_hooks.size()) { + m_current_hook = 0; + } else { + m_current_hook = (m_current_hook + 1) % m_hooks.size(); + } + this->exec_hook(); + } + + void ipc_module::action_prev() { + if (m_current_hook == 0) { + m_current_hook = m_hooks.size(); + } + m_current_hook--; + this->exec_hook(); + } + + void ipc_module::action_reset() { + if (m_initial != 0) { + m_current_hook = m_initial - 1; + this->exec_hook(); + } else { + m_current_hook = m_hooks.size(); + m_output.clear(); + broadcast(); + } + } + + void ipc_module::exec_hook() { + // Clear the output in case the command produces no output + m_output.clear(); + + try { + auto command = command_util::make_command(m_hooks[m_current_hook]->command); + command->exec(false); + command->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