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