Repository navigation
fix(macos): the donut opens and flips while another app holds Secure Input - #855
Conversation
…ds Secure Input While another app holds Secure Input (Terminal's Secure Keyboard Entry left on in the background, a password field), the donut's Option/Command gestures stopped in Filee: every press arrived without modifiers. SharpHook's libuiohook rebuilds its modifier mask on every event with CGEventSourceKeyState, which answers "up" for held keys while another app holds Secure Input. CGEventSourceFlagsState and the event's own flags stay right (what NSEvent.modifierFlags reports, and what other gesture apps read). - IPlatformServices.CurrentModifiers(): null by default; on macOS the flags state (CGEventSourceFlagsState). - TriggerService takes mouse gesture modifiers from it when present: at a press, and on moves while a button is held. Buttons still come from the hook. Windows and Linux are unchanged. - The Ctrl+Option+Space shortcut can't be helped: Secure Input withholds the key press itself from other apps. Checked on macOS 26.6 with a helper app holding Secure Input: without the change Option+drag does nothing, with it the donut opens. To drop once SharpHook reads the flags state itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
During a drag the donut heard the flip key only through the global hook, and while another app holds Secure Input (Terminal with Secure Keyboard Entry, which can keep it from the background) macOS gives no keys to a hook: Tab never flipped the donut. Measured: no key events reached the hook and CGEventSourceKeyState never read Tab down, while the app in front still got every key. On macOS the drag donut now takes the keyboard once files are dragged onto it, and Tab reaches its window like any key press; the drag carries on. The hook's copy of a press is ignored meanwhile (one flip per press). The keyboard goes back to the app that had it once a conversion starts or the donut closes, unless the user clicked elsewhere, and stays with Filee when the drop opens a window of its own (a tool, the merge order, the save dialog). Windows is unchanged: taking the keyboard there breaks Explorer's drag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks, @lightscape-jm, this is a careful piece of work: the measurements make both causes clear, and the "undone → caught by" table is exactly what I'd want for a change like this. I've started CI (first-time contributors need a maintainer to approve the run).
The code reads right to me. Commit 1 is contained: CurrentModifiers() is null everywhere but macOS, and on macOS it only replaces the mask at presses and held-button moves. Commit 2 changes how the drag donut behaves on every Mac, not only under Secure Input, so I'd like it tried on another Mac before merging. A few things I'd like checked there:
- Filee's own windows during a drag. With the main window or Settings open (behind Finder), Option-drag a picture onto the donut. Does Filee's other window stay where it was, or does it come to the front over Finder when the donut takes the keyboard?
- No Dock icon. The same drag and Tab flip with Show in Dock off (Filee as an accessory app), and Finder in front again after the drop.
- Without Secure Input. Tab flips exactly once per press (#504), and drops on lit slices still convert (#784).
@SeoJCalive 이 PR도 Mac에서 확인 부탁드려도 될까요? CI가 끝나면 portable-osx-arm64 아티팩트로 위 1–3번을 봐 주시면 돼요. 재현이 필요하면 PR 본문의 hold.swift로 백그라운드에서 Secure Input을 잡을 수 있어요(Option-드래그로 도넛이 뜨는지, Tab으로 뒤집히는지).
On the upstream note: an issue on libuiohook would be welcome if you'd like to open it yourself; we'll drop commit 1 once SharpHook reads the flags state.
…acos-secure-input # Conflicts: # src/Filee.Core/Platform/IPlatformServices.cs # src/Filee.Platform.MacOS/MacOSPlatformServices.cs
|
Thanks for the review and for starting CI! I merged Your three checks, on my Mac (macOS 26.6.2, Apple Silicon) with the merged branch alone and default settings:
That's the same Mac as before, so a second one is still welcome. Thanks, @SeoJCalive! On libuiohook: I'll open that issue there. |
|
@lightscape-jm Thanks for running the three checks and sorting out the #808 merge. I'll try it on my Mac too, including the Secure Input case with @KnifeLemon main을 합친 커밋( |
|
Thanks, @lightscape-jm. The #808 merge reads right: the donut gives the keyboard back before it hides, and I've approved CI for the merge commit ( @SeoJCalive CI 승인했어요. 빌드가 끝나면 |
|
@SeoJCalive 확인 부탁드려요. 위에서 Mac에서 이 세 가지만 알려 주시면 돼요.
결과만 주시면 바로 머지할게요. |
|
@KnifeLemon 늦어서 죄송해요. CI 승인하신 걸 놓쳤어요. 방금
참고로 이 시험은 키보드와 마우스를 직접 눌러서 했어요. 프로그램으로 보낸 키는 Secure Input 중에도 그대로 보여서, 그걸로는 이 상황이 재현되지 않더라고요. |
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @lightscape-jm, and thanks @SeoJCalive for the careful check on a real Mac (macOS 26.6.2, Apple silicon) with portable-osx-arm64 at 58a51ea:
- While another app holds Secure Input (
hold.swift, confirmed by polling: the Tab key never showed), Option-drag in Finder opened the donut, Tab flipped it each time, and the drop on JPG converted. - Focus went back to Finder after the drop.
- The log has only the drag, conversion and memory-clean-up lines: no warnings, no
_windowLayerContextstop, no crash report on quit.
CI is green, so merging.
Note
Standalone fix, based on
main(26cc434a), no dependency on any other PR. Two commits, each reviewable on itsown: the first makes the donut open, the second makes Tab flip it. Together they fix one problem; apart, the second
can't be tried, as the donut wouldn't open.
What does this change?
On macOS, while another app holds Secure Input, Filee's donut stops working:
Terminal holds Secure Input whenever Secure Keyboard Entry is on, and sometimes keeps it after it leaves the front.
Password fields hold it too. Anyone who uses Terminal, or Filee's Open in Terminal action, can hit this.
Both come from the same rule: while another app holds Secure Input, macOS hides keys from anything that watches the
keyboard from outside, which is how Filee's global hook works. Windows and Linux are unchanged.
Type of change
How to reproduce
You need something holding Secure Input while it's not the app in front. Either of these:
A helper (repeatable): save this as
hold.swift, runswift hold.swiftin Terminal, then switch to Finderwithin the minute. It holds Secure Input from the background, which is what Terminal did in the reports.
Terminal: tick Terminal → Secure Keyboard Entry. Terminal normally lets go when it leaves the front, so this
reproduces only when it doesn't (it happened twice in two days here, with no trigger found).
Then, in Finder, Option-drag a picture:
mainCommit 1: the gestures start (
read modifier keys…)Cause, measured: libuiohook (via SharpHook) rebuilds the modifier mask of every event from
CGEventSourceKeyState, which answers "up" for held keys while another app holds Secure Input. In a diagnostic build,Option was logged going down, and the next click, with Option still held, arrived with mask
None. Without SecureInput the same call says "down".
CGEventSourceFlagsStateand the event's own flags stay right (they are whatNSEvent.modifierFlagsreports).Change:
IPlatformServices.CurrentModifiers(): null by default; on macOS the flags state.TriggerServicetakes amouse gesture's modifiers from it when present: at a press, and on moves while a button is held (a modifier pressed
after the button). Buttons still come from the hook.
Not fixable here: the
Ctrl+Option+Spacekeyboard shortcut, as Secure Input withholds the key press itself.Commit 2: Tab flips (
the flip key works…)Cause, measured (diagnostic build, Secure Input held from the background; the donut open, waiting for the flip key):
CGEventSourceKeyState(kVK_Tab), polled every 15 msNo way of watching the keyboard sees Tab under Secure Input, but the app in front always gets its keys.
Change: on macOS the drag donut takes the keyboard once files are dragged onto it (
RadialController.TakeKeyboard),and Tab reaches its window like any key press. The drag carries on.
selection) takes nothing.
(
HookFlipsKey). That follows the window'sIsActivevalue throughPropertyChanged, not theActivatedevent,which can come before the value changes.
IPlatformServices.RememberFrontApp/ReturnFocus(NSWorkspace.frontmostApplication,NSRunningApplication.activateWithOptions:). The keyboard goes back once a conversion starts or the donut closes,unless the user has clicked into another app meanwhile. It stays with Filee when the drop opens a window of Filee's own
(a tool, the merge order, the save dialog of Ask every time), or that window would open behind Finder.
How was it tested?
dotnet format --verify-no-changesdotnet testfrom the background (the helper above).
Test suites, this branch and
mainat26cc434aon the same Mac:mainEvery failure is the same test on both, by name, so none comes from this PR. On this Mac:
First_run_engine_setup_renders(7 languages),Engines_page_shows_versions_descriptions_and_download_speed,EnginesPageTests.A_second_engine_can_be_installed_while_one_downloads_and_waits_its_turnandDonutHighlightTests.A_lost_animation_frame_does_not_keep_the_slice_dark.Encoder not found), and oneLibreOffice ODS / XLS round trip.
New and changed tests:
TriggerServiceLifecycleTests: a press takes the modifiers the platform reports when the hook's mask lost them; amodifier pressed after the button counts; without a platform reading (Windows, Linux) the hook's mask decides.
DonutFlipTests(macOS only, skipped elsewhere): the donut takes the keyboard only once files are on it; the window'spress alone flips (the Secure Input case); with the hook's copy too it flips once; after a click elsewhere the hook
flips again; the keyboard is given back after a conversion and kept for the crop tool's window.
Windows and Linux use.
Do the tests guard the fix? I undid each part and checked that a test fails every time:
A_press_takes_the_modifiers_the_platform_reports…,A_modifier_pressed_after_the_button_counts…A_modifier_pressed_after_the_button_counts…IsActive…stays_for_a_tools_windowOne of these caught a gap in my own test:
A_modifier_pressed_after_the_button_counts…passed without the move change,because the hook thread sometimes handled the press after the test had already set the modifier. It now waits for the
press first.
About #784 (drops on a lit slice sometimes not delivered on macOS): one suspect there is the donut window changing
during the drag, and commit 2 makes the window take the keyboard mid-drag. Measured on this Mac, drops on lit slices
without Secure Input, 15 drags each onto the same lit slice:
mainSo no drop was lost, but taking the keyboard does make macOS report the drag leaving and re-entering the donut more
often. I tried the alternative that avoids it: making the donut the key window without activating Filee (it is
already a non-activating panel). Tab arrived (30 / 30) and the extra leaves were gone (0), but the keyboard then stayed
with Filee after the drop, even with the donut closed, and asking Finder to take it back does nothing while Finder is
still the active app. I found no public way to hand it back, so this PR activates Filee and steps back afterwards. If
the extra leaves turn out to matter for #784, the key-window route is where I'd look.
Screenshots (UI changes)
No visual change.
Checklist
src/Filee.App/Assets/i18n/*.jsonfiles (none added)THIRD-PARTY-NOTICES.md(none added)Not in this PR
CGEventSourceFlagsStateitself; commit 1 can go once SharpHook does. I can openthat issue upstream if you'd like.
🤖 Generated with Claude Code