Skip to content

feat: use PowerAssertionDetector for meeting detection#30

Merged
pasrom merged 14 commits intomainfrom
feat/power-assertion-detector
Mar 17, 2026
Merged

feat: use PowerAssertionDetector for meeting detection#30
pasrom merged 14 commits intomainfrom
feat/power-assertion-detector

Conversation

@pasrom
Copy link
Copy Markdown
Owner

@pasrom pasrom commented Mar 16, 2026

Summary

  • Extract MeetingDetecting protocol from MeetingDetector for testability and build variant flexibility
  • Add PowerAssertionDetector that detects meetings via IOKit power assertions + window title lookup — works in sandbox without Screen Recording
  • Use PowerAssertionDetector as default detector for both Homebrew and App Store variants
  • Remove Teams AX verification from MeetingDetector (redundant with power assertion approach)
  • Add power assertion to MeetingSimulator for end-to-end testing
  • Show build variant (Homebrew/App Store) in Settings version string
  • Migrate WatchLoop tests to use PowerAssertionDetector

Test plan

  • swift build passes (Homebrew variant)
  • swift build -Xswiftc -DAPPSTORE passes (App Store variant)
  • 520 tests pass (23 new PowerAssertionDetector tests)
  • Lint clean (no serious violations)

@github-actions github-actions bot added the enhancement New feature or request label Mar 16, 2026
pasrom added 13 commits March 16, 2026 22:04
Introduce MeetingDetecting protocol with checkOnce(), isMeetingActive(),
and reset() methods. Move DetectedMeeting struct to dedicated file.
MeetingDetector now conforms to the protocol, WatchLoop depends on the
abstraction. Prepares for alternative detector implementations.
Uses IOPMCopyAssertionsByProcess() to detect active meetings via power
assertions. Meeting apps create PreventUserIdleDisplaySleep assertions
during calls (e.g. "Microsoft Teams Call in progress"). Sandbox-safe,
no entitlement needed. Supports Teams, Zoom, and Webex.

Adds AppMeetingPattern.forAppName() lookup helper.
APPSTORE build uses PowerAssertionDetector (sandbox-safe),
Homebrew build uses MeetingDetector (CGWindowList). Screen Recording
permission check and UI row hidden in App Store variant.
18 tests covering Teams/Zoom/Webex detection, non-meeting assertion
filtering, confirmation threshold, cooldown, isMeetingActive, case
insensitivity, and reset behavior. Uses injected assertionProvider.
Real-world testing revealed:
- Teams new client uses process name "MSTeams" (not "Microsoft Teams")
- "Microsoft Teams Call in progress" is the reliable assertion (only during calls)
- "Video Wake Lock" persists after call ends (Electron artifact) — removed
- "Playing audio" is intermittent — removed

Also: wrap Accessibility permission check with #if !APPSTORE (not needed
in sandbox), remove debug logging added during testing.
Screen Recording is still needed for CATapDescription (app audio capture)
in both variants. Only the meeting detection method differs:
- Homebrew: CGWindowList (needs Screen Recording)
- App Store: PowerAssertionDetector (no Screen Recording needed)

Adjust permission detail text per variant.
Uses CGWindowListCopyWindowInfo to look up the actual window title
for detected meeting apps, so protocol filenames use the real meeting
name instead of the power assertion name. Falls back to assertion
name when no matching window is found.

Injects windowListProvider closure for testability.
PowerAssertionDetector works reliably and is sandbox-safe. Remove
the #if APPSTORE/#else split — both build variants now use power
assertion detection. Removes accessibility check (only needed for
AX DOM verification) and MeetingSimulator pattern setup.
The Electron AX DOM parsing (verifyTeamsMeeting, findMeetingControl,
meetingDOMIdentifiers) was fragile and no longer needed — power
assertion detection has no false positives from chat windows.
Removes the meetingVerifier closure and ApplicationServices import.
Tests for PowerAssertionDetector window title lookup:
- Title found via CGWindowList → used as windowTitle
- No matching window → assertion name as fallback
- Empty/app-name-only titles skipped

Also removes meetingVerifier from WatchLoopE2ETests (AX
verification was removed in previous commit).
- Reuse MeetingDetector.systemWindowList() instead of duplicating it
- Remove unused lastDetectedAppName property
- Remove duplicate MARK comment in tests
Replace MeetingDetector with PowerAssertionDetector in all WatchLoop
and E2E tests to match the new default detector. Uses injected
assertionProvider instead of windowListProvider for mock control.
@pasrom pasrom force-pushed the feat/power-assertion-detector branch 2 times, most recently from dae981f to bbbc74a Compare March 16, 2026 21:10
MeetingSimulator now creates a PreventUserIdleDisplaySleep power
assertion with keyword "Simulator Meeting Call in progress", so
PowerAssertionDetector can detect it. Added matching AssertionPattern
for process name "meeting-simulator".

- Reference AppMeetingPattern.simulator.appName instead of
  duplicating the "MeetingSimulator" string literal
- Release power assertion in applicationWillTerminate
@pasrom pasrom force-pushed the feat/power-assertion-detector branch 2 times, most recently from d86fbf5 to b514c54 Compare March 16, 2026 21:34
@pasrom pasrom merged commit 3103eaa into main Mar 17, 2026
10 checks passed
@pasrom pasrom deleted the feat/power-assertion-detector branch March 17, 2026 05:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant