#pragma once #include #include #include #include "common.hpp" #include "components/builder.hpp" #include "components/config.hpp" #include "components/logger.hpp" #include "utils/inotify.hpp" #include "utils/string.hpp" #include "utils/threading.hpp" LEMONBUDDY_NS #define DEFAULT_FORMAT "format" #define DEFINE_MODULE(name, type) struct name : public type #define CONST_MOD(name) static_cast(*this) #define CAST_MOD(name) static_cast(this) namespace modules { using namespace drawtypes; DEFINE_ERROR(module_error); DEFINE_CHILD_ERROR(undefined_format, module_error); DEFINE_CHILD_ERROR(undefined_format_tag, module_error); // class definition : module_format {{{ struct module_format { string value; vector tags; string fg; string bg; string ul; string ol; int spacing; int padding; int margin; int offset; string decorate(builder* builder, string output) { if (offset != 0) builder->offset(offset); if (margin > 0) builder->space(margin); if (!bg.empty()) builder->background(bg); if (!fg.empty()) builder->color(fg); if (!ul.empty()) builder->underline(ul); if (!ol.empty()) builder->overline(ol); if (padding > 0) builder->space(padding); builder->append(output); if (padding > 0) builder->space(padding); if (!ol.empty()) builder->overline_close(); if (!ul.empty()) builder->underline_close(); if (!fg.empty()) builder->color_close(); if (!bg.empty()) builder->background_close(); if (margin > 0) builder->space(margin); return builder->flush(); } }; // }}} // class definition : module_formatter {{{ class module_formatter { public: explicit module_formatter(const config& conf, string modname) : m_conf(conf), m_modname(modname) {} void add(string name, string fallback, vector&& tags, vector&& whitelist = {}) { auto format = make_unique(); format->value = m_conf.get(m_modname, name, fallback); format->fg = m_conf.get(m_modname, name + "-foreground", ""); format->bg = m_conf.get(m_modname, name + "-background", ""); format->ul = m_conf.get(m_modname, name + "-underline", ""); format->ol = m_conf.get(m_modname, name + "-overline", ""); format->spacing = m_conf.get(m_modname, name + "-spacing", DEFAULT_SPACING); format->padding = m_conf.get(m_modname, name + "-padding", 0); format->margin = m_conf.get(m_modname, name + "-margin", 0); format->offset = m_conf.get(m_modname, name + "-offset", 0); format->tags.swap(tags); for (auto&& tag : string_util::split(format->value, ' ')) { if (tag[0] != '<' || tag[tag.length() - 1] != '>') continue; if (find(format->tags.begin(), format->tags.end(), tag) != format->tags.end()) continue; if (find(whitelist.begin(), whitelist.end(), tag) != whitelist.end()) continue; throw undefined_format_tag("[" + m_modname + "] Undefined \"" + name + "\" tag: " + tag); } m_formats.insert(make_pair(name, move(format))); } shared_ptr get(string format_name) { auto format = m_formats.find(format_name); if (format == m_formats.end()) throw undefined_format("Format \"" + format_name + "\" has not been added"); return format->second; } bool has(string tag, string format_name) { auto format = m_formats.find(format_name); if (format == m_formats.end()) throw undefined_format(format_name.c_str()); return format->second->value.find(tag) != string::npos; } bool has(string tag) { for (auto&& format : m_formats) if (format.second->value.find(tag) != string::npos) return true; return false; } protected: const config& m_conf; string m_modname; map> m_formats; }; // }}} // class definition : module_interface {{{ struct module_interface { public: virtual ~module_interface() {} virtual string name() const = 0; virtual bool running() const = 0; virtual void setup() = 0; virtual void start() = 0; virtual void stop() = 0; virtual void halt(string error_message) = 0; virtual string contents() = 0; virtual bool handle_event(string cmd) = 0; virtual bool receive_events() const = 0; virtual void set_update_cb(callback<>&& cb) = 0; virtual void set_stop_cb(callback<>&& cb) = 0; }; // }}} // class definition : module {{{ template class module : public module_interface { public: module(const bar_settings bar, const logger& logger, const config& config, string name) : m_bar(bar) , m_log(logger) , m_conf(config) , m_name("module/" + name) , m_builder(make_unique(bar)) , m_formatter(make_unique(m_conf, m_name)) {} ~module() noexcept { m_log.trace("%s: Deconstructing", name()); for (auto&& thread_ : m_threads) { if (thread_.joinable()) { thread_.join(); } } } void set_update_cb(callback<>&& cb) { m_update_callback = forward(cb); } void set_stop_cb(callback<>&& cb) { m_stop_callback = forward(cb); } string name() const { return m_name; } bool running() const { return m_enabled.load(std::memory_order_relaxed); } void setup() { m_log.trace("%s: Setup", m_name); try { CAST_MOD(Impl)->setup(); } catch (const std::exception& err) { m_log.err("%s: Setup failed", m_name); halt(err.what()); } } void stop() { if (!running()) { return; } m_log.info("%s: Stopping", name()); m_enabled.store(false, std::memory_order_relaxed); wakeup(); std::lock_guard guard(m_lock); { CAST_MOD(Impl)->teardown(); if (m_mainthread.joinable()) { m_mainthread.join(); } } if (m_stop_callback) { m_stop_callback(); } } void halt(string error_message) { m_log.err("%s: %s", name(), error_message); m_log.warn("Stopping '%s'...", name()); stop(); } void teardown() {} string contents() { return m_cache; } bool handle_event(string cmd) { return CAST_MOD(Impl)->handle_event(cmd); } bool receive_events() const { return false; } protected: void broadcast() { if (!running()) { return; } m_cache = CAST_MOD(Impl)->get_output(); if (m_update_callback) m_update_callback(); else m_log.warn("%s: No handler, ignoring broadcast...", name()); } void idle() { CAST_MOD(Impl)->sleep(25ms); } void sleep(chrono::duration sleep_duration) { std::unique_lock lck(m_sleeplock); m_sleephandler.wait_for(lck, sleep_duration); } void wakeup() { m_log.trace("%s: Release sleep lock", name()); m_sleephandler.notify_all(); } string get_format() const { return DEFAULT_FORMAT; } string get_output() { if (!running()) { m_log.trace("%s: Module is disabled", name()); return ""; } auto format_name = CONST_MOD(Impl).get_format(); auto format = m_formatter->get(format_name); int i = 0; bool tag_built = true; for (auto tag : string_util::split(format->value, ' ')) { bool is_blankspace = tag.empty(); if (tag[0] == '<' && tag[tag.length() - 1] == '>') { if (i > 0) m_builder->space(format->spacing); if (!(tag_built = CONST_MOD(Impl).build(m_builder.get(), tag)) && i > 0) m_builder->remove_trailing_space(format->spacing); if (tag_built) i++; } else if (is_blankspace && tag_built) { m_builder->node(" "); } else if (!is_blankspace) { m_builder->node(tag); } } return format->decorate(m_builder.get(), m_builder->flush()); } protected: callback<> m_update_callback; callback<> m_stop_callback; threading_util::spin_lock m_lock; const bar_settings m_bar; const logger& m_log; const config& m_conf; std::mutex m_sleeplock; std::condition_variable m_sleephandler; string m_name; unique_ptr m_builder; unique_ptr m_formatter; vector m_threads; thread m_mainthread; private: stateflag m_enabled{true}; string m_cache; }; // }}} // class definition : static_module {{{ template class static_module : public module { public: using module::module; void start() { CAST_MOD(Impl)->broadcast(); } bool build(builder*, string) const { return true; } }; // }}} // class definition : timer_module {{{ using interval_t = chrono::duration; template class timer_module : public module { public: using module::module; void start() { CAST_MOD(Impl)->m_mainthread = thread(&timer_module::runner, this); } protected: interval_t m_interval = 1s; void runner() { try { while (CONST_MOD(Impl).running()) { std::lock_guard guard(this->m_lock); { if (CAST_MOD(Impl)->update()) CAST_MOD(Impl)->broadcast(); } CAST_MOD(Impl)->sleep(m_interval); } } catch (const module_error& err) { CAST_MOD(Impl)->halt(err.what()); } catch (const std::exception& err) { CAST_MOD(Impl)->halt(err.what()); } } }; // }}} // class definition : event_module {{{ template class event_module : public module { public: using module::module; void start() { CAST_MOD(Impl)->m_mainthread = thread(&event_module::runner, this); } protected: void runner() { try { // Send initial broadcast to warmup cache if (CONST_MOD(Impl).running()) { CAST_MOD(Impl)->update(); CAST_MOD(Impl)->broadcast(); } while (CONST_MOD(Impl).running()) { CAST_MOD(Impl)->idle(); if (!CONST_MOD(Impl).running()) break; std::lock_guard guard(this->m_lock); { if (!CAST_MOD(Impl)->has_event()) continue; if (!CONST_MOD(Impl).running()) break; if (!CAST_MOD(Impl)->update()) continue; CAST_MOD(Impl)->broadcast(); } } } catch (const module_error& err) { CAST_MOD(Impl)->halt(err.what()); } catch (const std::exception& err) { CAST_MOD(Impl)->halt(err.what()); } } }; // }}} // class definition : inotify_module {{{ template class inotify_module : public module { public: using module::module; void start() { CAST_MOD(Impl)->m_mainthread = thread(&inotify_module::runner, this); } protected: void runner() { try { // Send initial broadcast to warmup cache if (CONST_MOD(Impl).running()) { CAST_MOD(Impl)->on_event(nullptr); CAST_MOD(Impl)->broadcast(); } while (CONST_MOD(Impl).running()) { CAST_MOD(Impl)->poll_events(); } } catch (const module_error& err) { CAST_MOD(Impl)->halt(err.what()); } catch (const std::exception& err) { CAST_MOD(Impl)->halt(err.what()); } } void watch(string path, int mask = IN_ALL_EVENTS) { this->m_log.trace("%s: Attach inotify at %s", CONST_MOD(Impl).name(), path); m_watchlist.insert(make_pair(path, mask)); } void idle() { CAST_MOD(Impl)->sleep(200ms); } void poll_events() { vector watches; try { for (auto&& w : m_watchlist) { watches.emplace_back(inotify_util::make_watch(w.first)); watches.back()->attach(w.second); } } catch (const system_error& e) { watches.clear(); this->m_log.err( "%s: Error while creating inotify watch (what: %s)", CONST_MOD(Impl).name(), e.what()); CAST_MOD(Impl)->sleep(0.1s); return; } while (CONST_MOD(Impl).running()) { std::unique_lock guard(this->m_lock); { for (auto&& w : watches) { this->m_log.trace_x("%s: Poll inotify watch %s", CONST_MOD(Impl).name(), w->path()); if (w->poll(1000 / watches.size())) { auto event = w->get_event(); for (auto&& w : watches) { try { w->remove(); } catch (const system_error&) { } } if (CAST_MOD(Impl)->on_event(event.get())) CAST_MOD(Impl)->broadcast(); CAST_MOD(Impl)->idle(); return; } if (!CONST_MOD(Impl).running()) break; } } guard.unlock(); CAST_MOD(Impl)->idle(); } } private: map m_watchlist; }; // }}} } LEMONBUDDY_NS_END