Skip to content

Latest commit

 

History

History
63 lines (44 loc) · 13 KB

File metadata and controls

63 lines (44 loc) · 13 KB

macOS API research

Research date: 2026-09-08. Target: a native, unprivileged macOS 14+ menu-bar app. Checked Apple's current documentation and the installed macOS 26.5 SDK headers before implementing the sensor/background adapters. Build validation uses Xcode 26.6 on macOS 26.5.1. API availability does not establish hardware behavior; manual testing is still necessary.

Input and global shortcut

  • Quartz Event Services and CGEvent.tapCreate support a passive (listenOnly) session tap. V1 uses cgSessionEventTap, not the root/HID entry point mentioned in older examples.
  • CGPreflightListenEventAccess and CGRequestListenEventAccess are declared in the current CoreGraphics/CGEvent.h, available since macOS 10.15. Tripwire preflights permission and checks actual tap creation. The permission button may open System Settings; the privacy-pane deep link is a convenience, not a sensor API contract. The user can navigate to Privacy & Security manually if that link changes.
  • The tap listens for keyboard/modifier, pointer/button/drag, and scroll events. It does not read or persist typed characters, raw trackpad contact, or other applications' content. A main-run-loop callback queues work and returns the original event unchanged.
  • Current CarbonEventsCore.h explicitly states that Secure Event Input restricts keyboard delivery to the focused app. Tripwire does not bypass it; a functioning tap is not proof of coverage inside Secure Input or the login window. The health check detects tap disablement and permission loss, not every OS suppression of events.
  • The installed SDK's CarbonEvents.h still declares RegisterEventHotKey and UnregisterEventHotKey, without a deprecation annotation on those functions. V1 uses this public API for one exclusive Control + Option + Command + T shortcut. Most of Carbon is historical; this specific API is still present. Registration errors are shown. This avoids adding a package dependency or an active event-filtering permission solely for a shortcut.

Charger and wake

  • IOPSNotificationCreateRunLoopSource provides notifications for changes to power sources. IOPSCopyPowerSourcesInfo and IOPSGetProvidingPowerSourceType supply the current public AC/battery/UPS state. V1 triggers on a known transition away from AC and preserves its last known value across unavailable readings. Battery percentage changes and repeated battery notifications are not triggers.
  • NSWorkspace.didWakeNotification and screensDidWakeNotification are observed on the workspace notification center. V1 does not use invented Darwin notification names or parse pmset logs for a wake cause.
  • willSleepNotification lets V1 cancel a pending countdown. An already armed process retains state in memory across sleep, then triggers on a delivered wake. The notification is not permission to keep running indefinitely during sleep.

Background execution and closed lid

  • PreventUserIdleSystemSleep prevents automatic idle system sleep. The current header's preferred spelling is kIOPMAssertPreventUserIdleSystemSleep, used here with IOPMAssertionCreateWithName. It does not prevent lid-close, explicit, low-battery, or other forced sleep. Display sleep remains possible.
  • ProcessInfo.beginActivity with userInitiatedAllowingIdleSystemSleep keeps this user-requested task active against App Nap; the separate IOKit assertion controls idle sleep. Both are released on cancellation/disarm.
  • A retained app controller, LSUIElement, and a menu-bar popover provide background lifetime while the process is running. No helper, launch daemon, deprecated keep-awake technique, root access, or global pmset change is needed for V1.
  • Actual sleep suspends the process and hardware required by an alarm/camera. A daemon cannot remove that hardware limitation. Closed-display desktop mode may keep some Macs awake, but V1 does not depend on it. Opening a lid in an already-awake closed-display setup need not yield a wake notification.

Lid and movement: experimental hardware adapter

  • CMMotionManager is explicitly annotated API_UNAVAILABLE(macos) in the installed CoreMotion/CMMotionManager.h. There is still no documented general-purpose native Mac chassis-motion or lid-angle API. This does not mean the hardware is absent.
  • Version 0.2 adds an isolated, experimental TripwireHardware adapter using the public IOKit/HID entry points and undocumented AppleSPU device reports and driver properties. Public functions do not make this hardware protocol an Apple-supported contract. It may stop working after an OS update, fail under sandboxing, or be absent on other models. No private framework symbols, old sudden-motion interface, SMC calls, root helper, or clamshell registry polling are used.
  • Primary protocol references: LidAngleSensor by Sam Henri Gold and apple-silicon-accelerometer by olvvier. These are hardware research implementations, not Apple API guarantees. Tripwire implements its own narrow adapter and testable detection logic.
  • Lid: enumerate AppleSPUHIDDevice, match primary usage page 0x20, usage 0x8A, open non-exclusively, and request feature report ID 1. Accept only three bytes: report ID 1 followed by a little-endian angle from 0 to 360 degrees. Poll at approximately 20 Hz while checking/arming/armed.
  • Movement: match page 0xFF00, usage 3, register a HID input-report callback, and accept the observed 22-byte layout. Signed little-endian 32-bit axes at offsets 6, 10, and 14 are scaled by 65,536 to g. Reject implausible readings and unexpected lengths. Merely opening the device did not produce reports on the development Mac.
  • To start motion reports, temporarily request SensorPropertyReportingState=1, SensorPropertyPowerState=1, and ReportInterval=10000 on matching accelerometer AppleSPUHIDDriver services only. These driver properties are undocumented, shared system state. Save old values; restore them on normal cancellation, disarm, check completion, or Quit, only when they still equal our request. For an initially absent property, restoration requests numeric zero (off); this does not remove the registry key. A crash or force quit can prevent restoration. Another client making the same request cannot be distinguished; do not run competing SPU sensor tools while protecting the Mac.
  • The hardware session owns its devices, buffer, and run loop on a dedicated thread. Replacement sessions wait for earlier cleanup; ordinary app termination awaits cleanup. It never requests privilege escalation. Permission failures or absent reports produce unavailable, not simulated movement coverage.
  • Baselines follow the arming countdown and freeze when protection arms. Motion needs at least 20 stationary gravity samples (rolling window up to 40); hinge changes of at least 3° for 80 ms or acceleration-vector changes of at least 0.08 g for 40 ms trigger. Comparing the full gravity vector detects tilt even without a change in its magnitude. The thresholds filter jitter; they are not a guarantee of detecting every small, smooth translation or vibration.
  • Once-working streams going silent for over two seconds report failure. A still-calibrating sensor prevents arming; an unavailable optional sensor does not disable input/power/wake protection. A sensor appearing only after arming has no baseline and is not marked ready. A stopped UI check shows its last readings, not active protection.
  • Observed on Mac16,5 / macOS 26.5.1, 2026-09-08: a live lid angle of roughly 123° and approximately 100 accelerometer samples/second, without sudo. A resting vector was approximately (0.003, 0.014, -0.986) g. This verifies device access and live data, not an attended end-to-end movement/alarm test or compatibility with all Apple silicon Macs. Intel binaries are provided, but these dedicated SPU sensors are not promised on Intel.
  • Wake remains a separate public signal. Actual system sleep suspends this adapter just as it suspends audio/camera. Direct hinge support does not make closed-lid/sleep protection continuous.

Camera

  • AVCaptureDevice, requestAccess, and AVCaptureVideoDataOutput provide supported discovery, permission, and frame delivery. V1 chooses a built-in wide-angle camera, preferring a .front position when reported. It does not silently choose an iPhone/Continuity or virtual camera.
  • NSCameraUsageDescription explains armed camera use. The hardened runtime camera entitlement permits capture once the user grants access. No microphone entitlement or recording is used.
  • V1 keeps an authorized capture session warm, verifies an actual frame before countdown, and saves three JPEG frames on trigger using Core Image. It does not need AVCapturePhotoOutput to save photographs; video-frame capture reduces shutter/startup delay. Capture and encoding use a serial queue rather than the UI thread. Immediate means the next delivered frame, not a real-time latency guarantee.
  • The current AVCaptureDevice.h documents isSuspended, including examples of a privacy iris or a closed notebook display suspending a camera. Camera suspension is not itself a reliable lid sensor. Lock, other camera clients, device suspension, or camera-service interruption may prevent pictures. A timeout records this explicitly.

Alarm and authentication

  • AVAudioEngine, AVAudioPlayerNode, and a generated PCM buffer supply the looping siren. kAudioOutputUnitProperty_CurrentDevice selects the engine output without changing the user's global default output. Core Audio device/property discovery prefers built-in speakers and checks whether gain/mute properties exist and are writable before changing them. Prior values are restored on normal stop. No audio API can promise audible output from unavailable, physically muted, damaged, or disconnected hardware.
  • LAPolicy.deviceOwnerAuthentication supports macOS Touch ID, password fallback, and eligible Apple Watch owner authentication. V1 checks policy availability before arming and evaluates a fresh context on every armed/triggered disarm. The alarm continues during the prompt. App-level authentication cannot prevent force quit or shutdown.

Explicitly not implemented

No network, phone notifications, account, background launch registration, restart re-arming, privileged anti-termination helper, keylogging, location tracking, microphone recording, continuous video storage, or lock-screen bypass. These omissions keep the first version's behavior small and reviewable.

Architecture and evidence

TripwireCore contains the state machine, injected service protocols, event storage, report parsing, and deterministic sensor thresholds. TripwireHardware owns the experimental HID session. Tripwire supplies the supported macOS adapters and retained menu-bar UI. Closing the popover never releases the protection controller.

EventObserving.eventDidChange(_:) is the future phone-notification extension point. A sender should enqueue work, deduplicate by event ID, and keep network failures independent of the local alarm. No sender is included now.

Events have UUIDs, UTC ISO 8601 arm/trigger/disarm timestamps, trigger reasons, per-photo timestamps, capture status, and diagnostics. JSON updates are atomic. Directories use mode 0700, files 0600. Photos are three JPEG video frames, roughly 0.8 seconds apart, with a 12-second capture deadline. Disarming can stop an unfinished burst. A camera/storage failure does not intentionally silence the siren. These ordinary local files are not encrypted separately, immutable, or automatically deleted; FileVault protects data at rest.