feat(ipc): hook, prev, next, reset module actions (#2528)

* WIP ipc actions

* feat(ipc): Add hook, prev, next and reset actions

Closes: #2464

* ipc: format code

* ipc: fix comparison

* Apply suggestions from code review

Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com>

* ipc: make index 0-based

* ipc: add 0-based indexing breaking change to Changelog

* ipc: restore 1-based index for  and  message

* ipc: fix initial=0 throwing an error

Co-authored-by: Martin Terneborg <martinterneborg@protonmail.com>
Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com>
This commit is contained in:
dvermd 2021-10-10 20:22:24 +02:00 committed by GitHub
parent 22014c70c4
commit 231af35354
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 27 deletions

View File

@ -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)) ([`#1441`](https://github.com/polybar/polybar/issues/1441))
- `internal/xkeyboard`: Allow configuring icons using variant - `internal/xkeyboard`: Allow configuring icons using variant
([`#2414`](https://github.com/polybar/polybar/issues/2414)) ([`#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 ### Changed
- Polybar now also reads `config.ini` when searching for config files. - Polybar now also reads `config.ini` when searching for config files.

View File

@ -261,9 +261,16 @@ custom/menu
custom/ipc 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 Deprecated Action Names
----------------------- -----------------------

View File

@ -35,9 +35,17 @@ namespace modules {
static constexpr auto TYPE = "custom/ipc"; static constexpr auto TYPE = "custom/ipc";
static constexpr auto EVENT_SEND = "send"; 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: protected:
void action_send(const string& data); void action_send(const string& data);
void action_hook(const string& data);
void action_next();
void action_prev();
void action_reset();
private: private:
static constexpr const char* TAG_OUTPUT{"<output>"}; static constexpr const char* TAG_OUTPUT{"<output>"};
@ -45,6 +53,8 @@ namespace modules {
map<mousebtn, string> m_actions; map<mousebtn, string> m_actions;
string m_output; string m_output;
size_t m_initial; size_t m_initial;
size_t m_current_hook;
void exec_hook();
}; };
} // namespace modules } // namespace modules

View File

@ -14,8 +14,12 @@ namespace modules {
* Load user-defined ipc hooks and * Load user-defined ipc hooks and
* create formatting tags * create formatting tags
*/ */
ipc_module::ipc_module(const bar_settings& bar, string name_) : module<ipc_module>(bar, move(name_)) { ipc_module::ipc_module(const bar_settings& bar, string name_) : module<ipc_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_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; size_t index = 0;
@ -25,8 +29,16 @@ namespace modules {
m_log.info("%s: Loaded %d hooks", name(), m_hooks.size()); 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()) { m_initial = m_conf.get(name(), "initial", 0_z);
throw module_error("Initial hook out of bounds (defined: " + to_string(m_hooks.size()) + ")"); 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 // clang-format off
@ -62,11 +74,9 @@ namespace modules {
broadcast(); broadcast();
}); });
if (m_initial) { if (m_initial != 0) {
// TODO do this in a thread. // TODO do this in a thread.
auto command = command_util::make_command<output_policy::REDIRECTED>(m_hooks.at(m_initial - 1)->command); exec_hook();
command->exec(false);
command->tail([this](string line) { m_output = line; });
} }
} }
@ -107,25 +117,14 @@ namespace modules {
* execute its command * execute its command
*/ */
void ipc_module::on_message(const string& message) { void ipc_module::on_message(const string& message) {
for (auto&& hook : m_hooks) { for (size_t i = 0; i < m_hooks.size(); i++) {
if (hook->payload != message) { const auto& hook = m_hooks[i];
continue; if (hook->payload == message) {
}
m_log.info("%s: Found matching hook (%s)", name(), hook->payload); m_log.info("%s: Found matching hook (%s)", name(), hook->payload);
m_current_hook = i;
try { this->exec_hook();
// Clear the output in case the command produces no output break;
m_output.clear();
auto command = command_util::make_command<output_policy::REDIRECTED>(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; m_output = data;
broadcast(); 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<output_policy::REDIRECTED>(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 } // namespace modules
POLYBAR_NS_END POLYBAR_NS_END