2021-10-02 23:27:11 +00:00
|
|
|
#include "adapters/script_runner.hpp"
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
#include <functional>
|
|
|
|
|
|
|
|
#include "modules/meta/base.hpp"
|
|
|
|
#include "utils/command.hpp"
|
|
|
|
#include "utils/io.hpp"
|
|
|
|
#include "utils/scope.hpp"
|
|
|
|
#include "utils/string.hpp"
|
|
|
|
|
|
|
|
POLYBAR_NS
|
|
|
|
|
2022-04-03 11:11:13 +00:00
|
|
|
script_runner::script_runner(on_update on_update, const string& exec, const string& exec_if, bool tail,
|
2021-10-02 23:27:11 +00:00
|
|
|
interval interval, const vector<pair<string, string>>& env)
|
|
|
|
: m_log(logger::make())
|
|
|
|
, m_on_update(on_update)
|
|
|
|
, m_exec(exec)
|
|
|
|
, m_exec_if(exec_if)
|
|
|
|
, m_tail(tail)
|
|
|
|
, m_interval(interval)
|
|
|
|
, m_env(env) {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if defined condition is met
|
|
|
|
*/
|
|
|
|
bool script_runner::check_condition() const {
|
|
|
|
if (m_exec_if.empty()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto exec_if_cmd = command_util::make_command<output_policy::IGNORED>(m_exec_if);
|
|
|
|
return exec_if_cmd->exec(true) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process mutex wrapped script handler
|
|
|
|
*/
|
|
|
|
script_runner::interval script_runner::process() {
|
|
|
|
if (m_tail) {
|
|
|
|
return run_tail();
|
|
|
|
} else {
|
|
|
|
return run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void script_runner::clear_output() {
|
|
|
|
set_output("");
|
|
|
|
}
|
|
|
|
|
|
|
|
void script_runner::stop() {
|
|
|
|
m_stopping = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool script_runner::is_stopping() const {
|
|
|
|
return m_stopping;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the current output.
|
|
|
|
*
|
|
|
|
* Returns true if the output changed.
|
|
|
|
*/
|
2022-02-23 14:01:28 +00:00
|
|
|
bool script_runner::set_output(string&& new_output) {
|
2022-04-03 11:11:13 +00:00
|
|
|
if (m_data.output != new_output) {
|
|
|
|
m_data.output = std::move(new_output);
|
2021-10-02 23:27:11 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-03 11:11:13 +00:00
|
|
|
/**
|
|
|
|
* Updates the current exit status
|
|
|
|
*
|
|
|
|
* Returns true if the exit status changed.
|
|
|
|
*/
|
|
|
|
bool script_runner::set_exit_status(int new_status) {
|
|
|
|
auto changed = (m_data.exit_status != new_status);
|
|
|
|
m_data.exit_status = new_status;
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
2021-10-02 23:27:11 +00:00
|
|
|
script_runner::interval script_runner::run() {
|
2022-04-03 11:11:13 +00:00
|
|
|
auto exec = string_util::replace_all(m_exec, "%counter%", to_string(++m_data.counter));
|
2021-10-02 23:27:11 +00:00
|
|
|
m_log.info("script_runner: Invoking shell command: \"%s\"", exec);
|
|
|
|
auto cmd = command_util::make_command<output_policy::REDIRECTED>(exec);
|
|
|
|
|
|
|
|
try {
|
2022-03-03 22:38:51 +00:00
|
|
|
cmd->exec(false, m_env);
|
2021-10-02 23:27:11 +00:00
|
|
|
} catch (const exception& err) {
|
|
|
|
m_log.err("script_runner: %s", err.what());
|
|
|
|
throw modules::module_error("Failed to execute command, stopping module...");
|
|
|
|
}
|
|
|
|
|
|
|
|
int fd = cmd->get_stdout(PIPE_READ);
|
|
|
|
assert(fd != -1);
|
2022-03-03 22:38:51 +00:00
|
|
|
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
bool got_output = false;
|
|
|
|
while (!m_stopping && cmd->is_running() && !io_util::poll(fd, POLLHUP, 0)) {
|
|
|
|
/**
|
|
|
|
* For non-tailed scripts, we only use the first line. However, to ensure interruptability when the module shuts
|
|
|
|
* down, we still need to continue polling.
|
|
|
|
*/
|
|
|
|
if (io_util::poll_read(fd, 25) && !got_output) {
|
|
|
|
changed = set_output(cmd->readline());
|
|
|
|
got_output = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_stopping) {
|
|
|
|
cmd->terminate();
|
|
|
|
return 0s;
|
|
|
|
}
|
|
|
|
|
2022-04-03 11:11:13 +00:00
|
|
|
auto exit_status_changed = set_exit_status(cmd->wait());
|
2021-10-02 23:27:11 +00:00
|
|
|
|
2022-04-03 11:11:13 +00:00
|
|
|
if (!changed && m_data.exit_status != 0) {
|
2021-10-02 23:27:11 +00:00
|
|
|
clear_output();
|
|
|
|
}
|
|
|
|
|
2022-04-03 11:11:13 +00:00
|
|
|
if (changed || exit_status_changed) {
|
|
|
|
m_on_update(m_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_data.exit_status == 0) {
|
2021-10-02 23:27:11 +00:00
|
|
|
return m_interval;
|
|
|
|
} else {
|
|
|
|
return std::max(m_interval, interval{1s});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
script_runner::interval script_runner::run_tail() {
|
2022-04-03 11:11:13 +00:00
|
|
|
auto exec = string_util::replace_all(m_exec, "%counter%", to_string(++m_data.counter));
|
2021-10-02 23:27:11 +00:00
|
|
|
m_log.info("script_runner: Invoking shell command: \"%s\"", exec);
|
|
|
|
auto cmd = command_util::make_command<output_policy::REDIRECTED>(exec);
|
|
|
|
|
|
|
|
try {
|
|
|
|
cmd->exec(false, m_env);
|
|
|
|
} catch (const exception& err) {
|
|
|
|
throw modules::module_error("Failed to execute command: " + string(err.what()));
|
|
|
|
}
|
|
|
|
|
2022-04-03 11:11:13 +00:00
|
|
|
auto pid_guard = scope_util::make_exit_handler([this]() {
|
|
|
|
m_data.pid = -1;
|
|
|
|
m_on_update(m_data);
|
|
|
|
});
|
|
|
|
|
|
|
|
m_data.pid = cmd->get_pid();
|
2021-10-02 23:27:11 +00:00
|
|
|
|
|
|
|
int fd = cmd->get_stdout(PIPE_READ);
|
|
|
|
assert(fd != -1);
|
|
|
|
|
|
|
|
while (!m_stopping && cmd->is_running() && !io_util::poll(fd, POLLHUP, 0)) {
|
|
|
|
if (io_util::poll_read(fd, 25)) {
|
2022-04-03 11:11:13 +00:00
|
|
|
auto changed = set_output(cmd->readline());
|
|
|
|
|
|
|
|
if (changed) {
|
|
|
|
m_on_update(m_data);
|
|
|
|
}
|
2021-10-02 23:27:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_stopping) {
|
|
|
|
cmd->terminate();
|
|
|
|
return 0s;
|
|
|
|
}
|
|
|
|
|
2022-02-23 14:01:28 +00:00
|
|
|
auto exit_status = cmd->wait();
|
2021-10-02 23:27:11 +00:00
|
|
|
|
|
|
|
if (exit_status == 0) {
|
|
|
|
return m_interval;
|
|
|
|
} else {
|
|
|
|
return std::max(m_interval, interval{1s});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
POLYBAR_NS_END
|