1use std::{
2 collections::BTreeSet,
3 ops::{Deref, DerefMut},
4};
5
6use color_eyre::Result;
7use log::{info, trace};
8use swayipc::{Connection, Mode};
9
10use crate::cfg::{Cfgs, Config, DesiredOutput};
11
12#[derive(Debug, Clone, Default)]
13pub struct Output {
14 name: String,
15 model: String,
16 position: (i32, i32),
17 resolution: (u32, u32),
18 scale: f64,
19 enabled: bool,
20 modes: Vec<Mode>,
21}
22
23impl Output {
24 fn new(
26 name: String,
27 model: String,
28 position: (i32, i32),
29 resolution: (u32, u32),
30 scale: f64,
31 enabled: bool,
32 modes: Vec<Mode>,
33 ) -> Self {
34 Self {
35 name,
36 model,
37 position,
38 resolution,
39 scale,
40 enabled,
41 modes,
42 }
43 }
44
45 pub fn name(&self) -> &str {
46 self.name.as_ref()
47 }
48
49 pub fn model(&self) -> &str {
50 self.model.as_ref()
51 }
52
53 pub fn enable(self) -> Self {
54 Self {
55 enabled: true,
56 ..self
57 }
58 }
59
60 pub fn with_scale(self, scale: f64) -> Self {
61 Self { scale, ..self }
62 }
63
64 pub fn disable(self) -> Self {
65 Self {
66 enabled: false,
67 ..self
68 }
69 }
70
71 fn display(&self, verbose: bool, name_pad: usize) -> String {
72 let pad = name_pad.saturating_sub(self.name.len()) + 1;
74 let modes = self.modes.iter().fold(String::new(), |mut acc, m| {
75 let refresh = m.refresh as f32 / 1000.0;
76 acc = acc + ", " + &format!("{}x{} ({} Hz)", m.width, m.height, refresh);
77 acc
78 });
79
80 let details = if verbose {
81 ", modes: ".to_string() + &modes
82 } else {
83 "".to_string()
84 };
85 let resolution = format!("{}x{}", self.resolution.0, self.resolution.1);
86 format!(
87 "{}:{:0pad$}position: {:4}/{}, resolution: {:>9}, scale: {:1.1}, model: {}{}",
88 self.name,
89 " ",
90 self.position.0,
91 self.position.1,
92 resolution,
93 self.scale,
94 self.model.as_str(),
95 details
96 )
97 }
98
99 pub fn best_mode(&'_ self) -> Option<&'_ Mode> {
100 self.modes
101 .iter()
102 .max_by_key(|mode| mode.width * mode.height)
103 }
104
105 pub fn enabled(&self) -> bool {
106 self.enabled
107 }
108
109 pub fn scale(&self) -> f64 {
110 self.scale
111 }
112}
113
114impl std::fmt::Display for Output {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 let fmt = self.display(false, 0);
117 write!(f, "{}", fmt)
118 }
119}
120
121impl PartialOrd for Output {
122 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
123 Some(self.cmp(other))
124 }
125}
126
127impl Ord for Output {
128 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
129 self.model.cmp(&other.model)
130 }
131}
132
133impl PartialEq for Output {
134 fn eq(&self, other: &Self) -> bool {
135 self.model == other.model
136 }
137}
138
139impl Eq for Output {}
140
141#[derive(Debug, PartialEq)]
142pub struct Outputs(BTreeSet<Output>);
143
144impl Outputs {
145 pub fn list() -> Result<Self> {
146 let raw_outputs = Connection::new()?.get_outputs()?;
147
148 let outputs = raw_outputs
149 .iter()
150 .map(|o| {
151 let resolution = o
152 .current_mode
153 .map(|m| (m.width as u32, m.height as u32))
154 .unwrap_or((0, 0));
155 let model = o.make.clone() + " " + &o.model;
156 Output::new(
157 o.name.clone(),
158 model,
159 (o.rect.x, o.rect.y),
160 resolution,
161 o.scale.unwrap_or(1.0),
162 o.active,
163 o.modes.clone(),
164 )
165 })
166 .collect();
167
168 let outputs = Self(outputs);
169 Ok(outputs)
170 }
171
172 fn longest_name(&self) -> usize {
173 self.0
174 .iter()
175 .fold(0, |len, output| len.max(output.name.len()))
176 }
177
178 pub fn set_models(&self, setup: &[DesiredOutput]) -> Result<()> {
179 let disable: Vec<Output> = self
180 .0
181 .iter()
182 .filter_map(|o| {
183 if !setup.iter().any(|d| d.name == o.model) {
184 Some(o.clone().disable())
185 } else {
186 None
187 }
188 })
189 .collect();
190
191 let new_setup: Result<Vec<Output>> = setup
192 .iter()
193 .map(|desired| {
194 self.0
195 .iter()
196 .find(|o| o.model == desired.name)
197 .ok_or(color_eyre::eyre::eyre!(
198 "Display '{}' is not connected",
199 desired.name
200 ))
201 .map(|o| o.clone().enable().with_scale(desired.scale.unwrap_or(1.0)))
202 })
203 .collect();
204 let new_setup = new_setup?;
205 self.set(new_setup.iter())?;
206 self.set(disable.iter())
207 }
208
209 pub fn set_by_name(&self, setup: &[String]) -> Result<()> {
210 let outputs: Vec<_> = self
211 .0
212 .iter()
213 .map(|o| {
214 if setup.iter().any(|desired| **desired == o.name) {
215 o.clone().enable()
216 } else {
217 o.clone().disable()
218 }
219 })
220 .collect();
221 self.set(outputs.iter())
222 }
223
224 fn set<'a>(&self, new_setup: impl Iterator<Item = &'a Output>) -> Result<()> {
225 let mut cmd_con = swayipc::Connection::new()?;
226 let mut last_x = 0;
227 for o in new_setup {
228 let payload = if o.enabled {
229 let desired_mode = o.best_mode();
230 let (width, height) = desired_mode.map(|m| (m.width, m.height)).unwrap_or((0, 0));
231 let payload = format!(
232 "output {} enable position {} 0 resolution {}x{} scale {}",
233 o.name(),
234 last_x,
235 width,
236 height,
237 o.scale
238 );
239 let scaled_w = width as f64 / o.scale;
240 last_x += scaled_w as i32;
241 payload
242 } else {
243 format!("output {} disable", o.name())
244 };
245 cmd_con.run_command(payload)?;
247 }
248
249 Ok(())
250 }
251
252 pub fn activate_config(&self, cfgs: &Cfgs) -> Result<()> {
253 let connected_names: BTreeSet<String> =
254 self.iter().map(|o| o.model().to_string()).collect();
255 trace!("connected displays: {:?}", connected_names);
256 let mut valid_cfgs: Vec<(&String, &Config)> = Vec::new();
258 for (k, v) in cfgs.iter() {
259 let names: BTreeSet<_> = v.outputs.iter().map(|d| d.name.clone()).collect();
260 if names.is_subset(&connected_names) {
261 valid_cfgs.push((k, v));
262 }
263 }
264
265 valid_cfgs.sort_by(|a, b| {
267 let pa = a.1.priority.unwrap_or(0);
268 let pb = b.1.priority.unwrap_or(0);
269 pa.cmp(&pb).then(a.1.outputs.len().cmp(&b.1.outputs.len()))
270 });
271
272 trace!("relevant cfgs: {:?}", valid_cfgs);
273 if let Some((name, best_cfg)) = valid_cfgs.last() {
274 info!(
275 "activating config '{}' (priority: {})",
276 name,
277 best_cfg.priority.unwrap_or(0)
278 );
279 self.set_models(&best_cfg.outputs)?;
280 }
281 Ok(())
282 }
283}
284
285impl std::fmt::Display for Outputs {
286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287 let verbose = f.alternate();
288 let name_pad = self.longest_name();
289 self.0.iter().try_fold((), |_, output| {
290 writeln!(f, "{}", output.display(verbose, name_pad))
291 })
292 }
293}
294
295impl Deref for Outputs {
296 type Target = BTreeSet<Output>;
297
298 fn deref(&self) -> &Self::Target {
299 &self.0
300 }
301}
302
303impl DerefMut for Outputs {
304 fn deref_mut(&mut self) -> &mut Self::Target {
305 &mut self.0
306 }
307}
308
309impl<'a> FromIterator<&'a Output> for Outputs {
310 fn from_iter<T: IntoIterator<Item = &'a Output>>(iter: T) -> Self {
311 let mut vec: BTreeSet<Output> = BTreeSet::new();
312 for n in iter {
313 vec.insert(n.clone());
314 }
315
316 Self(vec)
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn padding() {
326 let output = Output::new(
327 "1234".to_owned(),
328 "model".to_owned(),
329 (0, 0),
330 (0, 0),
331 1.0,
332 true,
333 Vec::new(),
334 );
335 let display = output.display(false, 8);
336 assert_eq!(&display[..10], "1234: ");
337 }
338}