Skip to main content

leftwm_core/handlers/
focus_handler.rs

1#![allow(clippy::wildcard_imports)]
2
3use super::*;
4use crate::models::{Handle, TagId};
5use crate::state::State;
6use crate::{display_action::DisplayAction, models::FocusBehaviour};
7
8impl<H: Handle> State<H> {
9    /// Focuses a window based upon the `FocusBehaviour`
10    pub fn handle_window_focus(&mut self, handle: &WindowHandle<H>) {
11        match self.focus_manager.behaviour {
12            FocusBehaviour::Sloppy if self.focus_manager.sloppy_mouse_follows_focus => {
13                let act = DisplayAction::MoveMouseOver(*handle, false);
14                self.actions.push_back(act);
15            }
16            _ => self.focus_window(handle),
17        }
18    }
19
20    /// Focuses the given window.
21    pub fn focus_window(&mut self, handle: &WindowHandle<H>) {
22        let Some(window) = self.focus_window_work(handle) else {
23            return;
24        };
25
26        // Make sure the focused window's workspace is focused.
27        if let Some(workspace_id) = self
28            .workspaces
29            .iter()
30            .find(|ws| ws.is_displaying(&window))
31            .map(|ws| ws.id)
32        {
33            _ = self.focus_workspace_work(workspace_id);
34        }
35
36        // Make sure the focused window's tag is focused.
37        if let Some(tag) = window.tag {
38            _ = self.focus_tag_work(tag, true);
39        }
40    }
41
42    /// Focuses the given workspace.
43    // NOTE: Should only be called externally from this file.
44    pub fn focus_workspace(&mut self, workspace: &Workspace) {
45        if self.focus_workspace_work(workspace.id) {
46            // Make sure this workspaces tag is focused.
47            workspace.tag.iter().for_each(|t| {
48                self.focus_tag_work(*t, false);
49
50                if let Some(handle) = self.focus_manager.tags_last_window.get(t).copied() {
51                    self.focus_window_work(&handle);
52                } else {
53                    self.unfocus_current_window();
54                }
55            });
56        }
57    }
58
59    /// Focuses the given tag.
60    // NOTE: Should only be called externally from this file.
61    pub fn focus_tag(&mut self, tag: &TagId) {
62        if !self.focus_tag_work(*tag, false) {
63            return;
64        }
65        // Check each workspace, if its displaying this tag it should be focused too.
66        let to_focus: Vec<Workspace> = self
67            .workspaces
68            .iter()
69            .filter(|w| w.has_tag(tag))
70            .cloned()
71            .collect();
72        for ws in &to_focus {
73            self.focus_workspace_work(ws.id);
74        }
75        // Make sure the focused window is on this workspace.
76        if self.focus_manager.behaviour.is_sloppy() && self.focus_manager.sloppy_mouse_follows_focus
77        {
78            let act = DisplayAction::FocusWindowUnderCursor;
79            self.actions.push_back(act);
80        } else if let Some(handle) = self.focus_manager.tags_last_window.get(tag).copied() {
81            self.focus_window_work(&handle);
82        } else if let Some(ws) = to_focus.first() {
83            let handle = self
84                .windows
85                .iter()
86                .find(|w| ws.is_managed(w))
87                .map(|w| w.handle);
88            if let Some(h) = handle {
89                self.focus_window_work(&h);
90            }
91        }
92
93        // Unfocus last window if the target tag is empty
94        if let Some(window) = self.focus_manager.window(&self.windows)
95            && window.tag != Some(*tag)
96        {
97            self.unfocus_current_window();
98        }
99    }
100
101    /// Focuses the workspace containing a given point.
102    pub fn focus_workspace_with_point(&mut self, x: i32, y: i32) {
103        let Some(focused_id) = self
104            .focus_manager
105            .workspace(&self.workspaces)
106            .map(|ws| ws.id)
107        else {
108            return;
109        };
110
111        if let Some(ws) = self
112            .workspaces
113            .iter()
114            .find(|ws| ws.contains_point(x, y) && ws.id != focused_id)
115            .cloned()
116        {
117            self.focus_workspace(&ws);
118        }
119    }
120
121    /// Focuses the window containing a given point.
122    pub fn focus_window_with_point(&mut self, x: i32, y: i32) {
123        let handle_found: Option<WindowHandle<H>> = self
124            .windows
125            .iter()
126            .filter(|x| x.can_focus())
127            .find(|w| w.contains_point(x, y))
128            .map(|w| w.handle);
129        match handle_found {
130            Some(found) => self.focus_window(&found),
131            // backup plan, move focus closest window in workspace
132            None => self.focus_closest_window(x, y),
133        }
134    }
135
136    /// Validates that the given window is focused.
137    pub fn validate_focus_at(&mut self, handle: &WindowHandle<H>) {
138        // If the window is already focused do nothing.
139        if let Some(current) = self.focus_manager.window(&self.windows)
140            && &current.handle == handle
141        {
142            return;
143        }
144        // Focus the window only if it is also focusable.
145        if self
146            .windows
147            .iter()
148            .any(|w| w.can_focus() && &w.handle == handle)
149        {
150            self.focus_window(handle);
151        }
152    }
153
154    // Helper function.
155
156    fn focus_closest_window(&mut self, x: i32, y: i32) {
157        let Some(ws) = self.workspaces.iter().find(|ws| ws.contains_point(x, y)) else {
158            return;
159        };
160        let mut dists: Vec<(i32, &Window<H>)> = self
161            .windows
162            .iter()
163            .filter(|x| ws.is_managed(x) && x.can_focus())
164            .map(|w| (distance(w, x, y), w))
165            .collect();
166        dists.sort_by_key(|a| a.0);
167        if let Some(first) = dists.first() {
168            let handle = first.1.handle;
169            self.focus_window(&handle);
170        }
171    }
172
173    fn focus_tag_work(&mut self, tag: TagId, update_workspace: bool) -> bool {
174        if let Some(current_tag) = self.focus_manager.tag(0)
175            && current_tag == tag
176        {
177            return false;
178        }
179        // Clean old history.
180        self.focus_manager.tag_history.truncate(10);
181        // Add this focus to the history.
182        self.focus_manager.tag_history.push_front(tag);
183
184        if update_workspace && let Some(ws) = self.focus_manager.workspace_mut(&mut self.workspaces)
185        {
186            ws.tag = Some(tag);
187            self.update_static();
188        }
189
190        let act = DisplayAction::SetCurrentTags(Some(tag));
191        self.actions.push_back(act);
192        true
193    }
194
195    fn focus_window_work(&mut self, handle: &WindowHandle<H>) -> Option<Window<H>> {
196        if self.screens.iter().any(|s| &s.root == handle) {
197            let act = DisplayAction::Unfocus(None, false);
198            self.actions.push_back(act);
199            self.focus_manager.window_history.push_front(None);
200            return None;
201        }
202        // Find the handle in our managed windows.
203        let found: &Window<H> = self.windows.iter().find(|w| &w.handle == handle)?;
204        // Docks don't want to get focus. If they do weird things happen. They don't get events...
205        if !found.is_managed() {
206            return None;
207        }
208        let previous = self.focus_manager.window(&self.windows);
209        // No new history if no change.
210        if let Some(previous) = previous {
211            if &previous.handle == handle {
212                // Return some so we still update the visuals.
213                return Some(found.clone());
214            }
215            if let Some(tag_id) = &previous.tag {
216                self.focus_manager
217                    .tags_last_window
218                    .insert(*tag_id, previous.handle);
219            }
220        }
221
222        // Clean old history.
223        self.focus_manager.window_history.truncate(10);
224        // Add this focus change to the history.
225        self.focus_manager.window_history.push_front(Some(*handle));
226
227        let act = DisplayAction::WindowTakeFocus {
228            window: found.clone(),
229            previous_window: previous.cloned(),
230        };
231        self.actions.push_back(act);
232
233        Some(found.clone())
234    }
235
236    fn focus_workspace_work(&mut self, ws_id: usize) -> bool {
237        // no new history if no change
238        if let Some(fws) = self.focus_manager.workspace(&self.workspaces)
239            && fws.id == ws_id
240        {
241            return false;
242        }
243        // Clean old history.
244        self.focus_manager.workspace_history.truncate(10);
245        // Add this focus to the history.
246        if let Some(index) = self.workspaces.iter().position(|x| x.id == ws_id) {
247            self.focus_manager.workspace_history.push_front(index);
248            return true;
249        }
250        false
251    }
252
253    fn unfocus_current_window(&mut self) {
254        if let Some(window) = self.focus_manager.window(&self.windows) {
255            self.actions.push_back(DisplayAction::Unfocus(
256                Some(window.handle),
257                window.floating(),
258            ));
259            self.focus_manager.window_history.push_front(None);
260            if let Some(tag_id) = &window.tag {
261                self.focus_manager
262                    .tags_last_window
263                    .insert(*tag_id, window.handle);
264            }
265        }
266    }
267}
268
269// Square root not needed as we are only interested in the comparison.
270fn distance<H: Handle>(window: &Window<H>, x: i32, y: i32) -> i32 {
271    // (x_2-x_1)²+(y_2-y_1)²
272    let (wx, wy) = window.calculated_xyhw().center();
273    let xs = (wx - x) * (wx - x);
274    let ys = (wy - y) * (wy - y);
275    xs + ys
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281    use crate::{Manager, models::MockHandle};
282
283    #[test]
284    fn focusing_a_workspace_should_make_it_active() {
285        let mut manager = Manager::new_test(vec![]);
286        manager.screen_create_handler(Screen::default());
287        manager.screen_create_handler(Screen::default());
288        let expected = manager.state.workspaces[0].clone();
289        manager.state.focus_workspace(&expected);
290        let actual = manager
291            .state
292            .focus_manager
293            .workspace(&manager.state.workspaces)
294            .unwrap();
295        assert_eq!(&expected, actual);
296    }
297
298    #[test]
299    fn focusing_a_workspace_should_focus_its_last_active_window() {
300        let mut manager = Manager::new_test(vec!["1".to_string(), "2".to_string()]);
301        manager.screen_create_handler(Screen::default());
302        manager.screen_create_handler(Screen::default());
303        manager
304            .state
305            .focus_workspace(&manager.state.workspaces[0].clone());
306        manager.window_created_handler(
307            Window::new(WindowHandle::<MockHandle>(1), None, None),
308            -1,
309            -1,
310        );
311        manager.window_created_handler(
312            Window::new(WindowHandle::<MockHandle>(2), None, None),
313            -1,
314            -1,
315        );
316
317        manager
318            .state
319            .focus_workspace(&manager.state.workspaces[0].clone());
320
321        let expected = manager.state.windows.get(1).map(|w| w.handle);
322        manager.state.focus_window(&expected.unwrap());
323
324        manager
325            .state
326            .focus_workspace(&manager.state.workspaces[1].clone());
327        manager
328            .state
329            .focus_workspace(&manager.state.workspaces[0].clone());
330
331        let actual = manager
332            .state
333            .focus_manager
334            .window(&manager.state.windows)
335            .map(|w| w.handle);
336
337        assert_eq!(expected, actual);
338    }
339
340    #[test]
341    fn focusing_the_same_workspace_shouldnt_add_to_the_history() {
342        let mut manager = Manager::new_test(vec![]);
343        manager.screen_create_handler(Screen::default());
344        manager.screen_create_handler(Screen::default());
345        let ws = manager.state.workspaces[0].clone();
346        manager.state.focus_workspace(&ws);
347        let start_length = manager.state.focus_manager.workspace_history.len();
348        manager.state.focus_workspace(&ws);
349        let end_length = manager.state.focus_manager.workspace_history.len();
350        assert_eq!(start_length, end_length, "expected no new history event");
351    }
352
353    #[test]
354    fn focusing_a_window_should_make_it_active() {
355        let mut manager = Manager::new_test(vec![]);
356        manager.screen_create_handler(Screen::default());
357        manager.window_created_handler(
358            Window::new(WindowHandle::<MockHandle>(1), None, None),
359            -1,
360            -1,
361        );
362        manager.window_created_handler(
363            Window::new(WindowHandle::<MockHandle>(2), None, None),
364            -1,
365            -1,
366        );
367        let expected = manager.state.windows[0].clone();
368        manager.state.focus_window(&expected.handle);
369        let actual = manager
370            .state
371            .focus_manager
372            .window(&manager.state.windows)
373            .unwrap()
374            .handle;
375        assert_eq!(expected.handle, actual);
376    }
377
378    #[test]
379    fn focusing_the_same_window_shouldnt_add_to_the_history() {
380        let mut manager = Manager::new_test(vec![]);
381        manager.screen_create_handler(Screen::default());
382        let window = Window::new(WindowHandle::<MockHandle>(1), None, None);
383        manager.window_created_handler(window.clone(), -1, -1);
384        manager.state.focus_window(&window.handle);
385        let start_length = manager.state.focus_manager.workspace_history.len();
386        manager.window_created_handler(window.clone(), -1, -1);
387        manager.state.focus_window(&window.handle);
388        let end_length = manager.state.focus_manager.workspace_history.len();
389        assert_eq!(start_length, end_length, "expected no new history event");
390    }
391
392    #[test]
393    fn focusing_a_tag_should_make_it_active() {
394        let mut manager = Manager::new_test(vec![]);
395        manager.screen_create_handler(Screen::default());
396        let state = &mut manager.state;
397        let expected: usize = 1;
398        state.focus_tag(&expected);
399        let actual = state.focus_manager.tag(0).unwrap();
400        assert_eq!(actual, expected);
401    }
402
403    #[test]
404    fn focusing_the_same_tag_shouldnt_add_to_the_history() {
405        let mut manager = Manager::new_test(vec![]);
406        manager.screen_create_handler(Screen::default());
407        let state = &mut manager.state;
408        let tag: usize = 1;
409        state.focus_tag(&tag);
410        let start_length = state.focus_manager.tag_history.len();
411        state.focus_tag(&tag);
412        let end_length = state.focus_manager.tag_history.len();
413        assert_eq!(start_length, end_length, "expected no new history event");
414    }
415
416    #[test]
417    fn focusing_a_tag_should_focus_its_workspace() {
418        let mut manager = Manager::new_test(vec!["1".to_string()]);
419        manager.screen_create_handler(Screen::default());
420        manager.screen_create_handler(Screen::default());
421        manager.state.focus_tag(&1);
422        let actual = manager
423            .state
424            .focus_manager
425            .workspace(&manager.state.workspaces)
426            .unwrap();
427        assert_eq!(actual.id, 1);
428    }
429
430    #[test]
431    fn focusing_a_workspace_should_focus_its_tag() {
432        let mut manager = Manager::new_test(vec![]);
433        manager.screen_create_handler(Screen::default());
434        manager.screen_create_handler(Screen::default());
435        manager.screen_create_handler(Screen::default());
436        let ws = manager.state.workspaces[1].clone();
437        manager.state.focus_workspace(&ws);
438        let actual = manager.state.focus_manager.tag(0).unwrap();
439        assert_eq!(2, actual);
440    }
441
442    #[test]
443    fn focusing_a_window_should_focus_its_tag() {
444        let mut manager = Manager::new_test(vec![]);
445        manager.screen_create_handler(Screen::default());
446        manager.screen_create_handler(Screen::default());
447        manager.screen_create_handler(Screen::default());
448        let mut window = Window::new(WindowHandle::<MockHandle>(1), None, None);
449        window.tag(&2);
450        manager.state.windows.push(window.clone());
451        manager.state.focus_window(&window.handle);
452        let actual = manager.state.focus_manager.tag(0).unwrap();
453        assert_eq!(2, actual);
454    }
455
456    #[test]
457    fn focusing_a_window_should_focus_workspace() {
458        let mut manager = Manager::new_test(vec![]);
459        manager.screen_create_handler(Screen::default());
460        manager.screen_create_handler(Screen::default());
461        manager.screen_create_handler(Screen::default());
462        let mut window = Window::new(WindowHandle::<MockHandle>(1), None, None);
463        window.tag(&2);
464        manager.state.windows.push(window.clone());
465        manager.state.focus_window(&window.handle);
466        let actual = manager
467            .state
468            .focus_manager
469            .workspace(&manager.state.workspaces)
470            .unwrap();
471        let expected = &manager.state.workspaces[1];
472        assert_eq!(expected, actual);
473    }
474
475    #[test]
476    fn focusing_an_empty_tag_should_unfocus_any_focused_window() {
477        let mut manager = Manager::new_test(vec![]);
478        manager.screen_create_handler(Screen::default());
479        let mut window = Window::new(WindowHandle::<MockHandle>(1), None, None);
480        window.tag(&1);
481        manager.state.windows.push(window.clone());
482        manager.state.focus_window(&window.handle);
483        manager.state.focus_tag(&2);
484        let focused = manager.state.focus_manager.window(&manager.state.windows);
485        assert!(focused.is_none());
486    }
487}