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