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 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 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 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}