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