1 #include "modules/script.hpp"
2 #include "drawtypes/label.hpp"
3 #include "modules/meta/base.inl"
4 
5 POLYBAR_NS
6 
7 namespace modules {
8   template class module<script_module>;
9 
10   /**
11    * Construct script module by loading configuration values
12    * and setting up formatting objects
13    */
script_module(const bar_settings & bar,string name_)14   script_module::script_module(const bar_settings& bar, string name_)
15       : module<script_module>(bar, move(name_)), m_handler([&]() -> function<chrono::duration<double>()> {
16 
17         m_tail = m_conf.get(name(), "tail", false);
18         // Handler for continuous tail commands {{{
19 
20         if (m_tail) {
21           return [&] {
22             if (!m_command || !m_command->is_running()) {
23               string exec{string_util::replace_all(m_exec, "%counter%", to_string(++m_counter))};
24               m_log.info("%s: Invoking shell command: \"%s\"", name(), exec);
25               m_command = command_util::make_command<output_policy::REDIRECTED>(exec);
26 
27               try {
28                 m_command->exec(false);
29               } catch (const exception& err) {
30                 m_log.err("%s: %s", name(), err.what());
31                 throw module_error("Failed to execute command, stopping module...");
32               }
33             }
34 
35             int fd = m_command->get_stdout(PIPE_READ);
36             while (!m_stopping && fd != -1 && m_command->is_running() && !io_util::poll(fd, POLLHUP, 0)) {
37               if (!io_util::poll_read(fd, 25)) {
38                 continue;
39               } else if ((m_output = m_command->readline()) != m_prev) {
40                 m_prev = m_output;
41                 broadcast();
42               }
43             }
44 
45             if (m_stopping) {
46               return chrono::duration<double>{0};
47             } else if (m_command && !m_command->is_running()) {
48               return std::max(m_command->get_exit_status() == 0 ? m_interval : 1s, m_interval);
49             } else {
50               return m_interval;
51             }
52           };
53         }
54 
55         // }}}
56         // Handler for basic shell commands {{{
57 
58         return [&] {
59           try {
60             auto exec = string_util::replace_all(m_exec, "%counter%", to_string(++m_counter));
61             m_log.info("%s: Invoking shell command: \"%s\"", name(), exec);
62             m_command = command_util::make_command<output_policy::REDIRECTED>(exec);
63             m_command->exec(true);
64           } catch (const exception& err) {
65             m_log.err("%s: %s", name(), err.what());
66             throw module_error("Failed to execute command, stopping module...");
67           }
68 
69           int fd = m_command->get_stdout(PIPE_READ);
70           if (fd != -1 && io_util::poll_read(fd) && (m_output = m_command->readline()) != m_prev) {
71             broadcast();
72             m_prev = m_output;
73           } else if (m_command->get_exit_status() != 0) {
74             m_output.clear();
75             m_prev.clear();
76             broadcast();
77           }
78 
79           return std::max(m_command->get_exit_status() == 0 ? m_interval : 1s, m_interval);
80         };
81 
82         // }}}
83       }()) {
84     // Load configuration values
85     m_exec = m_conf.get(name(), "exec", m_exec);
86     m_exec_if = m_conf.get(name(), "exec-if", m_exec_if);
87     m_interval = m_conf.get<decltype(m_interval)>(name(), "interval", 5s);
88 
89     // Load configured click handlers
90     m_actions[mousebtn::LEFT] = m_conf.get(name(), "click-left", ""s);
91     m_actions[mousebtn::MIDDLE] = m_conf.get(name(), "click-middle", ""s);
92     m_actions[mousebtn::RIGHT] = m_conf.get(name(), "click-right", ""s);
93     m_actions[mousebtn::DOUBLE_LEFT] = m_conf.get(name(), "double-click-left", ""s);
94     m_actions[mousebtn::DOUBLE_MIDDLE] = m_conf.get(name(), "double-click-middle", ""s);
95     m_actions[mousebtn::DOUBLE_RIGHT] = m_conf.get(name(), "double-click-right", ""s);
96     m_actions[mousebtn::SCROLL_UP] = m_conf.get(name(), "scroll-up", ""s);
97     m_actions[mousebtn::SCROLL_DOWN] = m_conf.get(name(), "scroll-down", ""s);
98 
99     // Setup formatting
100     m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
101     if (m_formatter->has(TAG_LABEL)) {
102       m_label = load_optional_label(m_conf, name(), "label", "%output%");
103     }
104   }
105 
106   /**
107    * Start the module worker
108    */
start()109   void script_module::start() {
110     m_mainthread = thread([&] {
111       try {
112         while (running() && !m_stopping) {
113           if (check_condition()) {
114             sleep(process(m_handler));
115           } else if (m_interval > 1s) {
116             sleep(m_interval);
117           } else {
118             sleep(1s);
119           }
120         }
121       } catch (const exception& err) {
122         halt(err.what());
123       }
124     });
125   }
126 
127   /**
128    * Stop the module worker by terminating any running commands
129    */
stop()130   void script_module::stop() {
131     m_stopping = true;
132     wakeup();
133 
134     std::lock_guard<decltype(m_handler)> guard(m_handler);
135 
136     m_command.reset();
137     module::stop();
138   }
139 
140   /**
141    * Check if defined condition is met
142    */
check_condition()143   bool script_module::check_condition() {
144     if (m_exec_if.empty()) {
145       return true;
146     } else if (command_util::make_command<output_policy::IGNORED>(m_exec_if)->exec(true) == 0) {
147       return true;
148     } else if (!m_output.empty()) {
149       broadcast();
150       m_output.clear();
151       m_prev.clear();
152     }
153     return false;
154   }
155 
156   /**
157    * Process mutex wrapped script handler
158    */
process(const decltype(m_handler)& handler) const159   chrono::duration<double> script_module::process(const decltype(m_handler) & handler) const {
160     std::lock_guard<decltype(handler)> guard(handler);
161     return handler();
162   }
163 
164   /**
165    * Generate module output
166    */
get_output()167   string script_module::get_output() {
168     if (m_output.empty()) {
169       return "";
170     }
171 
172     if (m_label) {
173       m_label->reset_tokens();
174       m_label->replace_token("%output%", m_output);
175     }
176 
177     string cnt{to_string(m_counter)};
178     string output{module::get_output()};
179 
180     for (auto btn : {mousebtn::LEFT, mousebtn::MIDDLE, mousebtn::RIGHT,
181                      mousebtn::DOUBLE_LEFT, mousebtn::DOUBLE_MIDDLE,
182                      mousebtn::DOUBLE_RIGHT, mousebtn::SCROLL_UP,
183                      mousebtn::SCROLL_DOWN}) {
184 
185       auto action = m_actions[btn];
186 
187       if (!action.empty()) {
188         auto action_replaced = string_util::replace_all(action, "%counter%", cnt);
189 
190         /*
191          * The pid token is only for tailed commands.
192          * If the command is not specified or running, replacement is unnecessary as well
193          */
194         if(m_tail && m_command && m_command->is_running()) {
195           action_replaced = string_util::replace_all(action_replaced, "%pid%", to_string(m_command->get_pid()));
196         }
197         m_builder->action(btn, action_replaced);
198       }
199     }
200 
201     m_builder->append(output);
202 
203     return m_builder->flush();
204   }
205 
206   /**
207    * Output format tags
208    */
build(builder * builder,const string & tag) const209   bool script_module::build(builder* builder, const string& tag) const {
210     if (tag == TAG_LABEL) {
211       builder->node(m_label);
212     } else {
213       return false;
214     }
215 
216     return true;
217   }
218 }
219 
220 POLYBAR_NS_END
221