1use std::os::unix::net::UnixStream;
26use std::time::{Duration, Instant};
27
28use wayland_client::protocol::wl_registry;
29use wayland_client::{Connection, Dispatch, EventQueue, QueueHandle, delegate_noop};
30use wayland_protocols_wlr::output_management::v1::client::{
31 zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1,
32 zwlr_output_configuration_v1::{self, ZwlrOutputConfigurationV1},
33 zwlr_output_head_v1::{self, ZwlrOutputHeadV1},
34 zwlr_output_manager_v1::{self, ZwlrOutputManagerV1},
35 zwlr_output_mode_v1::{self, ZwlrOutputModeV1},
36};
37
38use crate::wayland::wlclient::{IO_TIMEOUT, SyncState, bounded_roundtrip, impl_sync_callback};
39
40pub enum ScaleOutcome {
42 Applied,
43 Unsupported,
45}
46
47struct Head {
49 head: ZwlrOutputHeadV1,
50 name: Option<String>,
51 enabled: bool,
52 pos: (i32, i32),
53 scale: f64,
54}
55
56#[derive(Clone)]
59struct Screen {
60 head: ZwlrOutputHeadV1,
61 pos: (i32, i32),
62 mode: (i32, i32),
63 scale: f64,
64}
65
66fn logical_size(mode: (i32, i32), scale: f64) -> (i32, i32) {
68 (
69 (mode.0 as f64 / scale) as i32,
70 (mode.1 as f64 / scale) as i32,
71 )
72}
73
74pub(crate) fn close_gaps(
81 rects: &[(i32, i32, i32, i32)],
82 sizes: &[(i32, i32)],
83) -> Vec<(i32, i32, i32, i32)> {
84 let mut out: Vec<(i32, i32, i32, i32)> = rects
85 .iter()
86 .zip(sizes)
87 .map(|(r, s)| (r.0, r.1, s.0, s.1))
88 .collect();
89 let mut order: Vec<usize> = (0..out.len()).collect();
92 order.sort_by_key(|&i| rects[i].0);
93 for &i in &order {
94 if let Some(&j) = order
95 .iter()
96 .find(|&&j| rects[j].0 < rects[i].0 && rects[j].0 + rects[j].2 == rects[i].0)
97 {
98 out[i].0 = out[j].0 + out[j].2;
99 }
100 }
101 order.sort_by_key(|&i| rects[i].1);
102 for &i in &order {
103 if let Some(&j) = order
104 .iter()
105 .find(|&&j| rects[j].1 < rects[i].1 && rects[j].1 + rects[j].3 == rects[i].1)
106 {
107 out[i].1 = out[j].1 + out[j].3;
108 }
109 }
110 out
111}
112
113#[derive(Default)]
114struct OutState {
115 manager: Option<ZwlrOutputManagerV1>,
116 heads: Vec<Head>,
118 modes: Vec<(ZwlrOutputModeV1, (i32, i32))>,
121 current: Vec<(ZwlrOutputHeadV1, ZwlrOutputModeV1)>,
122 serial: Option<u32>,
123 applied: Option<bool>,
124 canceled: bool,
128 sync_done: bool,
129}
130
131impl SyncState for OutState {
132 fn sync_done_mut(&mut self) -> &mut bool {
133 &mut self.sync_done
134 }
135}
136
137impl OutState {
138 fn mode_size(&self, head: &ZwlrOutputHeadV1) -> (i32, i32) {
140 self.current
141 .iter()
142 .find(|(h, _)| h == head)
143 .and_then(|(_, mode)| self.modes.iter().find(|(m, _)| m == mode))
144 .map(|(_, size)| *size)
145 .unwrap_or((0, 0))
146 }
147
148 fn screens(&self) -> Vec<Screen> {
152 let mut screens: Vec<(String, Screen)> = self
153 .heads
154 .iter()
155 .filter(|h| h.enabled)
156 .map(|h| {
157 let screen = Screen {
158 head: h.head.clone(),
159 pos: h.pos,
160 mode: self.mode_size(&h.head),
161 scale: h.scale,
162 };
163 (h.name.clone().unwrap_or_default(), screen)
164 })
165 .collect();
166 screens.sort_by_key(|(name, _)| (trailing_number(name), name.clone()));
167 screens.into_iter().map(|(_, s)| s).collect()
168 }
169}
170impl_sync_callback!(OutState);
171
172impl Dispatch<wl_registry::WlRegistry, ()> for OutState {
173 fn event(
174 state: &mut Self,
175 registry: &wl_registry::WlRegistry,
176 event: wl_registry::Event,
177 _: &(),
178 _: &Connection,
179 qh: &QueueHandle<Self>,
180 ) {
181 if let wl_registry::Event::Global {
182 name,
183 interface,
184 version,
185 } = event
186 && interface == "zwlr_output_manager_v1"
187 && state.manager.is_none()
188 {
189 state.manager = Some(registry.bind(name, version.min(4), qh, ()));
190 }
191 }
192}
193
194impl Dispatch<ZwlrOutputManagerV1, ()> for OutState {
195 fn event(
196 state: &mut Self,
197 _: &ZwlrOutputManagerV1,
198 event: zwlr_output_manager_v1::Event,
199 _: &(),
200 _: &Connection,
201 _: &QueueHandle<Self>,
202 ) {
203 match event {
204 zwlr_output_manager_v1::Event::Head { head } => state.heads.push(Head {
205 head,
206 name: None,
207 enabled: false,
208 pos: (0, 0),
209 scale: 1.0,
210 }),
211 zwlr_output_manager_v1::Event::Done { serial } => state.serial = Some(serial),
214 _ => {}
215 }
216 }
217
218 wayland_client::event_created_child!(OutState, ZwlrOutputManagerV1, [
219 zwlr_output_manager_v1::EVT_HEAD_OPCODE => (ZwlrOutputHeadV1, ()),
220 ]);
221}
222
223impl Dispatch<ZwlrOutputHeadV1, ()> for OutState {
224 fn event(
225 state: &mut Self,
226 head: &ZwlrOutputHeadV1,
227 event: zwlr_output_head_v1::Event,
228 _: &(),
229 _: &Connection,
230 _: &QueueHandle<Self>,
231 ) {
232 let Some(entry) = state.heads.iter_mut().find(|h| h.head == *head) else {
233 return;
234 };
235 match event {
236 zwlr_output_head_v1::Event::Name { name } => entry.name = Some(name),
237 zwlr_output_head_v1::Event::Enabled { enabled } => entry.enabled = enabled != 0,
238 zwlr_output_head_v1::Event::Position { x, y } => entry.pos = (x, y),
239 zwlr_output_head_v1::Event::Scale { scale } => entry.scale = scale,
240 zwlr_output_head_v1::Event::CurrentMode { mode } => {
241 let head = head.clone();
242 state.current.retain(|(h, _)| *h != head);
243 state.current.push((head, mode));
244 }
245 _ => {}
246 }
247 }
248
249 wayland_client::event_created_child!(OutState, ZwlrOutputHeadV1, [
250 zwlr_output_head_v1::EVT_MODE_OPCODE => (ZwlrOutputModeV1, ()),
251 ]);
252}
253
254impl Dispatch<ZwlrOutputConfigurationV1, ()> for OutState {
255 fn event(
256 state: &mut Self,
257 _: &ZwlrOutputConfigurationV1,
258 event: zwlr_output_configuration_v1::Event,
259 _: &(),
260 _: &Connection,
261 _: &QueueHandle<Self>,
262 ) {
263 match event {
264 zwlr_output_configuration_v1::Event::Succeeded => state.applied = Some(true),
265 zwlr_output_configuration_v1::Event::Failed => state.applied = Some(false),
266 zwlr_output_configuration_v1::Event::Cancelled => {
267 state.canceled = true;
268 state.applied = Some(false);
269 }
270 _ => {}
271 }
272 }
273}
274
275impl Dispatch<ZwlrOutputModeV1, ()> for OutState {
276 fn event(
277 state: &mut Self,
278 mode: &ZwlrOutputModeV1,
279 event: zwlr_output_mode_v1::Event,
280 _: &(),
281 _: &Connection,
282 _: &QueueHandle<Self>,
283 ) {
284 if let zwlr_output_mode_v1::Event::Size { width, height } = event {
285 let mode = mode.clone();
286 state.modes.retain(|(m, _)| *m != mode);
287 state.modes.push((mode, (width, height)));
288 }
289 }
290}
291delegate_noop!(OutState: ZwlrOutputConfigurationHeadV1);
292
293pub fn set_output_scale(
297 socket_path: &str,
298 index: usize,
299 scale: f64,
300) -> Result<ScaleOutcome, String> {
301 if !(0.1..=16.0).contains(&scale) {
302 return Err(format!("scale {scale} out of range"));
303 }
304 configure(socket_path, |screens| rescale(screens, index, None, scale)).map(|changed| {
305 if changed == 0 {
306 ScaleOutcome::Unsupported
307 } else {
308 ScaleOutcome::Applied
309 }
310 })
311}
312
313fn rescale(
316 screens: &[Screen],
317 index: usize,
318 mode: Option<(i32, i32)>,
319 scale: f64,
320) -> Result<Vec<(ZwlrOutputHeadV1, Plan)>, String> {
321 if index >= screens.len() {
322 return Err(format!("no enabled screen at index {index}"));
323 }
324 let rects: Vec<(i32, i32, i32, i32)> = screens
325 .iter()
326 .map(|s| {
327 let (w, h) = logical_size(s.mode, s.scale);
328 (s.pos.0, s.pos.1, w, h)
329 })
330 .collect();
331 let mut sizes: Vec<(i32, i32)> = rects.iter().map(|r| (r.2, r.3)).collect();
332 sizes[index] = logical_size(mode.unwrap_or(screens[index].mode), scale);
333 let placed = close_gaps(&rects, &sizes);
334 Ok(screens
335 .iter()
336 .enumerate()
337 .filter_map(|(i, s)| {
338 let position = (placed[i].0, placed[i].1);
339 let mut plan = Plan {
340 position: (position != s.pos).then_some(position),
341 ..Plan::default()
342 };
343 if i == index {
344 plan.mode = mode;
345 plan.scale = Some(scale);
346 }
347 (i == index || plan.position.is_some()).then(|| (s.head.clone(), plan))
348 })
349 .collect())
350}
351
352pub fn set_screen_geometry(
360 socket_path: &str,
361 index: usize,
362 size: (i32, i32),
363 scale: f64,
364) -> Result<ScaleOutcome, String> {
365 if !(0.1..=16.0).contains(&scale) {
366 return Err(format!("scale {scale} out of range"));
367 }
368 if size.0 <= 0 || size.1 <= 0 {
369 return Err(format!("size {}x{} out of range", size.0, size.1));
370 }
371 configure(socket_path, move |screens| {
372 rescale(screens, index, Some(size), scale)
373 })
374 .map(|changed| {
375 if changed == 0 {
376 ScaleOutcome::Unsupported
377 } else {
378 ScaleOutcome::Applied
379 }
380 })
381}
382
383pub fn list_screens(socket_path: &str) -> Result<Vec<super::AppScreen>, String> {
389 let stream =
390 UnixStream::connect(socket_path).map_err(|e| format!("connect {socket_path}: {e}"))?;
391 let conn = Connection::from_socket(stream).map_err(|e| format!("wayland setup: {e}"))?;
392 let mut queue: EventQueue<OutState> = conn.new_event_queue();
393 let qh = queue.handle();
394 let _registry = conn.display().get_registry(&qh, ());
395 let mut state = OutState::default();
396 bounded_roundtrip(&conn, &mut queue, &mut state)?;
397 let Some(manager) = state.manager.clone() else {
398 return Ok(Vec::new());
399 };
400 bounded_roundtrip(&conn, &mut queue, &mut state)?;
401 let mut screens: Vec<(String, i32, i32, i32, i32)> = state
402 .heads
403 .iter()
404 .filter(|h| h.enabled)
405 .map(|h| {
406 let (w, hgt) = state.mode_size(&h.head);
407 (h.name.clone().unwrap_or_default(), h.pos.0, h.pos.1, w, hgt)
408 })
409 .collect();
410 screens.sort_by_key(|(name, _, _, _, _)| (trailing_number(name), name.clone()));
411 manager.stop();
412 let _ = queue.flush();
413 Ok(screens)
414}
415
416pub fn set_screen_layout(
434 socket_path: &str,
435 rects: Vec<(i32, i32, i32, i32)>,
436) -> Result<usize, String> {
437 if rects.iter().any(|(_, _, w, h)| *w <= 0 || *h <= 0) {
438 return Err("a screen rectangle has a non-positive size".to_string());
439 }
440 configure(socket_path, move |screens| {
441 let n = screens.len().min(rects.len());
442 let sizes: Vec<(i32, i32)> = screens
443 .iter()
444 .zip(&rects)
445 .map(|(s, r)| logical_size((r.2, r.3), s.scale))
446 .collect();
447 let placed = close_gaps(&rects[..n], &sizes);
448 Ok(screens
449 .iter()
450 .zip(&rects)
451 .zip(placed)
452 .map(|((s, r), p)| {
453 (
454 s.head.clone(),
455 Plan {
456 mode: Some((r.2, r.3)),
457 position: Some((p.0, p.1)),
458 ..Plan::default()
459 },
460 )
461 })
462 .collect())
463 })
464}
465
466pub fn hold_spare_screens(
471 socket_path: &str,
472 keep: usize,
473 size: (i32, i32),
474) -> Result<usize, String> {
475 configure(socket_path, move |screens| {
476 Ok(screens
477 .iter()
478 .skip(keep)
479 .map(|s| {
480 (
481 s.head.clone(),
482 Plan {
483 mode: Some(size),
484 ..Plan::default()
485 },
486 )
487 })
488 .collect())
489 })
490}
491
492pub(crate) fn trailing_number(name: &str) -> u32 {
494 let digits: String = name
495 .chars()
496 .rev()
497 .take_while(|c| c.is_ascii_digit())
498 .collect();
499 digits
500 .chars()
501 .rev()
502 .collect::<String>()
503 .parse()
504 .unwrap_or(0)
505}
506
507#[derive(Clone, Copy, Default)]
509struct Plan {
510 mode: Option<(i32, i32)>,
511 scale: Option<f64>,
512 position: Option<(i32, i32)>,
513}
514
515enum ConfigErr {
517 Stale,
521 Other(String),
522}
523
524impl From<String> for ConfigErr {
525 fn from(e: String) -> Self {
526 ConfigErr::Other(e)
527 }
528}
529
530const CONFIGURE_BUDGET: Duration = Duration::from_secs(1);
539
540fn configure<F>(socket_path: &str, plan: F) -> Result<usize, String>
546where
547 F: Fn(&[Screen]) -> Result<Vec<(ZwlrOutputHeadV1, Plan)>, String>,
548{
549 let deadline = Instant::now() + CONFIGURE_BUDGET;
550 let mut stale = 0;
551 loop {
552 match configure_once(socket_path, &plan) {
553 Ok(applied) => return Ok(applied),
554 Err(ConfigErr::Other(e)) => return Err(e),
555 Err(ConfigErr::Stale) => stale += 1,
556 }
557 if Instant::now() >= deadline {
558 return Err(format!(
559 "the compositor canceled the configuration {stale} times"
560 ));
561 }
562 }
563}
564
565fn configure_once<F>(socket_path: &str, plan: &F) -> Result<usize, ConfigErr>
571where
572 F: Fn(&[Screen]) -> Result<Vec<(ZwlrOutputHeadV1, Plan)>, String>,
573{
574 let stream =
575 UnixStream::connect(socket_path).map_err(|e| format!("connect {socket_path}: {e}"))?;
576 let conn = Connection::from_socket(stream).map_err(|e| format!("wayland setup: {e}"))?;
577 let mut queue: EventQueue<OutState> = conn.new_event_queue();
578 let qh = queue.handle();
579 let _registry = conn.display().get_registry(&qh, ());
580 let mut state = OutState::default();
581 bounded_roundtrip(&conn, &mut queue, &mut state)?;
582 let Some(manager) = state.manager.clone() else {
583 return Ok(0);
584 };
585 bounded_roundtrip(&conn, &mut queue, &mut state)?;
587 let serial = state
588 .serial
589 .ok_or_else(|| "output manager sent no state serial".to_string())?;
590 let screens = state.screens();
591 let wanted = plan(&screens)?;
592 if wanted.is_empty() {
593 manager.stop();
594 let _ = queue.flush();
595 return Ok(0);
596 }
597
598 let config = manager.create_configuration(serial, &qh, ());
600 for screen in &screens {
601 let cfg_head = config.enable_head(&screen.head, &qh, ());
602 if let Some((_, want)) = wanted.iter().find(|(h, _)| *h == screen.head) {
603 if let Some((w, h)) = want.mode {
604 cfg_head.set_custom_mode(w, h, 0);
605 }
606 if let Some(scale) = want.scale {
607 cfg_head.set_scale(scale);
608 }
609 if let Some((x, y)) = want.position {
610 cfg_head.set_position(x, y);
611 }
612 }
613 }
614 config.apply();
615 queue
616 .flush()
617 .map_err(|e| format!("flush configuration: {e}"))?;
618 state.applied = None;
619 let deadline = Instant::now() + IO_TIMEOUT;
620 while state.applied.is_none() && Instant::now() < deadline {
621 bounded_roundtrip(&conn, &mut queue, &mut state)?;
622 }
623 config.destroy();
624 manager.stop();
625 let _ = queue.flush();
626 match state.applied {
627 Some(true) => Ok(wanted.len()),
628 _ if state.canceled => Err(ConfigErr::Stale),
629 _ => Err(ConfigErr::Other(
630 "the compositor refused the configuration".to_string(),
631 )),
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::close_gaps;
638
639 #[test]
644 fn scaled_screens_close_up() {
645 let primary_right = [(3024, 0, 1920, 992), (0, 0, 3024, 1764)];
646 assert_eq!(
647 close_gaps(&primary_right, &[(1920, 992), (1512, 882)]),
648 vec![(1512, 0, 1920, 992), (0, 0, 1512, 882)]
649 );
650 let primary_left = [(0, 0, 1920, 1080), (1920, 0, 1920, 1080)];
651 assert_eq!(
652 close_gaps(&primary_left, &[(960, 540), (1920, 1080)]),
653 vec![(0, 0, 960, 540), (960, 0, 1920, 1080)]
654 );
655 assert_eq!(
656 close_gaps(&primary_left, &[(1920, 1080), (1920, 1080)]),
657 primary_left.to_vec()
658 );
659 let below = [(0, 1764, 1920, 992), (0, 0, 3024, 1764)];
660 assert_eq!(
661 close_gaps(&below, &[(1920, 992), (1512, 882)]),
662 vec![(0, 882, 1920, 992), (0, 0, 1512, 882)]
663 );
664 let chain = [
665 (0, 0, 1000, 500),
666 (1000, 0, 1000, 500),
667 (2000, 0, 1000, 500),
668 ];
669 assert_eq!(
670 close_gaps(&chain, &[(500, 250), (500, 250), (1000, 500)]),
671 vec![(0, 0, 500, 250), (500, 0, 500, 250), (1000, 0, 1000, 500)]
672 );
673 }
674}