Skip to content

fix(macos): restore donut window class on hide and before close - #808

Merged
KnifeLemon merged 2 commits into
KnifeLemon:mainfrom
wavever:fix/macos-window-lifecycle
Oct 30, 2026
Merged

KnifeLemon merged 2 commits into
KnifeLemon:mainfrom
wavever:fix/macos-window-lifecycle

Conversation

@wavever

@wavever wavever commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What does this change?

Fixes #805. Keep the donut as AvnPanel while visible for full-screen support (#251). When it hides and before it closes, clear the nonactivating style and restore AvnWindow so native destruction matches its initialization. Restoration is idempotent.

The controller handles actual visibility changes and Closing, including quitting while the donut is still on screen. Tests cover both hooks and repeated native class/style transitions.

Type of change

  • Bug fix

How was it tested?

  • Formatting check for all changed C# files.
  • Platform tests: 96 passed, 27 skipped for other operating systems.
  • Relevant donut/controller/close tests: 100 passed.
  • macOS 15.6 ARM64: minimal AppKit swap → restore → release exits 0.
  • Native smoke driver using Filee's actual RadialController/RadialWindow: repeated show/hide, then quit while visible. Dock on/off × managed shutdown, native application termination and Quit Apple Event: all six cases exit 0.

Full-screen Finder/Safari drag check (#251): awaiting the collaborator's check on the revised build, as arranged in the review.

Checklist

  • No UI strings or third-party dependencies added.
  • One focused fix.

@wavever
wavever requested a review from KnifeLemon as a code owner October 30, 2026 08:11
@github-actions github-actions Bot added the bug Something isn't working label Oct 30, 2026
@SeoJCalive

SeoJCalive commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks for the fix, @wavever. I went through it and checked a few things on my Mac. Some notes:

  1. [MacOS] Donut Toolbar bug #251 comes back: with the swap removed, the donut can again stay behind another app's full-screen Space while Filee is in the Dock. In a minimal AppKit repro (macOS 26.6.2), swapping the class back to the NSWindow subclass before the window is released avoided the _windowLayerContext abort, both while it was hidden and while it was still on screen. So putting the window back to AvnWindow when the donut hides and before it closes might fix the crash and keep [MacOS] Donut Toolbar bug #251. I haven't checked the full-screen case with that change.
  2. Couldn't reproduce it in the app here: on a main build (macOS 26.6.2, Apple Silicon) I opened the donut with Option-drag several times and quit through the quit Apple Event, with the Dock icon shown and hidden, including quitting while the donut was on screen. Filee exited normally every time on my machine. That may just come down to macOS 27 or a longer session, so it would help to try the patched build on the setup where the crash happened.
  3. Popups aren't panels in Avalonia 12.1.3: the description says the native backend picks NSPanel for popups, but PopupImpl uses the default usePanel = false, and nothing in 12.1.3 passes true. So switching the donut to a popup wouldn't give a panel either.

One small request: next time, a short comment on the issue when you start on a fix would be much appreciated. I'd started looking into this one too, and it helps avoid two people working on the same thing.

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick PR, @wavever! The diagnosis is spot on, and the test doubles make a nice regression test for "the class stays the same".

The catch is the trade-off you describe: removing the swap brings back #251. With Filee in the Dock (the default), the donut no longer appears over a full-screen app, which is how many Mac users work. I'd rather keep both working, so before taking the removal, could you try the smaller fix?

Restore the class before the window is released

The abort happens because the window is deallocated as an AvnPanel that never ran NSPanel's initialization. If it is turned back into an AvnWindow before AppKit releases it, dealloc runs the path matching its init:

  • On close: add something like NativeMethods.RestoreWindow(nint window). If object_getClass(window) == AvnPanel, it clears the nonactivating style bit and sets the class back to AvnWindow. Call it when the donut window closes on macOS (the Closing / Closed path at quit). Your harness can check that release then exits 0.
  • Simpler alternative: restore it every time the donut hides, and swap only while it's on screen. The window is then never released or reused as a panel.

Please check

  • Your minimal AppKit program: swap, then restore, then release exits 0.
  • In Filee on macOS: show and hide the donut a few times, then quit (⌘Q, Dock → Quit, and logging out). No crash report.
  • #251: with the Dock icon on, Ctrl-drag over a full-screen app (Safari or Finder in full screen) still shows the donut there.

If restoring turns out not to be enough, say so and we'll weigh the removal against #251 again. I've approved the CI run. Thanks!

@wavever

wavever commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for checking this. You're right: removing the swap regresses #251, so this isn't a complete fix. I also got the PopupImpl/NSPanel behavior wrong—thanks for catching that.

Restoring the original class and style before teardown seems worth testing, while preserving full-screen behavior with both Dock settings. I can revise the PR in that direction. Does that align with what you're already working on?

Sorry for the duplicated effort; I'll coordinate on the issue before further changes.

@KnifeLemon

Copy link
Copy Markdown
Owner

Yes, please go ahead with that direction, @wavever. Your branch is the one to continue, and @SeoJCalive can test it on his Mac.

Change: put the window back to AvnWindow (and clear the nonactivating style) when the donut hides and before it closes. Keep the swap while it's on screen.

Please check:

Check Expect
Your minimal AppKit repro exits 0 with swap → restore → release
Filee: show/hide the donut a few times, then quit (⌘Q, Dock → Quit, Apple Event) with the Dock icon on and off no abort
#251: Ctrl/Option-drag over a full-screen app (Finder or Safari) with the Dock icon on the donut still appears there

Keep the regression test you wrote, adjusted: the class may be AvnPanel while shown, and must be AvnWindow again after the hide/close hook.

@SeoJCalive, since you couldn't reproduce it on 26.6.2, could you run the #251 full-screen check on the revised build when it's up?

@wavever wavever changed the title fix(macos): preserve native window class to prevent quit crash fix(macos): restore donut window class on hide and before close Oct 30, 2026
@SeoJCalive

Copy link
Copy Markdown
Collaborator

@wavever No worries, I'm not working on a separate fix, so carry on with this branch. Once the revised build is ready and CI is green, ping me and I'll run the #251 full-screen check and the quit tests on my Mac.

@KnifeLemon

Copy link
Copy Markdown
Owner

CI is green on the revised commit (restore the class on hide and before close). Thanks, @wavever.

@SeoJCalive 수정된 빌드가 준비됐고 CI도 모두 통과했어요. Mac에서 확인 부탁드려요:

  • [MacOS] Donut Toolbar bug #251 전체 화면 확인: Dock에 Filee가 있을 때와 없을 때 모두, 다른 앱의 전체 화면 Space 위에 도넛이 뜨는지
  • 종료 테스트: 도넛을 숨긴 뒤 종료, 도넛이 떠 있는 상태에서 종료 — 둘 다 _windowLayerContext 중단 없이 끝나는지

결과 알려주시면 리뷰 상태 정리하고 머지할게요.

@SeoJCalive

Copy link
Copy Markdown
Collaborator

@KnifeLemon 수정본(825ecbd7, CI의 portable-osx-arm64)을 제 Mac(Apple silicon, macOS 26.6.2)에서 확인했어요. 모두 문제없었습니다.

  1. [MacOS] Donut Toolbar bug #251 전체 화면: Finder를 전체 화면으로 띄우고 그 위에서 Option-드래그했어요. Dock에 Filee가 있을 때와 없을 때 모두 도넛이 전체 화면 Space 위에 떴습니다.
  2. 종료 테스트: 두 Dock 설정 모두에서 도넛을 숨긴 뒤 종료, 도넛이 떠 있는 상태에서 종료를 해 봤어요(종료는 quit Apple Event). 네 번 모두 정상 종료됐고, _windowLayerContext 중단이나 크래시 리포트는 없었습니다.
  3. 참고: 이 Mac에서는 수정 전 main 빌드로도 종료 중단이 재현되지 않았어요. 그래서 2번은 수정본에서 새로 생긴 문제가 없다는 확인이고, macOS 27에서 중단이 사라졌는지는 원래 환경에서 봐야 합니다.
Dock에 있을 때 Dock에 없을 때
Dock 표시 상태에서 전체 화면 Finder 위의 도넛 Dock 숨김 상태에서 전체 화면 Finder 위의 도넛

@wavever

wavever commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for testing this, @SeoJCalive. @KnifeLemon, I've also tested the revised build on macOS 27.0.1 and haven't seen the crash recur. Since the original crash was intermittent and I don't have reliable reproduction steps, I can't confirm it's fully resolved yet, but so far it looks good. I'll report back if it happens again.

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @wavever and @SeoJCalive. SeoJ's run on macOS 26.6.2 covers #251 (the donut over a full-screen Space, with and without the Dock icon) and the four quit cases, and wavever's run on macOS 27.0.1 hasn't shown the crash again. CI is green on every platform.

Since the crash was intermittent, this closes #805 on merge. @wavever, if it happens again, please reopen #805 with the crash report.

@KnifeLemon
KnifeLemon merged commit 4533309 into KnifeLemon:main Oct 30, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS: crash on quit in NSWindow KVO observer cleanup after raising the donut

3 participants