Skip to content

Treat emacs child frames as popups#2036

Open
jperras wants to merge 1 commit intonikitabobko:mainfrom
jperras:emacs-child-frame-detector-heuristics
Open

Treat emacs child frames as popups#2036
jperras wants to merge 1 commit intonikitabobko:mainfrom
jperras:emacs-child-frame-detector-heuristics

Conversation

@jperras
Copy link
Copy Markdown

@jperras jperras commented Mar 31, 2026

This fixes the focus jumping issue when using Emacs completion popups (corfu, posframes, etc.) with AeroSpace.

Originally I had implemented a very hack-y "solution" to this in #1914, but it only addressed the symptom, and not the root cause of the issue.

Emacs "child frames" (posframes, corfu completion popups, etc.) are a bit of an oddity in modern window-based UIs, but for Aerospace, they are effectively transient elements that should not be managed.

These child frames were being detected as dialogs, which caused AeroSpace to track them. When they were closed and GC triggered, a focus recalculation would occur, and then the focus could potentially jump to another application, depending on application/window layouts, and GC timing.

After @nikitabobko mentioned that aerospace debug-windows could be used to capture window properties (great tool/shortcut, btw!), it was actually pretty straightforward to adjust the isWindowHeuristic in AxUiElementWindowType.swift to account for the emacs-specific oddities.

Child frames now get detected as popups by checking for an AXSubrole of AXFloatingWindow, and AXMain with value 0 for the emacs bundle ID. This seems to be consistent behaviour for modern emacs (Emacs-Plus 30.2) on macOS.

Dumps I generated have been added to axDumps for the issue. I had to parse the output to extract out the app/PID prefixes and convert it to JSON, but the format seems to be the same as other examples in the dumps folder.

I added two window property dumps, one for when the focus change was triggered by a completion "popup" from a popular emacs library named corfu, and one for posframe, another popular package that does similar things with "popups", and is used by a lot of other packages that want the popup-like functionality.

Without the fix, the test suite fails:

Test suite failures _without_ the updated dialog heuristic
$ ./run-tests.sh
[1/1] Planning build
Building for debugging...
[224/224] Applying AeroSpaceApp
Build complete! (7.49s)
Building for debugging...
[35/35] Compiling AppBundleTests TreeNodeTest.swift
Build of target: 'AppBundleTests' complete! (1.28s)
[1/1] Planning build
Building for debugging...
[256/256] Linking AeroSpacePackagePackageTests
Build complete! (7.88s)
/Users/jperras/src/AeroSpace/Sources/AppBundleTests/assert.swift:74: error: -[AppBundleTests.AxWindowKindTest test] : failed -
/Users/jperras/src/AeroSpace/Sources/AppBundleTests/AxUiElementWindowTypeTest.swift:25: Assertion failed
    Additional Message:
        /Users/jperras/src/AeroSpace/axDumps/emacs_child_frame_posframe.json5:0:0: AxUiElementWindowType doesn't match
    Expected:
        Optional(AppBundle.AxUiElementWindowType.popup)
    Actual:
        Optional(AppBundle.AxUiElementWindowType.dialog)
/Users/jperras/src/AeroSpace/Sources/AppBundleTests/assert.swift:74: error: -[AppBundleTests.AxWindowKindTest test] : failed -
/Users/jperras/src/AeroSpace/Sources/AppBundleTests/AxUiElementWindowTypeTest.swift:25: Assertion failed
    Additional Message:
        /Users/jperras/src/AeroSpace/axDumps/emacs_child_frame_corfu.json5:0:0: AxUiElementWindowType doesn't match
    Expected:
        Optional(AppBundle.AxUiElementWindowType.popup)
    Actual:
        Optional(AppBundle.AxUiElementWindowType.dialog)
Test Case '-[AppBundleTests.AxWindowKindTest test]' failed (0.192 seconds).
Test Suite 'AxWindowKindTest' failed at 2026-03-31 11;58;04.073.
         Executed 1 test, with 2 failures (0 unexpected) in 0.192 (0.193) seconds
Test Suite 'AeroSpacePackagePackageTests.xctest' failed at 2026-03-31 11;58;04.442.
         Executed 112 tests, with 2 failures (0 unexpected) in 0.553 (0.562) seconds
Test Suite 'All tests' failed at 2026-03-31 11;58;04.442.
         Executed 112 tests, with 2 failures (0 unexpected) in 0.553 (0.563) seconds
◇ Test run started.
↳ Testing Library Version: 6.2.4 (5ee435b15ad40ec)
↳ Target Platform: arm64-apple-macosx
✔ Test run with 0 tests in 0 suites passed after 0.001 seconds.
❌ Swift tests have failed

and with the updated heuristic, the test suite passes without issue.

Thank you @nikitabobko for the guidance, here, on how to address the underlying issue instead of mucking about with focus recalculation logic.

Fixes #776
Fixes #1220
Refs. #1914

PR checklist

  • Explain your changes in the relevant commit messages rather than in the PR description. The PR description must not contain more information than the commit messages (except for images and other media).
  • Each commit must explain what/why/how and motivation in its description. https://cbea.ms/git-commit/
  • Don't forget to link the appropriate issues/discussions in commit messages (if applicable).
  • Each commit must be an atomic change (a PR may contain several commits). Don't introduce new functional changes together with refactorings in the same commit.
  • ./run-tests.sh exits with non-zero exit code.
  • Avoid merge commits, always rebase and force push.

Emacs child frames (posframes, corfu completion popups, etc.) are
transient UI elements that should not be managed by Aerospace.

Fixes nikitabobko#776
@jperras
Copy link
Copy Markdown
Author

jperras commented Mar 31, 2026

@nikitabobko I realize my PR description is pretty verbose compared to the actual commit message for the change. I'm happy to force push/change the commit message to be more descriptive, and/or amend the PR body, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant