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.
The investigation started from a sound that never played and a sensor that clearly existed. Both were true, and unrelated:
0x20 / usage 0x8A, read as feature report 1. A live read returns
01 68 00 → 0x0068 → 104°. Reading it needs no root and triggers no
permission prompt.Lid Creaking Sound,
the sentence “Plays a creaking door sound when you open or close the lid.” and the
_creakingEnabled preference — and the string creaking.mp3 appears
0 times, LidAngle 0 times.Contents/Resources/Sounds/creaking.mp3,
150,816 bytes, 5.136 s, never referenced.LidAngleSensor ×4, CreakAudioEngine ×3,
lidCreakEnabled ×2. The free build shipped the switch without the engine.| symbol / string | FreeSlapMac 1.1.0 (5) | SlapMac (paid) | why it matters |
|---|---|---|---|
| _creakingEnabled / lidCreakEnabled | 2 | 2 | the switch exists in both; only one has anything behind it |
| “Lid Creaking Sound” + description | 1 + 1 | present | the free UI is a faithful copy of the paid UI |
| creaking.mp3 | 0 | present | the asset ships but is never opened by the free build |
| LidAngle / LidAngleSensor | 0 | 4 | no sensor code in the free binary at all |
| CreakAudioEngine | 0 | 3 | no playback engine either |
| spank (slap detector) | no lid code | — | it only watches the accelerometer (usage page 0xFF00) |
| field | value | how it was found |
|---|---|---|
| usage page / usage | 0x20 / 0x8A | enumerating every 0xFF00-adjacent SPU HID device and reading each feature report |
| report type | feature report 1 | input reports are silent on this device; the feature report answers on demand |
| payload | 01 68 00 → 0x0068 = 104° | byte 0 is the report ID, bytes 1–2 are the angle as uint16 little-endian |
| privileges | none | IOHIDDeviceOpen + IOHIDDeviceGetReport as a normal user, no TCC prompt |
| behaviour on sleep | device disappears | the helper re-enumerates instead of dying; a closed lid reads as “unavailable”, not 0° |
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
| constant | value | why it matters |
|---|---|---|
| POLL_HZ | 20 | fast enough that a 1 s lid swing is 20 samples; cheap enough to run forever (the agent idles at ~0% CPU) |
| SPEED_TRIGGER | 8 °/s | typing and desk vibration never exceed it; a deliberate lid push starts around 15–40 °/s. Measured swings: 6.6 → 46.5 °/s |
| RETRIGGER_S | 1.4 | one movement = one creak, without gating a genuine second push |
| SAMPLE_GAP_MAX | 0.5 s | after a sleep or a stall, Δangle/Δt would be nonsense; the sample pair is discarded instead |
| gain curve | 1.0 + speed/40 | a 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 -t | 1.8 s | the asset is 5.136 s; the creak is the first 1.8 s, so a movement does not leave a tail behind it |
| launchd KeepAlive | true | the agent survives logout/login, app updates and crashes; ThrottleInterval 5 stops a crash loop from spinning |
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
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.
trigger=YES lines from a
single session. The Mac's output was volume:0, muted:true — muted, so every one of those
afplay calls was rendered into nothing. The helper had no idea, and neither did the log:
it reported success because afplay exited 0.app_running() ran every trigger.
Fast lid moves of 61.1 and 136.3 °/s were logged as trigger=no — the same
text used for "the lid barely moved". FreeSlapMac was closed, and since the app never played this sound
in the first place, requiring it only ever produced silence with no explanation.creaking.mp3 is mastered 30 dB below the tap
sounds: mean −30.5 dBFS, peak −6.2 dBFS. At 0.35 gain the creak sits around −39 dBFS —
about the level of a laptop fan.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.
| check | result | how |
|---|---|---|
| trigger logic, offline | 6/6 | --trigger-test: slow drift, 20 °/s, 136 °/s, toggle off, 0.5 s retrigger, 2 s retrigger |
| audible at 60% output | yes | --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 quit | yes | the gate no longer consults pgrep; only _creakingEnabled decides |
| silence is explained | yes | muted/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.
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.
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.
| symptom | cause | handling |
|---|---|---|
| silence while the lid moves | the 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 off | FreeSlapMac's own switch is off | that preference is the on/off control, exactly as the app's UI implies |
log says skip:retrigger | a second movement within 1.4 s | one creak per movement burst; deliberate, so a swing does not machine-gun |
| no sensor at all | Intel Mac, desktop, or a model without the sensor | installer reports it and exits 2 rather than installing a silent agent |
| log says “sensor unavailable” | lid closed — the device leaves the HID tree | re-enumerate every 2 s; resume on wake without a restart |
| creak fires while typing | desk vibration above 8 °/s | raise --trigger (or SPEED_TRIGGER); the log prints the °/s of every movement so the number is chosen from data |
| a stray creak after a sleep | one huge Δangle across sleep | SAMPLE_GAP_MAX discards sample pairs older than 0.5 s |
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.
| artifact | bytes | sha-256 |
|---|---|---|
| freeslapmac-lidcreak-1.0.1.zip | 11,601 | 25857680092575e22fe784a9c38ca8fc458bcafe635b3b425050b86a70eb51a9 |
| lidcreak.py (385 lines) | 13,889 | 8deefa2135edab1778fdf69679ddf4316a529cf7406485d22e33907e3a715bc5 |
| install.sh (109 lines) | 3,552 | 21fd096883d8e42d0e42c36bc58b72b6dcc1841a7af3186b1d5e4e476e1be29c |
| uninstall.sh (25 lines) | 958 | ac0154c64c4753268bf57e1c0b7143c98b63f538156b27142420063e16321ddd |
| README.md (146 lines) | 6,936 | e6947210b34db90efb071113fec30057c246afd6a687df259ed54293d3d8fd92 |
| LICENSE (MIT) | 1,369 | 101f1c881f5f98511290ed22154316557590c8945ee804337c35f66bdfa44eca |
| 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.