liboswo/
outputs.rs

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::DesiredOutput, Cfgs};
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    /// Creates a new [`Output`].
25    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 enabled(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 disabled(self) -> Self {
65        Self {
66            enabled: false,
67            ..self
68        }
69    }
70
71    fn display(&self, verbose: bool, name_pad: usize) -> String {
72        // we want at least one whitespace, hence + 1
73        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
106impl std::fmt::Display for Output {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        let fmt = self.display(false, 0);
109        write!(f, "{}", fmt)
110    }
111}
112
113impl PartialOrd for Output {
114    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
115        Some(self.cmp(other))
116    }
117}
118
119impl Ord for Output {
120    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
121        self.model.cmp(&other.model)
122    }
123}
124
125impl PartialEq for Output {
126    fn eq(&self, other: &Self) -> bool {
127        self.model == other.model
128    }
129}
130
131impl Eq for Output {}
132
133#[derive(Debug, PartialEq)]
134pub struct Outputs(BTreeSet<Output>);
135
136impl Outputs {
137    pub fn list() -> Result<Self> {
138        let raw_outputs = Connection::new()?.get_outputs()?;
139
140        let outputs = raw_outputs
141            .iter()
142            .map(|o| {
143                let resolution = o
144                    .current_mode
145                    .map(|m| (m.width as u32, m.height as u32))
146                    .unwrap_or((0, 0));
147                let model = o.make.clone() + " " + &o.model;
148                Output::new(
149                    o.name.clone(),
150                    model,
151                    (o.rect.x, o.rect.y),
152                    resolution,
153                    o.scale.unwrap_or(1.0),
154                    o.active,
155                    o.modes.clone(),
156                )
157            })
158            .collect();
159
160        let outputs = Self(outputs);
161        Ok(outputs)
162    }
163
164    fn longest_name(&self) -> usize {
165        self.0
166            .iter()
167            .fold(0, |len, output| len.max(output.name.len()))
168    }
169
170    pub fn set_models(&self, setup: &[DesiredOutput]) -> Result<()> {
171        let disable: Vec<Output> = self
172            .0
173            .iter()
174            .filter_map(|o| {
175                if !setup.iter().any(|d| d.name == o.model) {
176                    Some(o.clone().disabled())
177                } else {
178                    None
179                }
180            })
181            .collect();
182
183        let new_setup: Result<Vec<Output>> = setup
184            .iter()
185            .map(|desired| {
186                self.0
187                    .iter()
188                    .find(|o| o.model == desired.name)
189                    .ok_or(color_eyre::eyre::eyre!(
190                        "Display '{}' is not connected",
191                        desired.name
192                    ))
193                    .map(|o| o.clone().enabled().with_scale(desired.scale.unwrap_or(1.0)))
194            })
195            .collect();
196        let new_setup = new_setup?;
197        self.set(new_setup.iter())?;
198        self.set(disable.iter())
199    }
200
201    pub fn set_by_name(&self, setup: &[String]) -> Result<()> {
202        let outputs: Vec<_> = self
203            .0
204            .iter()
205            .map(|o| {
206                if setup.iter().any(|desired| **desired == o.name) {
207                    o.clone().enabled()
208                } else {
209                    o.clone().disabled()
210                }
211            })
212            .collect();
213        self.set(outputs.iter())
214    }
215
216    fn set<'a>(&self, new_setup: impl Iterator<Item = &'a Output>) -> Result<()> {
217        let mut cmd_con = swayipc::Connection::new()?;
218        let mut last_x = 0;
219        for o in new_setup {
220            let payload = if o.enabled {
221                let desired_mode = o.best_mode();
222                let (width, height) = desired_mode.map(|m| (m.width, m.height)).unwrap_or((0, 0));
223                let payload = format!(
224                    "output {} enable position {} 0 resolution {}x{} scale {}",
225                    o.name(),
226                    last_x,
227                    width,
228                    height,
229                    o.scale
230                );
231                let scaled_w = width as f64 / o.scale;
232                last_x += scaled_w as i32;
233                payload
234            } else {
235                format!("output {} disable", o.name())
236            };
237            // println!("{}", payload);
238            cmd_con.run_command(payload)?;
239        }
240
241        Ok(())
242    }
243
244    pub fn activate_config(&self, cfgs: &Cfgs) -> Result<()> {
245        let connected_names: BTreeSet<String> =
246            self.iter().map(|o| o.model().to_string()).collect();
247        trace!("connected displays: {:?}", connected_names);
248        let mut valid_cfgs = Vec::new();
249        for (k, v) in cfgs.iter() {
250            let names: BTreeSet<_> = v.iter().map(|d| d.name.clone()).collect();
251            if names.is_subset(&connected_names) {
252                valid_cfgs.push((k, v));
253            }
254        }
255        valid_cfgs.sort_by_key(|a| a.1.len());
256        trace!("relevant cfgs: {:?}", valid_cfgs);
257        if let Some(best_cfg) = valid_cfgs.last() {
258            info!("activating config '{}'", best_cfg.0);
259            self.set_models(best_cfg.1)?;
260        }
261        Ok(())
262    }
263}
264
265impl std::fmt::Display for Outputs {
266    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267        let verbose = f.alternate();
268        let name_pad = self.longest_name();
269        self.0.iter().try_fold((), |_, output| {
270            writeln!(f, "{}", output.display(verbose, name_pad))
271        })
272    }
273}
274
275impl Deref for Outputs {
276    type Target = BTreeSet<Output>;
277
278    fn deref(&self) -> &Self::Target {
279        &self.0
280    }
281}
282
283impl DerefMut for Outputs {
284    fn deref_mut(&mut self) -> &mut Self::Target {
285        &mut self.0
286    }
287}
288
289impl<'a> FromIterator<&'a Output> for Outputs {
290    fn from_iter<T: IntoIterator<Item = &'a Output>>(iter: T) -> Self {
291        let mut vec: BTreeSet<Output> = BTreeSet::new();
292        for n in iter {
293            vec.insert(n.clone());
294        }
295
296        Self(vec)
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn padding() {
306        let output = Output::new(
307            "1234".to_owned(),
308            "model".to_owned(),
309            (0, 0),
310            (0, 0),
311            1.0,
312            true,
313            Vec::new(),
314        );
315        let display = output.display(false, 8);
316        assert_eq!(&display[..10], "1234:     ");
317    }
318}