isaac chan · macos · hardware sensors shipped · v1.0.1 · audible

A switch with nothing behind it

FreeSlapMac 1.1.0 ships a “Lid Creaking Sound” toggle. The label is in the binary, the description is in the binary, the preference is in the binary — and there is no lid-sensor code and zero references to the creaking.mp3 sitting inside its own bundle. This is the missing half: a 385-line user-level helper that reads the MacBook lid-angle sensor at 20 Hz and plays that unused asset, packaged as an installer with a self-test.

helper
385 lines
sensor poll
20 Hz
trigger
8 °/s
retrigger
1.4 s
package
11.6 KB zip
root required
none
⬇ Download v1.0.1 (11.6 KB)install.sh · uninstall.sh · lidcreak.py · README · MIT Try the simulator Read every file (mirror)
the brief, in his words “ok make it work.” — after being told the toggle was dead UI, not a permissions problem.

01What was actually wrong

The investigation started from a sound that never played and a sensor that clearly existed. Both were true, and unrelated:

02Evidence: free vs paid, same machine

symbol / stringFreeSlapMac 1.1.0 (5)SlapMac (paid)why it matters
_creakingEnabled / lidCreakEnabled22the switch exists in both; only one has anything behind it
“Lid Creaking Sound” + description1 + 1presentthe free UI is a faithful copy of the paid UI
creaking.mp30presentthe asset ships but is never opened by the free build
LidAngle / LidAngleSensor04no sensor code in the free binary at all
CreakAudioEngine03no playback engine either
spank (slap detector)no lid code—it only watches the accelerometer (usage page 0xFF00)

03The sensor contract

fieldvaluehow it was found
usage page / usage0x20 / 0x8Aenumerating every 0xFF00-adjacent SPU HID device and reading each feature report
report typefeature report 1input reports are silent on this device; the feature report answers on demand
payload01 68 00 → 0x0068 = 104°byte 0 is the report ID, bytes 1–2 are the angle as uint16 little-endian
privilegesnoneIOHIDDeviceOpen + IOHIDDeviceGetReport as a normal user, no TCC prompt
behaviour on sleepdevice disappearsthe helper re-enumerates instead of dying; a closed lid reads as “unavailable”, not 0°

04The decision path

hinge moves
   │
   ▼
IOHIDDeviceGetReport(dev, kIOHIDReportTypeFeature, 1)     20 Hz, unprivileged
   │  01 68 00 → 104°
   ▼
speed = |angle − prev| / Δt                                ignore Δt > 0.5 s (sleep/stall)
   │
   ├── speed < 1.5 °/s ................. drift, log nothing
   ├── 1.5 ≤ speed < 8 °/s ............. movement, no creak
   └── speed ≥ 8 °/s
          │
          ├── last creak < 1.4 s ago ... rate-limited (one creak per movement burst)
          └── gate: _creakingEnabled true   app open or quit — it never played this
                 │
                 ▼
          afplay -t 1.8 -v clamp(1.0 + speed/40, 1.0, 2.0) creaking.mp3
                 │
                 ▼
          ~/Library/Logs/lidcreak.log   creak vol=1.82

05Constants, and why each one exists

constantvaluewhy it matters
POLL_HZ20fast enough that a 1 s lid swing is 20 samples; cheap enough to run forever (the agent idles at ~0% CPU)
SPEED_TRIGGER8 °/styping and desk vibration never exceed it; a deliberate lid push starts around 15–40 °/s. Measured swings: 6.6 → 46.5 °/s
RETRIGGER_S1.4one movement = one creak, without gating a genuine second push
SAMPLE_GAP_MAX0.5 safter a sleep or a stall, Δangle/Δt would be nonsense; the sample pair is discarded instead
gain curve1.0 + speed/40a slow push creaks quietly, a fast flop creaks loud. The asset is mastered at −30.5 dBFS mean / −6.2 dBFS peak, so 1.0 is barely there and 2.0 is the loudest setting that still cannot clip. v1.0.0 used 0.35–0.9 — inaudible, which is the whole bug below
afplay -t1.8 sthe asset is 5.136 s; the creak is the first 1.8 s, so a movement does not leave a tail behind it
launchd KeepAlivetruethe agent survives logout/login, app updates and crashes; ThrottleInterval 5 stops a crash loop from spinning

06What actually came out of the speaker

Not a claim — the helper's own log, one tilt of the screen:

20:31:03  move 80->85 deg  speed=32.9 deg/s  CREAK
20:31:03  creak vol=1.82
20:31:03  move 84->80 deg  speed=39.6 deg/s  skip:retrigger   (one creak per movement)
20:31:13  move 95->94 deg  speed=4.6  deg/s  skip:slow       (drift)

And the installer's self-test, which is what a new user sees at the end of install.sh:

lidcreak 1.0.1
LID ANGLE: 94.0
APP RUNNING: False  TOGGLE ON: True
SYSTEM OUTPUT: volume=60 muted=False
GAIN: 1.50  (afplay -v)
played creaking.mp3

07Audible, on the third try

v1.0.0 was published before it produced a sound, and the way it failed is worth writing down: every part worked, and the result was still silence.

1.0.1 fixes the three separately, because they are three separate lies: the gate now only reads the preference (the app can be quit), the gain runs 1.0–2.0 against the measured peak, and every skip is logged with its reason so "no sound" has an answer instead of a shrug. The helper also refuses to pretend: --self-test prints the output volume and mute state, and if the Mac is muted the daemon logs a warning instead of quietly succeeding.

checkresulthow
trigger logic, offline6/6--trigger-test: slow drift, 20 °/s, 136 °/s, toggle off, 0.5 s retrigger, 2 s retrigger
audible at 60% outputyes--prove plays the creak at gain 1.0 / 1.5 / 2.0 through the speakers; self-test reports volume=60 muted=False
creaks with the app quityesthe gate no longer consults pgrep; only _creakingEnabled decides
silence is explainedyesmuted/zero output is a warning line, and skips read skip:slow / skip:toggle off / skip:retrigger

It does not fix a muted Mac by itself, and it should not: a helper that turns your system volume up is the same behaviour that makes FreeSlapMac so annoying. It tells you instead — one warning in the log, one line in the self-test.

08Install, verify, remove

unzip freeslapmac-lidcreak-1.0.1.zip && cd freeslapmac-lidcreak
bash install.sh          # copies the helper, writes the LaunchAgent, self-tests
tail -f ~/Library/Logs/lidcreak.log     # tilt the screen: watch it fire
bash uninstall.sh        # agent, helper and logs gone; the app is untouched

Three files, no bundle surgery, no code-signing: the app is never modified, so a FreeSlapMac update cannot conflict with it — and uninstalling the helper restores the original silence.

why this shape

Patching the app bundle would have meant re-signing it, breaking updates, and gatekeeping the fix to my own machine. A user LaunchAgent + a 385-line Python file is installable by anyone, readable by anyone, and removable in one command.

09Failure modes

symptomcausehandling
silence while the lid movesthe Mac is muted or at volume 0 (the actual cause of the first 422 creaks)self-test prints SYSTEM OUTPUT: volume=… muted=…; the daemon warns once; --prove is the two-second check
log says skip:toggle offFreeSlapMac's own switch is offthat preference is the on/off control, exactly as the app's UI implies
log says skip:retriggera second movement within 1.4 sone creak per movement burst; deliberate, so a swing does not machine-gun
no sensor at allIntel Mac, desktop, or a model without the sensorinstaller reports it and exits 2 rather than installing a silent agent
log says “sensor unavailable”lid closed — the device leaves the HID treere-enumerate every 2 s; resume on wake without a restart
creak fires while typingdesk vibration above 8 °/sraise --trigger (or SPEED_TRIGGER); the log prints the °/s of every movement so the number is chosen from data
a stray creak after a sleepone huge Δangle across sleepSAMPLE_GAP_MAX discards sample pairs older than 0.5 s
honest limitation

Closing the lid will never creak. macOS suspends the machine as the lid approaches ~10°, so the sensor is gone before the hinge gets there. The creak fires while the lid moves — the same behaviour the paid app has. That is physics, not a line of code I forgot.

Also: this is a companion, not a patch. The toggle in FreeSlapMac is still dead UI; the helper merely reads it. If a future version renames the preference, the gate fails open (it treats a missing key as “on”), which is the safe direction — you would hear creaks with the switch off, not silence with it on.

10Evidence table

artifactbytessha-256
freeslapmac-lidcreak-1.0.1.zip11,60125857680092575e22fe784a9c38ca8fc458bcafe635b3b425050b86a70eb51a9
lidcreak.py (385 lines)13,8898deefa2135edab1778fdf69679ddf4316a529cf7406485d22e33907e3a715bc5
install.sh (109 lines)3,55221fd096883d8e42d0e42c36bc58b72b6dcc1841a7af3186b1d5e4e476e1be29c
uninstall.sh (25 lines)958ac0154c64c4753268bf57e1c0b7143c98b63f538156b27142420063e16321ddd
README.md (146 lines)6,936e6947210b34db90efb071113fec30057c246afd6a687df259ed54293d3d8fd92
LICENSE (MIT)1,369101f1c881f5f98511290ed22154316557590c8945ee804337c35f66bdfa44eca
mirror: every file above, served byte-identically—./freeslapmac-lidcreak-mirror/

Verified on macOS 26.6, MacBook Air M2 (Mac14,2), FreeSlapMac 1.1.0 (build 5), Python 3.9 (the /usr/bin/python3 the LaunchAgent runs). The installer was also dry-run against a sandbox $HOME and a test launchd label: install → self-test → uninstall, twice, exit 0 both times, with the live agent untouched.

11What is next