#include "components/controller.hpp" #include "common.hpp" #include "components/bar.hpp" #include "components/config.hpp" #include "components/eventloop.hpp" #include "components/ipc.hpp" #include "components/logger.hpp" #include "events/signal.hpp" #include "modules/meta/factory.hpp" #include "utils/factory.hpp" #include "utils/process.hpp" #include "utils/string.hpp" #include "x11/xutils.hpp" POLYBAR_NS using namespace modules; controller::controller(connection& conn, signal_emitter& emitter, const logger& logger, const config& config, unique_ptr<eventloop> eventloop, unique_ptr<bar> bar, unique_ptr<ipc> ipc, watch_t confwatch, bool writeback) : m_connection(conn) , m_sig(emitter) , m_log(logger) , m_conf(config) , m_eventloop(move(eventloop)) , m_bar(move(bar)) , m_ipc(move(ipc)) , m_confwatch(move(confwatch)) , m_writeback(writeback) {} controller::~controller() { if (m_eventloop) { m_log.info("Deconstructing eventloop"); m_eventloop.reset(); } if (m_command) { m_log.info("Terminating running shell command"); m_command.reset(); } if (m_bar) { m_log.info("Deconstructing bar"); m_bar.reset(); } if (m_ipc) { m_log.info("Deconstructing ipc"); m_ipc.reset(); } if (!m_writeback) { m_log.info("Interrupting X event loop"); m_connection.send_dummy_event(m_connection.root()); } m_log.info("Joining active threads"); for (auto&& thread_ : m_threads) { if (thread_.joinable()) { thread_.join(); } } m_log.info("Waiting for spawned processes"); while (process_util::notify_childprocess()) { ; } m_sig.detach(this); m_connection.flush(); } void controller::setup() { string bs{m_conf.bar_section()}; m_log.trace("controller: Setup user-defined modules"); for (int i = 0; i < 3; i++) { alignment align = static_cast<alignment>(i + 1); string confkey; if (align == alignment::LEFT) { confkey = "modules-left"; } else if (align == alignment::CENTER) { confkey = "modules-center"; } else if (align == alignment::RIGHT) { confkey = "modules-right"; } 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"); if (type == "custom/ipc" && !m_ipc) { throw application_error("Inter-process messaging needs to be enabled"); } unique_ptr<module_interface> module{make_module(move(type), m_bar->settings(), m_log, m_conf, module_name)}; module->set_update_cb([&] { if (m_eventloop && m_running) { m_sig.emit(enqueue_update{eventloop_t::make_update_evt(false)}); } }); module->set_stop_cb([&] { if (m_eventloop && m_running) { m_sig.emit(enqueue_check{eventloop::make_check_evt()}); } }); module->setup(); m_eventloop->add_module(align, move(module)); } catch (const std::runtime_error& err) { m_log.err("Disabling module \"%s\" (reason: %s)", module_name, err.what()); } } } if (!m_eventloop->module_count()) { throw application_error("No modules created"); } } bool controller::run() { assert(!m_connection.connection_has_error()); m_sig.attach(this); m_log.info("Starting application"); m_running = true; if (m_confwatch && !m_writeback) { m_threads.emplace_back(thread(&controller::wait_for_configwatch, this)); } if (m_ipc) { m_threads.emplace_back(thread(&ipc::receive_messages, m_ipc.get())); } if (!m_writeback) { m_threads.emplace_back(thread(&controller::wait_for_xevent, this)); } if (m_eventloop) { m_threads.emplace_back(thread(&controller::wait_for_eventloop, this)); } m_log.trace("controller: Wait for signal"); m_waiting = true; sigemptyset(&m_waitmask); sigaddset(&m_waitmask, SIGINT); sigaddset(&m_waitmask, SIGQUIT); sigaddset(&m_waitmask, SIGTERM); sigaddset(&m_waitmask, SIGUSR1); sigaddset(&m_waitmask, SIGALRM); int caught_signal = 0; sigwait(&m_waitmask, &caught_signal); m_running = false; m_waiting = false; if (caught_signal == SIGUSR1) { m_reload = true; } m_log.warn("Termination signal received, shutting down..."); m_log.trace("controller: Caught signal %d", caught_signal); // Signal the eventloop, in case it's still running m_eventloop->enqueue(eventloop::make_quit_evt(false)); if (m_eventloop) { m_log.trace("controller: Stopping event loop"); m_eventloop->stop(); } if (!m_writeback && m_confwatch) { m_log.trace("controller: Removing config watch"); m_confwatch->remove(true); } return !m_running && !m_reload; } const bar_settings controller::opts() const { return m_bar->settings(); } void controller::wait_for_configwatch() { try { m_log.trace("controller: Attach config watch"); m_confwatch->attach(IN_MODIFY); m_log.trace("controller: Wait for config file inotify event"); if (m_confwatch->await_match() && m_running) { m_log.info("Configuration file changed"); kill(getpid(), SIGUSR1); } } catch (const system_error& err) { m_log.err(err.what()); m_log.trace("controller: Reset config watch"); m_confwatch.reset(); } } void controller::wait_for_xevent() { m_log.trace("controller: Listen for X events"); m_connection.flush(); while (m_running) { try { auto evt = m_connection.wait_for_event(); if (evt && m_running) { m_connection.dispatch_event(evt); } } catch (xpp::connection_error& err) { m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(err.code())); } catch (const exception& err) { m_log.err("Error in X event loop: %s", err.what()); } if (m_connection.connection_has_error()) { break; } } if (m_running) { kill(getpid(), SIGTERM); } } void controller::wait_for_eventloop() { m_eventloop->start(); this_thread::sleep_for(std::chrono::milliseconds{250}); if (m_running) { m_log.trace("controller: eventloop ended, raising SIGALRM"); kill(getpid(), SIGALRM); } } bool controller::on(const sig_ev::process_update& evt) { if (!m_bar) { return false; } const bar_settings& bar{m_bar->settings()}; string contents; string separator{bar.separator}; string padding_left(bar.padding.left, ' '); string padding_right(bar.padding.right, ' '); auto margin_left = bar.module_margin.left; auto margin_right = bar.module_margin.right; for (const auto& block : m_eventloop->modules()) { string block_contents; bool is_left = false; bool is_center = false; bool is_right = false; if (block.first == alignment::LEFT) { is_left = true; } else if (block.first == alignment::CENTER) { is_center = true; } else if (block.first == alignment::RIGHT) { is_right = true; } for (const auto& module : block.second) { auto module_contents = module->contents(); if (module_contents.empty()) { continue; } if (!block_contents.empty() && !(is_right && module == block.second.back())) { block_contents += string(margin_right, ' '); } if (!block_contents.empty() && !separator.empty()) { block_contents += separator; } if (!(is_left && module == block.second.front())) { block_contents += string(margin_left, ' '); } block_contents += module->contents(); } if (block_contents.empty()) { continue; } else if (is_left) { contents += "%{l}"; contents += padding_left; } else if (is_center) { contents += "%{c}"; } else if (is_right) { contents += "%{r}"; block_contents += padding_right; } // Strip unnecessary reset tags block_contents = string_util::replace_all(block_contents, "T-}%{T", "T"); block_contents = string_util::replace_all(block_contents, "B-}%{B#", "B#"); block_contents = string_util::replace_all(block_contents, "F-}%{F#", "F#"); block_contents = string_util::replace_all(block_contents, "U-}%{U#", "U#"); block_contents = string_util::replace_all(block_contents, "u-}%{u#", "u#"); block_contents = string_util::replace_all(block_contents, "o-}%{o#", "o#"); // Join consecutive tags contents += string_util::replace_all(block_contents, "}%{", " "); } try { if (!m_writeback) { m_bar->parse(contents, evt()); } else { std::cout << contents << std::endl; } } catch (const exception& err) { m_log.err("Failed to update bar contents (reason: %s)", err.what()); } return true; } bool controller::on(const sig_ev::process_input& evt) { try { string input{(*evt()).data}; if (m_command) { m_log.warn("Terminating previous shell command"); m_command->terminate(); } m_log.info("Executing shell command: %s", input); m_command = command_util::make_command(input); m_command->exec(); m_command.reset(); } catch (const application_error& err) { m_log.err("controller: Error while forwarding input to shell -> %s", err.what()); } return true; } bool controller::on(const sig_ev::process_quit&) { kill(getpid(), SIGUSR1); return false; } bool controller::on(const sig_ui::button_press& evt) { if (!m_eventloop) { return false; } string input{*evt()}; if (input.length() >= sizeof(eventloop::input_data)) { m_log.warn("Ignoring input event (size)"); } else if (!m_sig.emit(enqueue_input{eventloop::make_input_data(move(input))})) { m_log.trace_x("controller: Dispatcher busy"); } return true; } bool controller::on(const sig_ipc::process_action& evt) { ipc_action a{*evt()}; string action{a.payload}; action.erase(0, strlen(ipc_action::prefix)); if (action.size() >= sizeof(eventloop::input_data)) { m_log.warn("Ignoring input event (size)"); } else if (action.empty()) { m_log.err("Cannot enqueue empty ipc action"); } else { m_log.info("Enqueuing ipc action: %s", action); m_eventloop->enqueue(eventloop::make_input_evt()); } return true; } bool controller::on(const sig_ipc::process_command& evt) { ipc_command c{*evt()}; string command{c.payload}; command.erase(0, strlen(ipc_command::prefix)); if (command.empty()) { return false; } if (command == "quit") { m_eventloop->enqueue(eventloop::make_quit_evt(false)); } else if (command == "restart") { m_eventloop->enqueue(eventloop::make_quit_evt(true)); } else { m_log.warn("\"%s\" is not a valid ipc command", command); } return true; } bool controller::on(const sig_ipc::process_hook& evt) { const ipc_hook hook{*evt()}; for (const auto& block : m_eventloop->modules()) { for (const auto& module : block.second) { auto ipc = dynamic_cast<ipc_module*>(module.get()); if (ipc != nullptr) { ipc->on_message(hook); } } } return true; } POLYBAR_NS_END