@@ -820,26 +820,46 @@ impl WmState { |
| 820 | 820 | fn focus_window_impl(&mut self, id: WindowId, activate_app: bool) { |
| 821 | 821 | if let Some(ax_ref) = self.ax_refs.get(&id) { |
| 822 | 822 | if activate_app { |
| 823 | | - // Full activation sequence for reliable cross-monitor focus: |
| 824 | | - // 1. Set AXFrontmost on the app-level AX element |
| 825 | | - // 2. NSRunningApplication.activate (brings app to foreground) |
| 826 | | - // 3. AXRaise (brings window to front of app's stack) |
| 827 | | - // 4. Set AXMain on window (makes it the key window) |
| 828 | | - // 5. Set AXFocused on window (tells AX this is focused) |
| 829 | | - // |
| 830 | | - // Setting AXFrontmost on the app AND calling activate_app |
| 831 | | - // covers both same-app (WezTerm→WezTerm) and cross-app cases. |
| 832 | | - if let Some(w) = self.registry.get(id) { |
| 833 | | - let app_ref = unsafe { |
| 834 | | - objc2_application_services::AXUIElement::new_application(w.app_pid) |
| 835 | | - }; |
| 836 | | - let frontmost_key = |
| 837 | | - objc2_core_foundation::CFString::from_static_str("AXFrontmost"); |
| 838 | | - let _ = |
| 839 | | - crate::platform::accessibility::ax_set_bool(&app_ref, &frontmost_key, true); |
| 840 | | - crate::platform::application::activate_app(w.app_pid); |
| 823 | + let is_floating = self.active_workspace().is_floating(id); |
| 824 | + let has_floating = !self.active_workspace().floating.is_empty(); |
| 825 | + |
| 826 | + if is_floating || !has_floating { |
| 827 | + // Full activation: no floating windows to protect, or we're |
| 828 | + // focusing a floating window itself — safe to raise. |
| 829 | + if let Some(w) = self.registry.get(id) { |
| 830 | + let app_ref = unsafe { |
| 831 | + objc2_application_services::AXUIElement::new_application(w.app_pid) |
| 832 | + }; |
| 833 | + let frontmost_key = |
| 834 | + objc2_core_foundation::CFString::from_static_str("AXFrontmost"); |
| 835 | + let _ = crate::platform::accessibility::ax_set_bool( |
| 836 | + &app_ref, |
| 837 | + &frontmost_key, |
| 838 | + true, |
| 839 | + ); |
| 840 | + crate::platform::application::activate_app(w.app_pid); |
| 841 | + } |
| 842 | + let _ = ax_perform_action(ax_ref, "AXRaise"); |
| 843 | + } else { |
| 844 | + // Focusing a tiled window while floating windows exist: |
| 845 | + // activate the app for keyboard input but DON'T AXRaise, |
| 846 | + // so the tiled window stays behind floating windows. |
| 847 | + if let Some(w) = self.registry.get(id) { |
| 848 | + let app_ref = unsafe { |
| 849 | + objc2_application_services::AXUIElement::new_application(w.app_pid) |
| 850 | + }; |
| 851 | + let frontmost_key = |
| 852 | + objc2_core_foundation::CFString::from_static_str("AXFrontmost"); |
| 853 | + let _ = crate::platform::accessibility::ax_set_bool( |
| 854 | + &app_ref, |
| 855 | + &frontmost_key, |
| 856 | + true, |
| 857 | + ); |
| 858 | + crate::platform::application::activate_app(w.app_pid); |
| 859 | + } |
| 860 | + // Skip AXRaise — floating windows stay visually on top |
| 841 | 861 | } |
| 842 | | - let _ = ax_perform_action(ax_ref, "AXRaise"); |
| 862 | + |
| 843 | 863 | let main_key = objc2_core_foundation::CFString::from_static_str("AXMain"); |
| 844 | 864 | let _ = crate::platform::accessibility::ax_set_bool(ax_ref, &main_key, true); |
| 845 | 865 | let focused_key = objc2_core_foundation::CFString::from_static_str("AXFocused"); |
@@ -995,8 +1015,21 @@ impl WmState { |
| 995 | 1015 | /// Called after every focus change since app activation can reset ordering. |
| 996 | 1016 | fn enforce_floating_levels(&self) { |
| 997 | 1017 | use crate::platform::skylight::{K_CG_FLOATING_WINDOW_LEVEL, set_window_level}; |
| 998 | | - for fw in &self.active_workspace().floating { |
| 999 | | - set_window_level(fw.id, K_CG_FLOATING_WINDOW_LEVEL); |
| 1018 | + // Reassert floating level on all visible workspaces (all monitors) |
| 1019 | + for monitor in &self.monitors { |
| 1020 | + for fw in &self.workspaces.get(monitor.active_workspace).floating { |
| 1021 | + set_window_level(fw.id, K_CG_FLOATING_WINDOW_LEVEL); |
| 1022 | + } |
| 1023 | + } |
| 1024 | + // Also cover active special/scratchpad workspaces |
| 1025 | + for special_idx in self.active_specials.iter().flatten() { |
| 1026 | + for fw in &self.workspaces.get(*special_idx).floating { |
| 1027 | + set_window_level(fw.id, K_CG_FLOATING_WINDOW_LEVEL); |
| 1028 | + } |
| 1029 | + // Special workspace tiled windows also float visually |
| 1030 | + for wid in self.workspaces.get(*special_idx).tree.windows() { |
| 1031 | + set_window_level(wid, K_CG_FLOATING_WINDOW_LEVEL); |
| 1032 | + } |
| 1000 | 1033 | } |
| 1001 | 1034 | } |
| 1002 | 1035 | |