1use std::time::Instant;
2
3use super::*;
4use clap::{ArgAction, Parser};
5use log::*;
6use regex::Regex;
7
8#[derive(Debug, Default, Parser)]
9#[command(version, about)]
10pub struct Cli {
35 #[arg(skip)]
36 pub monitors: Vec<Monitor>,
39
40 #[arg(short, long)]
41 pub backend: Option<String>,
43
44 #[arg(id = "capabilities", short, long)]
45 pub needs_capabilities: bool,
47
48 #[arg(short = 'n', long)]
49 pub dry_run: bool,
51
52 #[arg(short, long, action = ArgAction::Count)]
53 pub verbose: u8,
55
56 #[arg(skip)]
57 set_index: Option<usize>,
58
59 pub args: Vec<String>,
63}
64
65impl Cli {
66 pub fn new() -> Self {
68 Cli {
69 monitors: Monitor::enumerate(),
70 ..Default::default()
71 }
72 }
73
74 pub fn init_logger(&self) {
77 simplelog::CombinedLogger::init(vec![simplelog::TermLogger::new(
78 match self.verbose {
79 0 => simplelog::LevelFilter::Info,
80 1 => simplelog::LevelFilter::Debug,
81 _ => simplelog::LevelFilter::Trace
82 },
83 simplelog::Config::default(),
84 simplelog::TerminalMode::Mixed,
85 simplelog::ColorChoice::Auto,
86 )])
87 .unwrap();
88 }
89
90 fn apply_filters(&mut self) -> anyhow::Result<()> {
91 if let Some(backend_str) = &self.backend {
92 self.monitors
93 .retain(|monitor| monitor.contains_backend(backend_str));
94 }
95 Ok(())
96 }
97
98 fn for_each<C>(&mut self, name: &str, mut callback: C) -> anyhow::Result<()>
99 where
100 C: FnMut(usize, &mut Monitor) -> anyhow::Result<()>,
101 {
102 if let Ok(index) = name.parse::<usize>() {
103 let monitor = &mut self.monitors[index];
104 if self.needs_capabilities {
105 let _ = monitor.update_capabilities();
107 }
108 return callback(index, monitor);
109 }
110
111 let mut has_match = false;
112 for (index, monitor) in (&mut self.monitors).into_iter().enumerate() {
113 if self.needs_capabilities {
114 let _ = monitor.update_capabilities();
116 }
117 if name.len() > 0 && !monitor.contains(name) {
118 continue;
119 }
120 has_match = true;
121 callback(index, monitor)?;
122 }
123 if has_match {
124 return Ok(());
125 }
126
127 anyhow::bail!("No display monitors found for \"{name}\".");
128 }
129
130 fn compute_toggle_set_index(
131 current_input_source: InputSourceRaw,
132 input_sources: &[InputSourceRaw],
133 ) -> usize {
134 input_sources
135 .iter()
136 .position(|v| *v == current_input_source)
137 .map_or(0, |i| i + 1)
139 }
140
141 fn toggle(&mut self, name: &str, values: &[&str]) -> anyhow::Result<()> {
142 let mut input_sources: Vec<InputSourceRaw> = vec![];
143 for value in values {
144 input_sources.push(InputSource::raw_from_str(value)?);
145 }
146 let mut set_index = self.set_index;
147 let result = self.for_each(name, |_, monitor: &mut Monitor| {
148 if set_index.is_none() {
149 let current_input_source = monitor.current_input_source()?;
150 set_index = Some(Self::compute_toggle_set_index(
151 current_input_source,
152 &input_sources,
153 ));
154 debug!(
155 "Set = {index} (because InputSource({monitor}) is {input_source})",
156 index = set_index.unwrap(),
157 input_source = InputSource::str_from_raw(current_input_source)
158 );
159 }
160 let used_index = set_index.unwrap().min(input_sources.len() - 1);
161 let input_source = input_sources[used_index];
162 monitor.set_current_input_source(input_source)
163 });
164 self.set_index = set_index;
165 result
166 }
167
168 fn set(&mut self, name: &str, value: &str) -> anyhow::Result<()> {
169 let toggle_values: Vec<&str> = value.split(',').collect();
170 if toggle_values.len() > 1 {
171 return self.toggle(name, &toggle_values);
172 }
173 let input_source = InputSource::raw_from_str(value)?;
174 self.for_each(name, |_, monitor: &mut Monitor| {
175 monitor.set_current_input_source(input_source)
176 })
177 }
178
179 fn print_list(&mut self, name: &str) -> anyhow::Result<()> {
180 self.for_each(name, |index, monitor| {
181 println!("{index}: {}", monitor.to_long_string());
182 trace!("{:?}", monitor);
183 Ok(())
184 })
185 }
186
187 fn sleep_all_if_needed(&mut self) {
188 let start_time = Instant::now();
189 for monitor in &mut self.monitors {
190 monitor.sleep_if_needed();
191 }
192 debug!("sleep_all() elapsed: {:?}", start_time.elapsed());
193 }
194
195 const RE_SET_PATTERN: &str = r"^([^=]+)=(.+)$";
196
197 pub fn run(&mut self) -> anyhow::Result<()> {
199 let start_time = Instant::now();
200 Monitor::set_dry_run(self.dry_run);
201 self.apply_filters()?;
202
203 let re_set = Regex::new(Self::RE_SET_PATTERN).unwrap();
204 let mut has_valid_args = false;
205 let args = self.args.clone();
206 for arg in args {
207 if let Some(captures) = re_set.captures(&arg) {
208 self.set(&captures[1], &captures[2])?;
209 has_valid_args = true;
210 continue;
211 }
212
213 self.print_list(&arg)?;
214 has_valid_args = true;
215 }
216 if !has_valid_args {
217 self.print_list("")?;
218 }
219 self.sleep_all_if_needed();
220 debug!("Elapsed: {:?}", start_time.elapsed());
221 Ok(())
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use std::vec;
228
229 use super::*;
230
231 #[test]
232 fn cli_parse() {
233 let mut cli = Cli::parse_from([""]);
234 assert_eq!(cli.verbose, 0);
235 assert_eq!(cli.args.len(), 0);
236
237 cli = Cli::parse_from(["", "abc", "def"]);
238 assert_eq!(cli.verbose, 0);
239 assert_eq!(cli.args, ["abc", "def"]);
240
241 cli = Cli::parse_from(["", "-v", "abc", "def"]);
242 assert_eq!(cli.verbose, 1);
243 assert_eq!(cli.args, ["abc", "def"]);
244
245 cli = Cli::parse_from(["", "-vv", "abc", "def"]);
246 assert_eq!(cli.verbose, 2);
247 assert_eq!(cli.args, ["abc", "def"]);
248 }
249
250 #[test]
251 fn cli_parse_option_after_positional() {
252 let cli = Cli::parse_from(["", "abc", "def", "-v"]);
253 assert_eq!(cli.verbose, 1);
254 assert_eq!(cli.args, ["abc", "def"]);
255 }
256
257 #[test]
258 fn cli_parse_positional_with_hyphen() {
259 let cli = Cli::parse_from(["", "--", "-abc", "-def"]);
260 assert_eq!(cli.args, ["-abc", "-def"]);
261 }
262
263 fn matches<'a>(re: &'a Regex, input: &'a str) -> Vec<&'a str> {
264 re.captures(input)
265 .unwrap()
266 .iter()
267 .skip(1)
268 .map(|m| m.unwrap().as_str())
269 .collect()
270 }
271
272 #[test]
273 fn re_set() {
274 let re_set = Regex::new(Cli::RE_SET_PATTERN).unwrap();
275 assert_eq!(re_set.is_match("a"), false);
276 assert_eq!(re_set.is_match("a="), false);
277 assert_eq!(re_set.is_match("=a"), false);
278 assert_eq!(matches(&re_set, "a=b"), vec!["a", "b"]);
279 assert_eq!(matches(&re_set, "1=23"), vec!["1", "23"]);
280 assert_eq!(matches(&re_set, "12=34"), vec!["12", "34"]);
281 assert_eq!(matches(&re_set, "12=3,4"), vec!["12", "3,4"]);
282 }
283
284 #[test]
285 fn compute_toggle_set_index() {
286 assert_eq!(Cli::compute_toggle_set_index(1, &[1, 4, 9]), 1);
287 assert_eq!(Cli::compute_toggle_set_index(4, &[1, 4, 9]), 2);
288 assert_eq!(Cli::compute_toggle_set_index(9, &[1, 4, 9]), 3);
289 assert_eq!(Cli::compute_toggle_set_index(0, &[1, 4, 9]), 0);
291 assert_eq!(Cli::compute_toggle_set_index(2, &[1, 4, 9]), 0);
292 assert_eq!(Cli::compute_toggle_set_index(10, &[1, 4, 9]), 0);
293 }
294}