@@ -1161,16 +1161,29 @@ impl WmState { |
| 1161 | if self.recent_internal_focus.is_empty() { | 1161 | if self.recent_internal_focus.is_empty() { |
| 1162 | return false; | 1162 | return false; |
| 1163 | } | 1163 | } |
| 1164 | - match requested { | 1164 | + // Ignore the event if either (a) the requested window is one we |
| 1165 | - Some(id) => self | 1165 | + // just internally focused — re-adopting it is at best a no-op — or |
| | 1166 | + // (b) the event's pid matches an app we just activated. Side-effect |
| | 1167 | + // AXFocusedWindowChanged notifications fire for *other* windows of |
| | 1168 | + // the activated app (e.g. activating Ghostty window A on monitor B |
| | 1169 | + // causes Ghostty to also emit FocusChanged for the previous key |
| | 1170 | + // window B in a stack on monitor A), and adopting those events |
| | 1171 | + // perpetually drags focus back into the stack we were trying to |
| | 1172 | + // mod+arrow out of. The 600ms TTL is short enough that genuine |
| | 1173 | + // user clicks on a sibling window of the same app within that |
| | 1174 | + // window are rare; if one does happen we miss it, which is |
| | 1175 | + // strictly less disruptive than the cross-monitor focus loop. |
| | 1176 | + if let Some(id) = requested |
| | 1177 | + && self |
| 1166 | .recent_internal_focus | 1178 | .recent_internal_focus |
| 1167 | .iter() | 1179 | .iter() |
| 1168 | - .any(|(eid, _, _)| *eid == id), | 1180 | + .any(|(eid, _, _)| *eid == id) |
| 1169 | - None => self | 1181 | + { |
| 1170 | - .recent_internal_focus | 1182 | + return true; |
| 1171 | - .iter() | | |
| 1172 | - .any(|(_, epid, _)| *epid == pid), | | |
| 1173 | } | 1183 | } |
| | 1184 | + self.recent_internal_focus |
| | 1185 | + .iter() |
| | 1186 | + .any(|(_, epid, _)| *epid == pid) |
| 1174 | } | 1187 | } |
| 1175 | | 1188 | |
| 1176 | fn is_external_focus_candidate(&self, id: WindowId) -> bool { | 1189 | fn is_external_focus_candidate(&self, id: WindowId) -> bool { |