@@ -856,6 +856,59 @@ impl WmState { |
| 856 | self.focus_window_impl(id, true); | 856 | self.focus_window_impl(id, true); |
| 857 | } | 857 | } |
| 858 | | 858 | |
| | 859 | + fn dismiss_special_on_monitor(&mut self, monitor_idx: usize) { |
| | 860 | + if monitor_idx >= self.active_specials.len() { |
| | 861 | + return; |
| | 862 | + } |
| | 863 | + let Some(special_idx) = self.active_specials[monitor_idx] else { |
| | 864 | + return; |
| | 865 | + }; |
| | 866 | + let wids = self.workspaces.get(special_idx).all_window_ids(); |
| | 867 | + self.active_specials[monitor_idx] = None; |
| | 868 | + self.sync_workspace_visibility(); |
| | 869 | + for wid in wids { |
| | 870 | + self.hide_window(wid); |
| | 871 | + } |
| | 872 | + } |
| | 873 | + |
| | 874 | + fn adopt_external_focus(&mut self, id: WindowId) { |
| | 875 | + let Some(ws_idx) = self.workspaces.find_window(id) else { |
| | 876 | + return; |
| | 877 | + }; |
| | 878 | + |
| | 879 | + self.focus_window_impl(id, false); |
| | 880 | + |
| | 881 | + if let Some(mi) = self.monitor_showing_workspace(ws_idx) { |
| | 882 | + if mi < self.active_specials.len() && self.active_specials[mi] != Some(ws_idx) { |
| | 883 | + self.dismiss_special_on_monitor(mi); |
| | 884 | + } |
| | 885 | + self.focused_monitor = mi; |
| | 886 | + self.ffm_last_window = None; |
| | 887 | + return; |
| | 888 | + } |
| | 889 | + |
| | 890 | + let ws_id = self.workspaces.get(ws_idx).id.clone(); |
| | 891 | + match ws_id { |
| | 892 | + super::workspace::WorkspaceId::Special(name) => { |
| | 893 | + let current_overlay = self |
| | 894 | + .active_specials |
| | 895 | + .iter() |
| | 896 | + .position(|special| *special == Some(ws_idx)); |
| | 897 | + if let Some(mi) = current_overlay { |
| | 898 | + self.focused_monitor = mi; |
| | 899 | + } else { |
| | 900 | + self.toggle_special(&name); |
| | 901 | + } |
| | 902 | + self.focus_window_impl(id, false); |
| | 903 | + } |
| | 904 | + _ => { |
| | 905 | + if let Some(target) = ws_id.as_regular_target() { |
| | 906 | + self.switch_workspace(&target); |
| | 907 | + } |
| | 908 | + } |
| | 909 | + } |
| | 910 | + } |
| | 911 | + |
| 859 | fn focus_window_impl(&mut self, id: WindowId, activate_app: bool) { | 912 | fn focus_window_impl(&mut self, id: WindowId, activate_app: bool) { |
| 860 | if let Some(ax_ref) = self.ax_refs.get(&id) { | 913 | if let Some(ax_ref) = self.ax_refs.get(&id) { |
| 861 | if activate_app { | 914 | if activate_app { |
@@ -2735,15 +2788,7 @@ impl WmState { |
| 2735 | if let Ok(id) = ax_get_window_id(element) | 2788 | if let Ok(id) = ax_get_window_id(element) |
| 2736 | && self.registry.contains(id) | 2789 | && self.registry.contains(id) |
| 2737 | { | 2790 | { |
| 2738 | - if self.is_window_hidden(id) { | 2791 | + self.adopt_external_focus(id); |
| 2739 | - return; | | |
| 2740 | - } | | |
| 2741 | - // Record on the workspace that contains this window, | | |
| 2742 | - // not the active workspace (same fix as focus_window_impl) | | |
| 2743 | - if let Some(ws_idx) = self.workspaces.find_window(id) { | | |
| 2744 | - self.workspaces.get_mut(ws_idx).tree.set_stack_active(id); | | |
| 2745 | - self.workspaces.get_mut(ws_idx).record_focus(id); | | |
| 2746 | - } | | |
| 2747 | } | 2792 | } |
| 2748 | } | 2793 | } |
| 2749 | WindowEvent::Moved { element, .. } => { | 2794 | WindowEvent::Moved { element, .. } => { |
@@ -3183,6 +3228,89 @@ mod tests { |
| 3183 | assert_eq!(state.active_workspace().focused, Some(3)); | 3228 | assert_eq!(state.active_workspace().focused, Some(3)); |
| 3184 | } | 3229 | } |
| 3185 | | 3230 | |
| | 3231 | + #[test] |
| | 3232 | + fn external_focus_reveals_hidden_workspace_and_tracks_target_window() { |
| | 3233 | + let mut state = WmState::new(); |
| | 3234 | + state.monitors = vec![Monitor { |
| | 3235 | + id: 42, |
| | 3236 | + frame: Rect::new(0.0, 0.0, 1920.0, 1080.0), |
| | 3237 | + usable_frame: Rect::new(0.0, 33.0, 1920.0, 1047.0), |
| | 3238 | + is_primary: true, |
| | 3239 | + active_workspace: 0, |
| | 3240 | + }]; |
| | 3241 | + state.sync_workspace_visibility(); |
| | 3242 | + let screen = state.monitors[0].usable_frame; |
| | 3243 | + |
| | 3244 | + state |
| | 3245 | + .workspaces |
| | 3246 | + .get_or_create_target(&WorkspaceTarget::Numbered(2)); |
| | 3247 | + |
| | 3248 | + { |
| | 3249 | + let ws = state.workspaces.get_mut(1); |
| | 3250 | + ws.tree.insert_with_rect(10, None, screen); |
| | 3251 | + ws.tree.insert_with_rect(11, Some(10), screen); |
| | 3252 | + assert!(ws.tree.make_stack_for_window(11)); |
| | 3253 | + ws.tree.set_stack_active(10); |
| | 3254 | + ws.record_focus(10); |
| | 3255 | + } |
| | 3256 | + |
| | 3257 | + state.adopt_external_focus(11); |
| | 3258 | + |
| | 3259 | + assert_eq!(state.monitors[0].active_workspace, 1); |
| | 3260 | + assert_eq!(state.focused_monitor, 0); |
| | 3261 | + assert_eq!(state.active_workspace().focused, Some(11)); |
| | 3262 | + assert_eq!( |
| | 3263 | + state.active_workspace().tree.stack_info(11), |
| | 3264 | + Some((vec![10, 11], 1)) |
| | 3265 | + ); |
| | 3266 | + } |
| | 3267 | + |
| | 3268 | + #[test] |
| | 3269 | + fn external_focus_jumps_to_monitor_showing_window_workspace() { |
| | 3270 | + let mut state = WmState::new(); |
| | 3271 | + state.monitors = vec![ |
| | 3272 | + Monitor { |
| | 3273 | + id: 42, |
| | 3274 | + frame: Rect::new(0.0, 0.0, 1920.0, 1080.0), |
| | 3275 | + usable_frame: Rect::new(0.0, 33.0, 1920.0, 1047.0), |
| | 3276 | + is_primary: true, |
| | 3277 | + active_workspace: 0, |
| | 3278 | + }, |
| | 3279 | + Monitor { |
| | 3280 | + id: 43, |
| | 3281 | + frame: Rect::new(1920.0, 0.0, 1920.0, 1080.0), |
| | 3282 | + usable_frame: Rect::new(1920.0, 0.0, 1920.0, 1080.0), |
| | 3283 | + is_primary: false, |
| | 3284 | + active_workspace: 1, |
| | 3285 | + }, |
| | 3286 | + ]; |
| | 3287 | + state |
| | 3288 | + .workspaces |
| | 3289 | + .get_or_create_target(&WorkspaceTarget::Numbered(2)); |
| | 3290 | + state.sync_workspace_visibility(); |
| | 3291 | + let screen = state.monitors[1].usable_frame; |
| | 3292 | + |
| | 3293 | + { |
| | 3294 | + let ws = state.workspaces.get_mut(1); |
| | 3295 | + ws.tree.insert_with_rect(21, None, screen); |
| | 3296 | + ws.tree.insert_with_rect(22, Some(21), screen); |
| | 3297 | + assert!(ws.tree.make_stack_for_window(22)); |
| | 3298 | + ws.tree.set_stack_active(21); |
| | 3299 | + ws.record_focus(21); |
| | 3300 | + } |
| | 3301 | + |
| | 3302 | + state.focused_monitor = 0; |
| | 3303 | + state.adopt_external_focus(22); |
| | 3304 | + |
| | 3305 | + assert_eq!(state.focused_monitor, 1); |
| | 3306 | + assert_eq!(state.monitors[1].active_workspace, 1); |
| | 3307 | + assert_eq!(state.workspaces.get(1).focused, Some(22)); |
| | 3308 | + assert_eq!( |
| | 3309 | + state.workspaces.get(1).tree.stack_info(22), |
| | 3310 | + Some((vec![21, 22], 1)) |
| | 3311 | + ); |
| | 3312 | + } |
| | 3313 | + |
| 3186 | #[test] | 3314 | #[test] |
| 3187 | fn unstack_focused_restores_original_tree() { | 3315 | fn unstack_focused_restores_original_tree() { |
| 3188 | let mut state = WmState::new(); | 3316 | let mut state = WmState::new(); |