@@ -1161,16 +1161,29 @@ impl WmState { |
| 1161 | 1161 | if self.recent_internal_focus.is_empty() { |
| 1162 | 1162 | return false; |
| 1163 | 1163 | } |
| 1164 | | - match requested { |
| 1165 | | - Some(id) => self |
| 1164 | + // Ignore the event if either (a) the requested window is one we |
| 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 | 1178 | .recent_internal_focus |
| 1167 | 1179 | .iter() |
| 1168 | | - .any(|(eid, _, _)| *eid == id), |
| 1169 | | - None => self |
| 1170 | | - .recent_internal_focus |
| 1171 | | - .iter() |
| 1172 | | - .any(|(_, epid, _)| *epid == pid), |
| 1180 | + .any(|(eid, _, _)| *eid == id) |
| 1181 | + { |
| 1182 | + return true; |
| 1173 | 1183 | } |
| 1184 | + self.recent_internal_focus |
| 1185 | + .iter() |
| 1186 | + .any(|(_, epid, _)| *epid == pid) |
| 1174 | 1187 | } |
| 1175 | 1188 | |
| 1176 | 1189 | fn is_external_focus_candidate(&self, id: WindowId) -> bool { |