1use std::collections::VecDeque;
7
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 child_process::{exec_shell_with_args, ChildID},
12 models::{Handle, ScratchPadName, TagId, WindowHandle},
13 Command, Config, DisplayAction, DisplayServer, Manager, Window,
14};
15
16#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
18pub enum ReleaseScratchPadOption<H: Handle> {
19 #[serde(bound = "")]
21 Handle(WindowHandle<H>),
22 ScratchpadName(ScratchPadName),
25 None,
27}
28
29fn hide_scratchpad<H: Handle, C: Config, SERVER: DisplayServer<H>>(
32 manager: &mut Manager<H, C, SERVER>,
33 scratchpad_window: &WindowHandle<H>,
34) -> Result<(), &'static str> {
35 tracing::trace!("Hide scratchpad window {:?}", scratchpad_window);
36 let nsp_tag = manager
37 .state
38 .tags
39 .get_hidden_by_label("NSP")
40 .ok_or("Could not find NSP tag")?;
41 let window = manager
42 .state
43 .windows
44 .iter_mut()
45 .find(|w| w.handle == *scratchpad_window)
46 .ok_or("Could not find window from scratchpad_window")?;
47
48 window.untag();
49 window.tag(&nsp_tag.id);
51 window.set_visible(false);
52
53 let act = DisplayAction::SetWindowTag(*scratchpad_window, window.tag);
55 manager.state.actions.push_back(act);
56 manager.state.sort_windows();
57 manager
58 .state
59 .handle_single_border(manager.config.border_width());
60
61 let last_focused_still_visible = manager
63 .state
64 .focus_manager
65 .window_history
66 .iter()
67 .find(|handle| {
68 manager
69 .state
70 .windows
71 .iter()
72 .find(|window| Some(window.handle) == **handle)
73 .is_some_and(Window::visible)
74 })
75 .copied();
76
77 let handle = if let Some(prev) = last_focused_still_visible {
79 prev
80 } else if let Some(ws) = manager
81 .state
82 .focus_manager
83 .workspace(&manager.state.workspaces)
84 {
85 manager
86 .state
87 .windows
88 .iter()
89 .find(|w| ws.is_managed(w))
90 .map(|w| w.handle)
91 } else {
92 None
93 };
94 if let Some(handle) = handle {
95 manager.state.handle_window_focus(&handle);
96 }
97
98 Ok(())
99}
100
101fn show_scratchpad<H: Handle, C: Config, SERVER: DisplayServer<H>>(
104 manager: &mut Manager<H, C, SERVER>,
105 scratchpad_window: &WindowHandle<H>,
106) -> Result<(), &'static str> {
107 tracing::trace!("Show scratchpad window {:?}", scratchpad_window);
108 let current_tag = &manager
109 .state
110 .focus_manager
111 .tag(0)
112 .ok_or("Could not retrieve the current tag")?;
113 let window = manager
114 .state
115 .windows
116 .iter_mut()
117 .find(|w| w.handle == *scratchpad_window)
118 .ok_or("Could not find window from scratchpad_window")?;
119 let previous_tag = window.tag;
120 window.untag();
121
122 if let Some(previous_tag) = previous_tag {
125 manager
126 .state
127 .focus_manager
128 .tags_last_window
129 .remove(&previous_tag);
130 }
131 window.tag(current_tag);
133 window.set_visible(true);
134
135 let act = DisplayAction::SetWindowTag(*scratchpad_window, window.tag);
137 manager.state.actions.push_back(act);
138 manager.state.sort_windows();
139 manager
140 .state
141 .handle_single_border(manager.config.border_width());
142 manager.state.handle_window_focus(scratchpad_window);
143 manager.state.move_to_top(scratchpad_window);
144
145 Ok(())
146}
147
148fn next_valid_scratchpad_pid<H: Handle>(
152 scratchpad_windows: &mut VecDeque<u32>,
153 managed_windows: &[Window<H>],
154 direction: Direction,
155) -> Option<u32> {
156 while let Some(window) = if direction == Direction::Forward {
157 scratchpad_windows.pop_front()
158 } else {
159 scratchpad_windows.pop_back()
160 } {
161 if managed_windows.iter().any(|w| w.pid == Some(window)) {
162 if direction == Direction::Forward {
163 scratchpad_windows.push_front(window);
164 } else {
165 scratchpad_windows.push_back(window);
166 }
167 return Some(window);
168 }
169
170 tracing::info!(
171 "Dead window in scratchpad found, discard: window PID: {}",
172 window
173 );
174 }
175
176 None
177}
178
179fn is_scratchpad_visible<H: Handle, C: Config, SERVER: DisplayServer<H>>(
182 manager: &Manager<H, C, SERVER>,
183 scratchpad_name: &ScratchPadName,
184) -> bool {
185 macro_rules! try_bool {
187 ($cond:expr) => {
188 if let Some(value) = $cond {
189 value
190 } else {
191 return false;
192 }
193 };
194 }
195 let current_tag = try_bool!(manager.state.focus_manager.tag(0));
196 let scratchpad = try_bool!(manager.state.active_scratchpads.get(scratchpad_name));
197
198 scratchpad
201 .iter()
202 .filter_map(|pid| manager.state.windows.iter().find(|w| w.pid == Some(*pid)))
203 .any(|window| window.has_tag(¤t_tag))
204}
205
206pub fn toggle_scratchpad<H: Handle, C: Config, SERVER: DisplayServer<H>>(
208 manager: &mut Manager<H, C, SERVER>,
209 name: &ScratchPadName,
210) -> Option<bool> {
211 let current_tag = &manager.state.focus_manager.tag(0)?;
212
213 if let Some(id) = manager.state.active_scratchpads.get_mut(name) {
215 if let Some(first_in_scratchpad) =
216 next_valid_scratchpad_pid(id, &manager.state.windows, Direction::Forward)
217 {
218 if let Some((is_visible, window_handle)) = manager
219 .state
220 .windows
221 .iter()
222 .find(|w| w.pid == Some(first_in_scratchpad))
223 .map(|w| (w.has_tag(current_tag), w.handle))
224 {
225 let action_result = if is_visible {
226 hide_scratchpad(manager, &window_handle)
228 } else {
229 show_scratchpad(manager, &window_handle)
231 };
232
233 return match action_result {
235 Ok(()) => Some(true),
236 Err(msg) => {
237 tracing::error!("{}", msg);
238 return Some(false);
239 }
240 };
241 }
242 }
243 }
244
245 let scratchpad = manager
246 .state
247 .scratchpads
248 .iter()
249 .find(|s| name == &s.name)?
250 .clone();
251
252 tracing::debug!(
253 "No active scratchpad found for name {:?}. Creating a new one",
254 name
255 );
256 tracing::debug!("Args for scratchpad: {:?}", &scratchpad.args);
257
258 let pid: ChildID = exec_shell_with_args(
259 &scratchpad.value,
260 scratchpad.args.unwrap_or_else(Vec::new),
261 &mut manager.children,
262 )?;
263
264 match manager.state.active_scratchpads.get_mut(name) {
265 Some(windows) => {
266 windows.push_front(pid);
267 }
268 None => {
269 manager
270 .state
271 .active_scratchpads
272 .insert(scratchpad.name, VecDeque::from([pid]));
273 }
274 }
275
276 None
277}
278
279pub fn attach_scratchpad<H: Handle, C: Config, SERVER: DisplayServer<H>>(
281 window: Option<WindowHandle<H>>,
282 scratchpad: &ScratchPadName,
283 manager: &mut Manager<H, C, SERVER>,
284) -> Option<bool> {
285 let window_handle = {
287 let current_window = manager
288 .state
289 .focus_manager
290 .window_history
291 .front()?
292 .as_ref()
293 .copied();
294
295 window.or(current_window)?
296 };
297
298 let window_pid = {
300 let ws = manager
301 .state
302 .focus_manager
303 .workspace(&manager.state.workspaces)?;
304 let to_scratchpad = manager
305 .state
306 .scratchpads
307 .iter()
308 .find(|s| &s.name == scratchpad)?;
309 let new_float_exact = to_scratchpad.xyhw(&ws.xyhw);
310
311 let window = manager
312 .state
313 .windows
314 .iter_mut()
315 .find(|w| w.handle == window_handle)?;
316
317 window.set_floating(true);
319 window.normal = ws.xyhw;
320 window.set_floating_exact(new_float_exact);
321 tracing::debug!("Set window to floating: {:?}", window);
322
323 window.pid?
324 };
325
326 if let Some(windows) = manager.state.active_scratchpads.get_mut(scratchpad) {
327 tracing::debug!(
328 "Scratchpad {:?} already active, push scratchpad",
329 &scratchpad
330 );
331 let previous_scratchpad_handle = manager
332 .state
333 .windows
334 .iter()
335 .find(|w| w.pid.as_ref() == windows.front())
336 .map(|w| w.handle);
337
338 if windows.iter().any(|pid| *pid == window_pid) {
340 return Some(false);
341 }
342
343 windows.push_front(window_pid);
344 if let Some(previous_scratchpad_handle) = previous_scratchpad_handle {
345 hide_scratchpad(manager, &previous_scratchpad_handle).ok()?; }
347 } else {
348 tracing::debug!(
349 "Scratchpad {:?} not active yet, open scratchpad",
350 &scratchpad
351 );
352 manager
353 .state
354 .active_scratchpads
355 .insert(scratchpad.clone(), VecDeque::from([window_pid]));
356 }
357 manager.state.sort_windows();
358
359 Some(true)
360}
361
362pub fn release_scratchpad<H: Handle, C: Config, SERVER: DisplayServer<H>>(
366 window: ReleaseScratchPadOption<H>,
367 tag: Option<TagId>,
368 manager: &mut Manager<H, C, SERVER>,
369) -> Option<bool> {
370 let destination_tag =
371 tag.or_else(|| manager.state.focus_manager.tag_history.front().copied())?;
372
373 let window = if window == ReleaseScratchPadOption::None {
375 ReleaseScratchPadOption::Handle(
376 manager
377 .state
378 .focus_manager
379 .window_history
380 .front()?
381 .as_ref()
382 .copied()?,
383 )
384 } else {
385 window
386 };
387
388 match window {
389 ReleaseScratchPadOption::Handle(window_handle) => {
390 let window = manager
392 .state
393 .windows
394 .iter_mut()
395 .find(|w| w.handle == window_handle)?;
396
397 let scratchpad_name: ScratchPadName = manager
398 .state
399 .active_scratchpads
400 .iter_mut()
401 .find(|(_, id)| window.pid.as_ref() == id.front())
402 .map(|(name, _)| name.clone())?;
403
404 tracing::debug!(
405 "Releasing scratchpad {:?} to tag {}",
406 scratchpad_name,
407 destination_tag
408 );
409
410 if let Some(windows) = manager.state.active_scratchpads.get_mut(&scratchpad_name) {
412 if windows.len() > 1 {
413 tracing::debug!("Removed 1 window from scratchpad {:?}", &scratchpad_name);
415 windows.remove(
416 windows
417 .iter()
418 .position(|w| Some(w) == window.pid.as_ref())?,
419 );
420 } else {
421 tracing::debug!(
423 "Empty scratchpad {:?}, removing from active_scratchpads",
424 &scratchpad_name
425 );
426 manager.state.active_scratchpads.remove(&scratchpad_name);
427 }
428 }
429
430 Some(manager.command_handler(&Command::SendWindowToTag {
431 window: Some(window_handle),
432 tag: destination_tag,
433 }))
434 }
435 ReleaseScratchPadOption::ScratchpadName(scratchpad_name) => {
436 let window_pid = manager
438 .state
439 .active_scratchpads
440 .get_mut(&scratchpad_name)
441 .and_then(|pids| {
442 next_valid_scratchpad_pid(pids, &manager.state.windows, Direction::Forward)
443 })?;
444 manager .state
446 .active_scratchpads
447 .get_mut(&scratchpad_name)?
448 .pop_front();
449
450 let window_handle = manager
451 .state
452 .windows
453 .iter()
454 .find(|w| w.pid == Some(window_pid))
455 .map(|w| w.handle);
456
457 tracing::debug!(
458 "Releasing scratchpad {:?} to tag {}",
459 scratchpad_name,
460 destination_tag
461 );
462
463 Some(manager.command_handler(&Command::SendWindowToTag {
464 window: window_handle,
465 tag: destination_tag,
466 }))
467 }
468 ReleaseScratchPadOption::None => unreachable!(), }
470}
471
472#[derive(Debug, Clone, Copy, Eq, PartialEq)]
473pub enum Direction {
474 Forward,
475 Backward,
476}
477
478pub fn cycle_scratchpad_window<H: Handle, C: Config, SERVER: DisplayServer<H>>(
481 manager: &mut Manager<H, C, SERVER>,
482 scratchpad_name: &ScratchPadName,
483 direction: Direction,
484) -> Option<bool> {
485 if !is_scratchpad_visible(manager, scratchpad_name) {
487 return Some(false);
488 }
489
490 let scratchpad = manager.state.active_scratchpads.get_mut(scratchpad_name)?;
491 let visible_window_handle = manager
493 .state
494 .windows
495 .iter()
496 .find(|w| w.pid.as_ref() == scratchpad.front()) .map(|w| w.handle);
498
499 next_valid_scratchpad_pid(scratchpad, &manager.state.windows, direction)?;
502 match direction {
504 Direction::Forward => scratchpad.rotate_left(1),
505 Direction::Backward => scratchpad.rotate_right(1),
506 };
507 let new_window_pid = *scratchpad.front()?;
508
509 if let Err(msg) = hide_scratchpad(manager, &visible_window_handle?) {
511 tracing::error!("{}", msg);
512 return Some(false);
513 }
514
515 let new_window_handle = manager
517 .state
518 .windows
519 .iter()
520 .find(|w| w.pid == Some(new_window_pid))
521 .map(|w| w.handle)?;
522 if let Err(msg) = show_scratchpad(manager, &new_window_handle) {
523 tracing::error!("{}", msg);
524 return Some(false);
525 }
526
527 manager.state.sort_windows();
529
530 Some(true)
531}
532
533#[cfg(test)]
534mod tests {
535 use crate::{
536 config::ScratchPad,
537 models::{MockHandle, ScratchPadName},
538 };
539
540 use super::*;
541
542 #[test]
543 fn show_scratchpad_test() {
544 let mut manager = Manager::new_test(vec!["AO".to_string(), "EU".to_string()]);
545 manager.screen_create_handler(Default::default());
546 let nsp_tag = manager.state.tags.get_hidden_by_label("NSP").unwrap().id;
547 let first_tag = manager.state.tags.get(1).unwrap().id;
548
549 let mock_window = 1_u32;
550 let window_handle = WindowHandle::<MockHandle>(mock_window as i32);
551 manager.window_created_handler(Window::new(window_handle, None, Some(mock_window)), -1, -1);
552 manager.command_handler(&Command::SendWindowToTag {
554 window: None,
555 tag: first_tag,
556 });
557
558 show_scratchpad(&mut manager, &window_handle).unwrap();
559
560 let window = manager
561 .state
562 .windows
563 .iter_mut()
564 .find(|w| w.pid == Some(mock_window))
565 .unwrap();
566
567 assert!(
568 !window.has_tag(&nsp_tag),
569 "Scratchpad window is still in hidden NSP tag"
570 );
571 assert!(
572 window.visible(),
573 "Scratchpad window still is marked as invisible"
574 );
575 }
576
577 #[test]
578 fn hide_scratchpad_test() {
579 let mut manager = Manager::new_test(vec!["AO".to_string(), "EU".to_string()]);
580 manager.screen_create_handler(Default::default());
581 let nsp_tag = manager.state.tags.get_hidden_by_label("NSP").unwrap().id;
582 let first_tag = manager.state.tags.get(1).unwrap().id;
583
584 let mock_window = 1_u32;
585 let window_handle = WindowHandle::<MockHandle>(mock_window as i32);
586 manager.window_created_handler(Window::new(window_handle, None, Some(mock_window)), -1, -1);
587 manager.command_handler(&Command::SendWindowToTag {
589 window: None,
590 tag: first_tag,
591 });
592
593 hide_scratchpad(&mut manager, &window_handle).unwrap();
594
595 let window = manager
596 .state
597 .windows
598 .iter_mut()
599 .find(|w| w.pid == Some(mock_window))
600 .unwrap();
601
602 assert!(
603 window.has_tag(&nsp_tag),
604 "Scratchpad window is not in hidden NSP tag"
605 );
606 assert!(
607 !window.visible(),
608 "Scratchpad window is not marked as invisible"
609 );
610 }
611
612 #[test]
613 fn toggle_scratchpad_test() {
614 let mut manager = Manager::new_test(vec!["AO".to_string(), "EU".to_string()]);
615 manager.screen_create_handler(Default::default());
616 let nsp_tag = manager.state.tags.get_hidden_by_label("NSP").unwrap().id;
617
618 let mock_window = 1_u32;
619 let window_handle = WindowHandle::<MockHandle>(mock_window as i32);
620 let scratchpad_name: ScratchPadName = "Alacritty".into();
621 manager.window_created_handler(Window::new(window_handle, None, Some(mock_window)), -1, -1);
622 manager.state.scratchpads.push(ScratchPad {
623 name: scratchpad_name.clone(),
624 value: String::new(),
625 args: None,
626 x: None,
627 y: None,
628 height: None,
629 width: None,
630 });
631 manager
632 .state
633 .active_scratchpads
634 .insert(scratchpad_name.clone(), VecDeque::from([mock_window]));
635
636 manager.command_handler(&Command::ToggleScratchPad(scratchpad_name.clone()));
637
638 {
640 let window = manager
641 .state
642 .windows
643 .iter_mut()
644 .find(|w| w.pid == Some(mock_window))
645 .unwrap();
646
647 assert!(
648 window.has_tag(&nsp_tag),
649 "Scratchpad window is not in hidden NSP tag"
650 );
651 assert!(!window.visible(), "Scratchpad is still marked as visible");
652 }
653
654 manager.command_handler(&Command::ToggleScratchPad(scratchpad_name));
655
656 {
658 let window = manager
659 .state
660 .windows
661 .iter_mut()
662 .find(|w| w.pid == Some(mock_window))
663 .unwrap();
664
665 assert!(
666 !window.has_tag(&nsp_tag),
667 "Scratchpad window should not be in the hidden NSP tag"
668 );
669 assert!(
670 window.visible(),
671 "Scratchpad window is still marked as invisible"
672 );
673 }
674 }
675
676 #[test]
677 fn release_scratchpad_test() {
680 let mut manager = Manager::new_test(vec!["AO".to_string(), "EU".to_string()]);
681 manager.screen_create_handler(Default::default());
682
683 let mock_window1 = 10_u32;
685 let scratchpad_name: ScratchPadName = "Alacritty".into();
686 manager
687 .state
688 .active_scratchpads
689 .insert(scratchpad_name.clone(), VecDeque::from([mock_window1]));
690 manager.window_created_handler(
691 Window::new(
692 WindowHandle::<MockHandle>(mock_window1 as i32),
693 None,
694 Some(mock_window1),
695 ),
696 -1,
697 -1,
698 );
699
700 let expected_tag = manager.state.tags.get(1).unwrap().id;
701
702 manager.command_handler(&Command::ReleaseScratchPad {
704 window: ReleaseScratchPadOption::Handle(WindowHandle::<MockHandle>(
705 mock_window1 as i32,
706 )),
707 tag: Some(expected_tag),
708 });
709
710 assert!(!manager
712 .state
713 .active_scratchpads
714 .contains_key(&scratchpad_name));
715 assert_eq!(
716 *manager.state.focus_manager.tag_history.front().unwrap(),
717 expected_tag
718 );
719 }
720
721 #[test]
722 fn release_scratchpad_multiple_windows_test() {
725 let mut manager = Manager::new_test(vec!["AO".to_string(), "EU".to_string()]);
726 manager.screen_create_handler(Default::default());
727 let nsp_tag = manager.state.tags.get_hidden_by_label("NSP").unwrap().id;
728
729 let mock_window1 = 1_u32;
731 let mock_window2 = 2_u32;
732 let mock_window3 = 3_u32;
733 let scratchpad_name: ScratchPadName = "Alacritty".into();
734 manager.state.active_scratchpads.insert(
735 scratchpad_name.clone(),
736 VecDeque::from([mock_window1, mock_window2, mock_window3]),
737 );
738 for window in [mock_window1, mock_window2, mock_window3] {
739 manager.window_created_handler(
740 Window::new(
741 WindowHandle::<MockHandle>(window as i32),
742 None,
743 Some(window),
744 ),
745 -1,
746 -1,
747 );
748 }
749
750 let expected_tag = manager.state.tags.get(1).unwrap().id;
751
752 manager.command_handler(&Command::ReleaseScratchPad {
754 window: ReleaseScratchPadOption::Handle(WindowHandle::<MockHandle>(
755 mock_window1 as i32,
756 )),
757 tag: Some(expected_tag),
758 });
759
760 let scratchpad = manager
762 .state
763 .active_scratchpads
764 .get_mut(&scratchpad_name)
765 .unwrap();
766
767 assert!(manager
768 .state
769 .windows
770 .iter()
771 .find(|w| w.pid == Some(mock_window1))
772 .map(|w| !w.has_tag(&nsp_tag))
773 .unwrap());
774 for mock_window_pid in [mock_window2, mock_window3] {
775 let window_pid = scratchpad.pop_front();
776 assert_eq!(window_pid, Some(mock_window_pid));
777 assert!(!manager
778 .state
779 .windows
780 .iter()
781 .find(|w| w.pid == window_pid)
782 .map(|w| w.has_tag(&nsp_tag))
783 .unwrap());
784 }
785 assert_eq!(scratchpad.pop_front(), None);
786
787 assert_eq!(
788 *manager.state.focus_manager.tag_history.front().unwrap(),
789 expected_tag
790 );
791 }
792
793 #[test]
794 fn attach_scratchpad_test() {
795 let mut manager = Manager::new_test(vec!["AO".to_string(), "EU".to_string()]);
796 manager.screen_create_handler(Default::default());
797 let nsp_tag = manager.state.tags.get_hidden_by_label("NSP").unwrap().id;
798
799 let mock_window1 = 1_u32;
801 let mock_window2 = 2_u32;
802 let mock_window3 = 3_u32;
803 let scratchpad_name: ScratchPadName = "Alacritty".into();
804 manager.state.scratchpads.push(ScratchPad {
805 name: scratchpad_name.clone(),
806 value: "scratchpad".to_string(),
807 args: None,
808 x: None,
809 y: None,
810 height: None,
811 width: None,
812 });
813 manager.state.active_scratchpads.insert(
814 scratchpad_name.clone(),
815 VecDeque::from([mock_window2, mock_window3]),
816 );
817 for mock_window in [mock_window1, mock_window2, mock_window3] {
818 let mut window = Window::new(
819 WindowHandle::<MockHandle>(mock_window as i32),
820 None,
821 Some(mock_window),
822 );
823 if mock_window != mock_window1 {
824 window.tag(&nsp_tag);
825 }
826
827 manager.window_created_handler(window, -1, -1);
828 }
829
830 manager.command_handler(&Command::AttachScratchPad {
832 window: Some(WindowHandle::<MockHandle>(mock_window1 as i32)),
833 scratchpad: scratchpad_name.clone(),
834 });
835
836 let scratchpad = manager
838 .state
839 .active_scratchpads
840 .get_mut(&scratchpad_name)
841 .unwrap();
842
843 assert_eq!(scratchpad.pop_front(), Some(mock_window1));
844 assert!(manager
845 .state
846 .windows
847 .iter()
848 .find(|w| w.pid == Some(mock_window1))
849 .map(|w| !w.has_tag(&nsp_tag))
850 .unwrap());
851 for mock_window_pid in [mock_window2, mock_window3] {
852 let window_pid = scratchpad.pop_front();
853 assert_eq!(window_pid, Some(mock_window_pid));
854 assert!(manager
855 .state
856 .windows
857 .iter()
858 .find(|w| w.pid == window_pid)
859 .map(|w| w.has_tag(&nsp_tag))
860 .unwrap());
861 }
862 assert_eq!(scratchpad.pop_front(), None);
863 }
864
865 #[test]
866 fn next_valid_pid_forward_test() {
867 let mock_window1 = 1_u32;
869 let mock_window2 = 2_u32;
870 let mock_window3 = 3_u32;
871 let mock_window4 = 4_u32;
872
873 let mut managed_windows = [mock_window1, mock_window2, mock_window3, mock_window4]
874 .iter()
875 .map(|pid| Window::new(WindowHandle::<MockHandle>(*pid as i32), None, Some(*pid)))
876 .collect::<Vec<Window<MockHandle>>>();
877 let mut scratchpad =
878 VecDeque::from([mock_window1, mock_window2, mock_window3, mock_window4]);
879
880 assert_eq!(
881 next_valid_scratchpad_pid(&mut scratchpad, &managed_windows, Direction::Forward),
882 Some(1)
883 );
884
885 managed_windows.remove(1);
886 assert_eq!(
887 next_valid_scratchpad_pid(&mut scratchpad, &managed_windows, Direction::Forward),
888 Some(1)
889 );
890
891 scratchpad.pop_front();
892 assert_eq!(
893 next_valid_scratchpad_pid(&mut scratchpad, &managed_windows, Direction::Forward),
894 Some(3)
895 );
896 assert_eq!(scratchpad.len(), 2);
897 }
898
899 #[test]
900 fn next_valid_pid_backward_test() {
901 let mock_window1 = 1_u32;
903 let mock_window2 = 2_u32;
904 let mock_window3 = 3_u32;
905 let mock_window4 = 4_u32;
906
907 let mut managed_windows = [mock_window1, mock_window2, mock_window3, mock_window4]
908 .iter()
909 .map(|pid| Window::new(WindowHandle::<MockHandle>(*pid as i32), None, Some(*pid)))
910 .collect::<Vec<Window<MockHandle>>>();
911 let mut scratchpad =
912 VecDeque::from([mock_window1, mock_window2, mock_window3, mock_window4]);
913
914 assert_eq!(
915 next_valid_scratchpad_pid(&mut scratchpad, &managed_windows, Direction::Backward),
916 Some(4)
917 );
918
919 managed_windows.remove(2);
920 assert_eq!(
921 next_valid_scratchpad_pid(&mut scratchpad, &managed_windows, Direction::Backward),
922 Some(4)
923 );
924
925 scratchpad.pop_back();
926 assert_eq!(
927 next_valid_scratchpad_pid(&mut scratchpad, &managed_windows, Direction::Backward),
928 Some(2)
929 );
930 assert_eq!(scratchpad.len(), 2);
931 }
932
933 #[test]
934 #[allow(clippy::too_many_lines)]
935 fn cycle_scratchpad_window_test() {
936 fn is_visible<H: Handle, C: Config, SERVER: DisplayServer<H>>(
937 manager: &Manager<H, C, SERVER>,
938 pid: u32,
939 nsp_tag: TagId,
940 ) -> bool {
941 manager
942 .state
943 .windows
944 .iter()
945 .find(|w| w.pid == Some(pid))
946 .map(|w| w.visible() && !w.has_tag(&nsp_tag))
947 .unwrap()
948 }
949 fn is_only_first_visible<H: Handle, C: Config, SERVER: DisplayServer<H>>(
950 manager: &Manager<H, C, SERVER>,
951 mut pids: impl Iterator<Item = u32>,
952 nsp_tag: TagId,
953 ) -> bool {
954 if !is_visible(manager, pids.next().unwrap(), nsp_tag) {
955 return false;
956 }
957 for pid in pids {
958 if is_visible(manager, pid, nsp_tag) {
959 return false;
960 }
961 }
962
963 true
964 }
965
966 let mut manager = Manager::new_test(vec!["AO".to_string(), "EU".to_string()]);
967 manager.screen_create_handler(Default::default());
968 let nsp_tag = manager.state.tags.get_hidden_by_label("NSP").unwrap().id;
969
970 let mock_window1 = 1_u32;
972 let mock_window2 = 2_u32;
973 let mock_window3 = 3_u32;
974 let scratchpad_name: ScratchPadName = "Alacritty".into();
975
976 for mock_window in [mock_window1, mock_window2, mock_window3] {
977 let mut window = Window::new(
978 WindowHandle::<MockHandle>(mock_window as i32),
979 None,
980 Some(mock_window),
981 );
982 if mock_window != mock_window1 {
983 window.tag(&nsp_tag);
984 }
985
986 manager.window_created_handler(window, -1, -1);
987 }
988 manager.state.scratchpads.push(ScratchPad {
989 name: scratchpad_name.clone(),
990 value: "scratchpad".to_string(),
991 args: None,
992 x: None,
993 y: None,
994 height: None,
995 width: None,
996 });
997 manager.state.active_scratchpads.insert(
998 scratchpad_name.clone(),
999 VecDeque::from([mock_window1, mock_window2, mock_window3]),
1000 );
1001
1002 cycle_scratchpad_window(&mut manager, &scratchpad_name, Direction::Forward);
1003 let mut scratchpad_iterator = manager
1004 .state
1005 .active_scratchpads
1006 .get(&scratchpad_name)
1007 .unwrap()
1008 .iter();
1009 assert!(
1010 is_only_first_visible(&manager, scratchpad_iterator.clone().copied(), nsp_tag),
1011 "On the first forward cycle, the first window is not visible or the other windows are visible"
1012 );
1013 assert_eq!(scratchpad_iterator.next(), Some(&mock_window2));
1014 assert_eq!(scratchpad_iterator.next(), Some(&mock_window3));
1015 assert_eq!(scratchpad_iterator.next(), Some(&mock_window1));
1016 assert_eq!(scratchpad_iterator.next(), None);
1017
1018 cycle_scratchpad_window(&mut manager, &scratchpad_name, Direction::Forward);
1019 let mut scratchpad_iterator = manager
1020 .state
1021 .active_scratchpads
1022 .get(&scratchpad_name)
1023 .unwrap()
1024 .iter();
1025 assert!(is_only_first_visible(
1026 &manager,
1027 scratchpad_iterator.clone().copied(),
1028 nsp_tag
1029 ),
1030 "On the second forward cycle, the first window is not visible or the other windows are visible"
1031 );
1032 assert_eq!(scratchpad_iterator.next(), Some(&mock_window3));
1033 assert_eq!(scratchpad_iterator.next(), Some(&mock_window1));
1034 assert_eq!(scratchpad_iterator.next(), Some(&mock_window2));
1035 assert_eq!(scratchpad_iterator.next(), None);
1036
1037 cycle_scratchpad_window(&mut manager, &scratchpad_name, Direction::Backward);
1038 let mut scratchpad_iterator = manager
1039 .state
1040 .active_scratchpads
1041 .get(&scratchpad_name)
1042 .unwrap()
1043 .iter();
1044 assert!(is_only_first_visible(
1045 &manager,
1046 scratchpad_iterator.clone().copied(),
1047 nsp_tag
1048 ),
1049 "After 2 forward and 1 backward cycles, the first window is not visible or the other windows are visible"
1050 );
1051 assert_eq!(scratchpad_iterator.next(), Some(&mock_window2));
1052 assert_eq!(scratchpad_iterator.next(), Some(&mock_window3));
1053 assert_eq!(scratchpad_iterator.next(), Some(&mock_window1));
1054 assert_eq!(scratchpad_iterator.next(), None);
1055
1056 cycle_scratchpad_window(&mut manager, &scratchpad_name, Direction::Backward);
1057 let mut scratchpad_iterator = manager
1058 .state
1059 .active_scratchpads
1060 .get(&scratchpad_name)
1061 .unwrap()
1062 .iter();
1063 assert!(is_only_first_visible(
1064 &manager,
1065 scratchpad_iterator.clone().copied(),
1066 nsp_tag
1067 ),
1068 "After 2 forward and 2 backward cycles, the first window is not visible or the other windows are visible"
1069 );
1070 assert_eq!(scratchpad_iterator.next(), Some(&mock_window1));
1071 assert_eq!(scratchpad_iterator.next(), Some(&mock_window2));
1072 assert_eq!(scratchpad_iterator.next(), Some(&mock_window3));
1073 assert_eq!(scratchpad_iterator.next(), None);
1074 }
1075
1076 #[test]
1077 fn change_focus_with_open_scratchpad_test() {
1078 let mut manager = Manager::new_test(vec!["AO".to_string(), "EU".to_string()]);
1079 manager.screen_create_handler(Default::default());
1080
1081 let mock_window1 = 1_u32;
1083 let mock_window2 = 2_u32;
1084 let mock_window3 = 3_u32;
1085 let scratchpad_name: ScratchPadName = "Alacritty".into();
1086
1087 for mock_window in [mock_window1, mock_window2, mock_window3] {
1088 let mut window = Window::new(
1089 WindowHandle::<MockHandle>(mock_window as i32),
1090 None,
1091 Some(mock_window),
1092 );
1093 window.set_visible(true);
1094 window.tag(&1);
1095
1096 manager.window_created_handler(window, -1, -1);
1097 }
1098 manager.state.scratchpads.push(ScratchPad {
1099 name: scratchpad_name.clone(),
1100 value: "scratchpad".to_string(),
1101 args: None,
1102 x: None,
1103 y: None,
1104 height: None,
1105 width: None,
1106 });
1107 manager
1108 .state
1109 .active_scratchpads
1110 .insert(scratchpad_name, VecDeque::from([mock_window3]));
1111
1112 let focus_window_handler = manager.state.windows[0].handle;
1114 manager.state.handle_window_focus(&focus_window_handler);
1115 assert_eq!(
1116 manager
1117 .state
1118 .focus_manager
1119 .window(&manager.state.windows)
1120 .unwrap()
1121 .handle,
1122 WindowHandle::<MockHandle>(1),
1123 "Initially the first window (1) should be focused"
1124 );
1125
1126 manager.command_handler(&Command::FocusWindowDown);
1127 assert_eq!(
1128 manager
1129 .state
1130 .focus_manager
1131 .window(&manager.state.windows)
1132 .unwrap()
1133 .handle,
1134 WindowHandle::<MockHandle>(2),
1135 "After 1 down window (2) should be focused"
1136 );
1137
1138 manager.command_handler(&Command::FocusWindowDown);
1139 assert_eq!(
1140 manager
1141 .state
1142 .focus_manager
1143 .window(&manager.state.windows)
1144 .unwrap()
1145 .handle,
1146 WindowHandle::<MockHandle>(3),
1147 "After 2 down window (3) should be focused"
1148 );
1149
1150 manager.command_handler(&Command::FocusWindowDown);
1151 assert_eq!(
1152 manager
1153 .state
1154 .focus_manager
1155 .window(&manager.state.windows)
1156 .unwrap()
1157 .handle,
1158 WindowHandle::<MockHandle>(1),
1159 "After 3 down window (1) should be focused (cycle back)"
1160 );
1161
1162 manager.command_handler(&Command::FocusWindowUp);
1163 assert_eq!(
1164 manager
1165 .state
1166 .focus_manager
1167 .window(&manager.state.windows)
1168 .unwrap()
1169 .handle,
1170 WindowHandle::<MockHandle>(3),
1171 "After 3 down and 1 up window (3) should be focused (cycle back)"
1172 );
1173
1174 manager.command_handler(&Command::FocusWindowUp);
1175 assert_eq!(
1176 manager
1177 .state
1178 .focus_manager
1179 .window(&manager.state.windows)
1180 .unwrap()
1181 .handle,
1182 WindowHandle::<MockHandle>(2),
1183 "After 3 down and 2 up window (2) should be focused"
1184 );
1185 }
1186
1187 #[test]
1188 fn focus_top_from_scratchpad_test() {
1189 let mut manager = Manager::new_test(vec!["AO".to_string(), "EU".to_string()]);
1190 manager.screen_create_handler(Default::default());
1191
1192 let mock_window1 = 1_u32;
1194 let mock_window2 = 2_u32;
1195 let mock_window3 = 3_u32;
1196 let scratchpad_name: ScratchPadName = "Alacritty".into();
1197
1198 for mock_window in [mock_window1, mock_window2, mock_window3] {
1199 let mut window = Window::new(
1200 WindowHandle::<MockHandle>(mock_window as i32),
1201 None,
1202 Some(mock_window),
1203 );
1204 window.set_visible(true);
1205 window.tag(&1);
1206
1207 manager.window_created_handler(window, -1, -1);
1208 }
1209 manager.state.scratchpads.push(ScratchPad {
1210 name: scratchpad_name.clone(),
1211 value: "scratchpad".to_string(),
1212 args: None,
1213 x: None,
1214 y: None,
1215 height: None,
1216 width: None,
1217 });
1218 manager
1219 .state
1220 .active_scratchpads
1221 .insert(scratchpad_name, VecDeque::from([mock_window3]));
1222
1223 let focus_window_handler = manager.state.windows[0].handle;
1225 manager.state.handle_window_focus(&focus_window_handler);
1226 assert_eq!(
1227 manager
1228 .state
1229 .focus_manager
1230 .window(&manager.state.windows)
1231 .unwrap()
1232 .handle,
1233 WindowHandle::<MockHandle>(1),
1234 "Initially the first window (1) should be focused"
1235 );
1236
1237 manager.command_handler(&Command::FocusWindowUp);
1238 assert_eq!(
1239 manager
1240 .state
1241 .focus_manager
1242 .window(&manager.state.windows)
1243 .unwrap()
1244 .handle,
1245 WindowHandle::<MockHandle>(3),
1246 "After 1 up window (3) should be focused (scratchpad window)"
1247 );
1248
1249 manager.command_handler(&Command::FocusWindowTop { swap: false });
1250 assert_eq!(
1251 manager
1252 .state
1253 .focus_manager
1254 .window(&manager.state.windows)
1255 .unwrap()
1256 .handle,
1257 WindowHandle::<MockHandle>(1),
1258 "After focusing the scratchpad and then focusing the top, window (1) should be focused"
1259 );
1260 }
1261
1262 #[test]
1263 fn toggle_scratchpad_also_toggles_single_window_borders() {
1264 let mut manager = Manager::new_test_with_border(vec!["1".to_string(), "2".to_string()], 1);
1265 manager.screen_create_handler(Default::default());
1266 let second_tag = manager.state.tags.get(2).unwrap().id;
1267
1268 let scratchpad_pid = 1_u32;
1269 let scratchpad_handle = WindowHandle::<MockHandle>(scratchpad_pid as i32);
1270 let scratchpad_name: ScratchPadName = "Alacritty".into();
1271 let mut scratchpad = Window::new(scratchpad_handle, None, Some(scratchpad_pid));
1272 scratchpad.tag = Some(second_tag);
1273 manager.window_created_handler(scratchpad, -1, -1);
1274 manager.state.scratchpads.push(ScratchPad {
1275 name: scratchpad_name.clone(),
1276 value: String::new(),
1277 args: None,
1278 x: None,
1279 y: None,
1280 height: None,
1281 width: None,
1282 });
1283 manager
1284 .state
1285 .active_scratchpads
1286 .insert(scratchpad_name.clone(), VecDeque::from([scratchpad_pid]));
1287
1288 let window_pid = 2_u32;
1289 let window_handle = WindowHandle::<MockHandle>(window_pid as i32);
1290 manager.window_created_handler(Window::new(window_handle, None, Some(window_pid)), -1, -1);
1291
1292 manager.command_handler(&Command::ToggleScratchPad(scratchpad_name.clone()));
1293
1294 {
1295 let scratchpad = manager
1296 .state
1297 .windows
1298 .iter_mut()
1299 .find(|w| w.pid == Some(scratchpad_pid))
1300 .unwrap();
1301 assert_eq!(scratchpad.border(), 1);
1302
1303 let window = manager
1304 .state
1305 .windows
1306 .iter_mut()
1307 .find(|w| w.pid == Some(window_pid))
1308 .unwrap();
1309 assert_eq!(window.border(), 1);
1310 }
1311
1312 manager.command_handler(&Command::ToggleScratchPad(scratchpad_name));
1313
1314 {
1315 let window = manager
1316 .state
1317 .windows
1318 .iter_mut()
1319 .find(|w| w.pid == Some(window_pid))
1320 .unwrap();
1321
1322 assert_eq!(window.border(), 0);
1323 }
1324 }
1325}