Name the process that has the microphone - #22
Merged
Conversation
The prompt and the auto-record banner offered an Ignore button only when the
capture pid's executable sat inside an .app bundle. Two common clients don't:
a system daemon has no bundle at all, and proc_pidpath returns nothing for a
process whose binary was replaced under it, which is every self-updating app's
helper. Both produced a bare "Recording meeting" pill with no name and no way
to exclude it — measured live, macOS's speech daemon under "Hey Siri" reads as
`nil "nil"` on main.
Core Audio knows what the path doesn't. Every input stream carries a bundle id
of its own ('pbid', 0.02 ms to read), so identity now falls back to it,
resolves it through the installed copy and collapses it onto the outermost
.app: a helper reads as the app that owns it, under the same id the Settings
picker would have stored, and a daemon keeps its raw identifier with its last
component for a name. Only a bare executable is left anonymous, which is
honest — there is nothing to store.
Excluding an app then has to mean it, or the fix is cosmetic. A declined
client stays the one the detector follows, and following it is a cache hit
that skips the scan, so a process holding an input stream for hours hid every
other app behind it: after ignoring macOS's speech daemon, no real call could
be detected while Siri listened. An excluded client now drops out of the scan
like our own pid does, and the scan carries on.
Measured on an M4: device gate 0.11 ms, cached re-check 0.74 ms, full client
scan 3.8 ms at 32 clients — the comment claiming ~45 ms was stale, and is
updated. Nothing new runs while idle.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the reported bug: a recording that starts by itself gave no option to exclude the app behind it.
What was wrong
The Ignore button on the prompt and on the auto-record banner was gated on having a bundle id, and identity came only from
proc_pidpath-> outermost.app. Two common capture clients have neither:.appon its path at all — macOS's speech daemon holds an input stream continuously when "Hey Siri" is on;proc_pidpathreturns 0 bytes for a process whose binary was replaced under it, which is every self-updating app's helper.Either one produced a bare "Recording meeting" pill with no name and no Ignore, so there was nothing to click and no way to stop it recurring short of turning detection off.
A second defect made the exclusion list cosmetic even where it did match. A client we had answered about stayed the one the detector followed, and following it is a cache hit that short-circuits the scan — so a process that holds the microphone for hours hid every other app behind it, and no real call could be detected while it ran.
What changed
MeetingTitle.app(forPID:audioBundleID:)resolves identity from the executable path and falls back to Core Audio's own attribution (kAudioProcessPropertyBundleID, 0.02 ms, read only when prompting). The id is resolved through the installed copy and collapsed onto the outermost.app, so a helper reads as the app that owns it under the id the Settings picker would have stored, and a daemon keeps its raw identifier with its last component for a name. A bare executable stays anonymous — there is genuinely nothing to store.MeetingDetector.ignoreCurrentClient()drops an excluded client out of the scan like our own pid, so the scan carries on to whatever else has the microphone. Held by pid, cleared when the microphone goes idle so config edits take effect.MeetingApp?through; the pill and prompt take their name and Ignore label from it.Measured (M4, p50)
Nothing new runs while idle; the extra read happens only on the poll that prompts.
Verification
Before/after with harnesses compiled from the real sources, against live capture clients,
holdA/holdBbeing bare executables that take the mic:On the installed build, read out of the accessibility tree and clicked through System Events:
That last line is the positive control for the second fix: with two ignored clients on the microphone, a fresh one is still detected, which
maincannot do.Local gate green:
swift test,swift build -c release --arch arm64, the three smoke subcommands and the embedded__info_plistcheck.Version bumped to 0.3.1 with release notes in
CHANGELOG.md.