These are copies, not re-exports: each row's sha-256 is the hash of the file you download here and of the same file inside the zip. Both the copy in this folder and the copy served through this page are checked against the package before it is published.
zip sha-256 25857680092575e22fe784a9c38ca8fc458bcafe635b3b425050b86a70eb51a9
| file | bytes | lines | sha-256 of the served copy |
|---|---|---|---|
| lidcreak.py | 13,889 | 385 | 13889 |
| install.sh | 3,552 | 109 | 3552 |
| uninstall.sh | 958 | 25 | 958 |
| README.md | 6,936 | 146 | 6936 |
| LICENSE | 1,369 | 28 | 1369 |
#!/usr/bin/env python3
"""lidcreak — make FreeSlapMac's dead "Lid Creaking Sound" switch actually creak.
Reads the MacBook's hidden lid-angle sensor (IOHID usage page 0x20 / usage 0x8A,
feature report 1) and plays FreeSlapMac's Sounds/creaking.mp3 while the lid moves.
v1.0.1 — two fixes over 1.0.0:
* no longer gated on FreeSlapMac being open (it never was the thing playing the
creak, so requiring it only ever produced silence with the app quit),
* real gain (1.0-2.0 via afplay -v) because creaking.mp3 is mastered ~30 dB
below the other sounds; the old 0.35 volume was inaudible even at full system
volume, and a muted/zero system output now gets reported instead of ignored.
Gated by FreeSlapMac's own toggle (pref _creakingEnabled in com.devesh.freeslapmac),
so the switch inside the app really does control it.
No root, no kext, no code signature changes: feature-report reads are unprivileged.
Usage:
lidcreak.py run the daemon (what launchd does)
lidcreak.py --self-test sensor + audio report, play one creak
lidcreak.py --prove play three creaks, quiet -> loud, to pick a level
lidcreak.py --trigger-test exercise the trigger logic offline (no audio)
lidcreak.py --watch print live angle + speed to stdout (tuning mode)
Options:
--app PATH FreeSlapMac.app location (default: auto-detect)
--trigger DEG_S lid speed that counts as a creak (default 8.0 deg/s)
--volume GAIN fixed gain 1.0-2.0 (default: 1.0 + speed/40, capped 2.0)
--sound PATH override the mp3
"""
import ctypes
import ctypes.util
import os
import subprocess
import sys
import time
VERSION = "1.0.1"
BUNDLE = "com.devesh.freeslapmac"
PREF_KEY = "_creakingEnabled"
PAGE, USAGE = 0x20, 0x8A # lid angle sensor
POLL_HZ = 20
SPEED_TRIGGER = 8.0 # deg/sec of lid movement that counts
RETRIGGER_S = 1.4
SAMPLE_GAP_MAX = 0.5 # ignore deltas across sleep/stall
GAIN_MIN, GAIN_MAX = 1.0, 2.0 # afplay -v gain (creaking.mp3 is quiet)
HOME = os.path.expanduser("~")
LOG = os.path.join(HOME, "Library/Logs/lidcreak.log")
CANDIDATE_APPS = [
"/Applications/FreeSlapMac.app",
os.path.join(HOME, "Applications/FreeSlapMac.app"),
"/Applications/FreeSlapMac (free).app",
]
IOKit = ctypes.cdll.LoadLibrary(ctypes.util.find_library("IOKit"))
CF = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreFoundation"))
kCFAllocatorDefault = ctypes.c_void_p.in_dll(CF, "kCFAllocatorDefault")
CF.CFStringCreateWithCString.restype = ctypes.c_void_p
CF.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32]
CF.CFNumberCreate.restype = ctypes.c_void_p
CF.CFNumberCreate.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
CF.CFDictionaryCreate.restype = ctypes.c_void_p
CF.CFDictionaryCreate.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p),
ctypes.POINTER(ctypes.c_void_p), ctypes.c_long,
ctypes.c_void_p, ctypes.c_void_p]
IOKit.IOHIDManagerCreate.restype = ctypes.c_void_p
IOKit.IOHIDManagerCreate.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
IOKit.IOHIDManagerSetDeviceMatching.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
IOKit.IOHIDManagerCopyDevices.restype = ctypes.c_void_p
IOKit.IOHIDManagerCopyDevices.argtypes = [ctypes.c_void_p]
CF.CFSetGetCount.restype = ctypes.c_long
CF.CFSetGetCount.argtypes = [ctypes.c_void_p]
CF.CFSetGetValues.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p)]
IOKit.IOHIDDeviceOpen.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
IOKit.IOHIDDeviceGetReport.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_long,
ctypes.c_char_p, ctypes.POINTER(ctypes.c_long)]
IOKit.IOHIDDeviceClose.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
kCFNumberIntType = 9
kCFStringEncodingUTF8 = 0x08000100
def find_app():
for p in CANDIDATE_APPS:
if os.path.isdir(p):
return p
return None
def opt(flag, default):
"""--flag VALUE (also --flag=VALUE)"""
for i, a in enumerate(sys.argv):
if a == flag and i + 1 < len(sys.argv):
return sys.argv[i + 1]
if a.startswith(flag + "="):
return a.split("=", 1)[1]
return default
APP = opt("--app", None) or find_app() or "/Applications/FreeSlapMac.app"
CREAK = opt("--sound", os.path.join(APP, "Contents/Resources/Sounds/creaking.mp3"))
SPEED_TRIGGER = float(opt("--trigger", SPEED_TRIGGER))
FIXED_VOLUME = opt("--volume", None)
VERBOSE = "--watch" in sys.argv
def log(msg):
line = f"{time.strftime('%Y-%m-%d %H:%M:%S')} {msg}\n"
try:
if os.path.exists(LOG) and os.path.getsize(LOG) > 512 * 1024:
os.replace(LOG, LOG + ".1")
with open(LOG, "a") as fh:
fh.write(line)
except OSError:
pass
def cfstr(s):
return CF.CFStringCreateWithCString(kCFAllocatorDefault, s.encode(), kCFStringEncodingUTF8)
def cfnum(i):
v = ctypes.c_int(i)
return CF.CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, ctypes.byref(v))
def open_lid_device():
keys = (ctypes.c_void_p * 2)(cfstr("PrimaryUsagePage"), cfstr("PrimaryUsage"))
vals = (ctypes.c_void_p * 2)(ctypes.cast(cfnum(PAGE), ctypes.c_void_p),
ctypes.cast(cfnum(USAGE), ctypes.c_void_p))
matching = CF.CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, None, None)
mgr = IOKit.IOHIDManagerCreate(kCFAllocatorDefault, 0)
IOKit.IOHIDManagerSetDeviceMatching(mgr, matching)
devs = IOKit.IOHIDManagerCopyDevices(mgr)
if not devs:
return None
n = CF.CFSetGetCount(devs)
if n < 1:
return None
arr = (ctypes.c_void_p * n)()
CF.CFSetGetValues(devs, arr)
dev = arr[0]
if IOKit.IOHIDDeviceOpen(dev, 0) != 0:
return None
return dev
def read_angle(dev):
"""Feature report 1 -> uint16 LE angle in degrees (report byte 0 = report ID)."""
buf = ctypes.create_string_buffer(8)
ln = ctypes.c_long(8)
if IOKit.IOHIDDeviceGetReport(dev, 2, 1, buf, ctypes.byref(ln)) != 0:
return None
raw = buf.raw[: ln.value]
if len(raw) < 3:
return None
return float(raw[1] | (raw[2] << 8))
def app_running():
return subprocess.run(["pgrep", "-x", "FreeSlapMac"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL).returncode == 0
def creak_enabled():
r = subprocess.run(["defaults", "read", BUNDLE, PREF_KEY],
capture_output=True, text=True)
if r.returncode != 0:
return True # never written: treat as on
return r.stdout.strip().lower() in ("1", "true", "yes")
def audio_state():
"""(volume, muted) of the default output, or (None, None) if unreadable."""
try:
r = subprocess.run(["osascript", "-e", "get volume settings"],
capture_output=True, text=True, timeout=5)
except (OSError, subprocess.TimeoutExpired):
return None, None
vol = muted = None
for part in r.stdout.replace("\n", ",").split(","):
part = part.strip()
if part.startswith("output volume:"):
try:
vol = int(part.split(":")[1])
except ValueError:
pass
elif part.startswith("output muted:"):
muted = part.split(":")[1].strip() == "true"
return vol, muted
def audio_ok():
"""True when the machine can actually be heard. Logs the reason when not."""
vol, muted = audio_state()
if muted:
log(f"WARNING system output is MUTED - creaks play but cannot be heard "
f"(set volume output muted false)")
return False
if vol == 0:
log("WARNING system output volume is 0 - creaks play but cannot be heard")
return False
return True
def gain_for(speed):
if FIXED_VOLUME:
return float(FIXED_VOLUME)
return min(GAIN_MAX, max(GAIN_MIN, GAIN_MIN + speed / 40.0))
_player = None
def play_creak(volume):
global _player
if _player is not None and _player.poll() is None:
_player.kill()
try:
_player = subprocess.Popen(
["afplay", "-t", "1.8", "-v", f"{volume:.2f}", CREAK],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
log(f"creak vol={volume:.2f}")
return True
except OSError as exc:
log(f"afplay failed: {exc}")
return False
def should_play(speed, now, last_play, enabled):
"""Pure trigger decision -> (play, reason). Kept separate so it can be tested."""
if speed < SPEED_TRIGGER:
return False, "slow"
if not enabled:
return False, "toggle off"
if (now - last_play) < RETRIGGER_S:
return False, "retrigger"
return True, "creak"
def trigger_test():
"""Offline proof the trigger fires on a real lid close and stays quiet otherwise."""
cases = [
("slow drift 2 deg/s", should_play(2.0, 100.0, 0.0, True), False),
("normal move 20 deg/s", should_play(20.0, 100.0, 0.0, True), True),
("fast close 136 deg/s", should_play(136.0, 100.0, 0.0, True), True),
("toggle off", should_play(136.0, 100.0, 0.0, False), False),
("0.5s after last creak", should_play(136.0, 100.5, 100.0, True), False),
("2s after last creak", should_play(136.0, 102.0, 100.0, True), True),
]
bad = 0
for name, (play, reason), want in cases:
ok = play == want
bad += 0 if ok else 1
print(f"{'PASS' if ok else 'FAIL'} {name:26s} play={play!s:5s} reason={reason}")
print(f"\n{len(cases) - bad}/{len(cases)} trigger cases pass")
return 0 if bad == 0 else 1
def self_test():
print(f"lidcreak {VERSION}")
print(f"app: {APP}")
print(f"sound: {CREAK} exists={os.path.exists(CREAK)}")
dev = open_lid_device()
if dev is None:
print("LID DEVICE: not found")
return 1
angle = read_angle(dev)
print(f"LID ANGLE: {angle}")
print(f"APP RUNNING: {app_running()} TOGGLE ON: {creak_enabled()}")
vol, muted = audio_state()
print(f"SYSTEM OUTPUT: volume={vol} muted={muted}"
f"{' <-- SILENT, nothing can be heard' if (muted or vol == 0) else ''}")
print(f"GAIN: {gain_for(20.0):.2f} (afplay -v)")
play_creak(gain_for(20.0))
time.sleep(2.2)
print("played creaking.mp3")
return 0 if angle is not None else 1
def prove():
"""Play the creak at three levels so the user can pick a loudness."""
vol, muted = audio_state()
print(f"system output: volume={vol} muted={muted}")
if muted or vol == 0:
print("!! output is muted / at 0 — fixing that first")
subprocess.run(["osascript", "-e", "set volume output muted false",
"-e", "set volume output volume 60"])
if not os.path.exists(CREAK):
print(f"missing sound file: {CREAK}")
return 1
for gain in (1.0, 1.5, 2.0):
print(f"gain {gain:.1f}", flush=True)
subprocess.run(["afplay", "-t", "2.2", "-v", f"{gain:.2f}", CREAK])
time.sleep(0.4)
print("done — peak of the sample is -6.2 dBFS, so 2.0 is the loudest clean setting")
return 0
def watch():
dev = None
prev = prev_t = None
while True:
if dev is None:
dev = open_lid_device()
if dev is None:
print("sensor unavailable (lid closed?) — retrying", flush=True)
time.sleep(2)
continue
angle = read_angle(dev)
now = time.time()
if angle is None:
dev = None
continue
speed = 0.0 if prev is None else abs(angle - prev) / max(now - prev_t, 1e-3)
print(f"angle={angle:6.1f} speed={speed:6.1f} deg/s "
f"{'TRIGGER' if speed >= SPEED_TRIGGER else ''}", flush=True)
prev, prev_t = angle, now
time.sleep(1.0 / POLL_HZ)
def main():
if "--self-test" in sys.argv:
return self_test()
if "--prove" in sys.argv:
return prove()
if "--trigger-test" in sys.argv:
return trigger_test()
if VERBOSE:
return watch()
log(f"agent start v{VERSION} app={APP} trigger={SPEED_TRIGGER} deg/s")
warned_silent = False
dev = None
prev = None
prev_t = None
last_play = 0.0
closed_logged = False
while True:
if dev is None:
dev = open_lid_device()
if dev is None:
if not closed_logged:
log("lid sensor unavailable (lid closed?) - retrying")
closed_logged = True
time.sleep(2)
continue
log("lid sensor opened")
closed_logged = False
prev, prev_t = None, None
angle = read_angle(dev)
now = time.time()
if angle is None:
try:
IOKit.IOHIDDeviceClose(dev, 0)
except Exception:
pass
dev = None
continue
if prev is not None and prev_t is not None:
dt = now - prev_t
if dt <= SAMPLE_GAP_MAX:
speed = abs(angle - prev) / dt
play, reason = should_play(speed, now, last_play, creak_enabled())
if play:
if not audio_ok() and not warned_silent:
warned_silent = True
vol = gain_for(speed)
play_creak(vol)
last_play = now
if speed >= 1.5:
log(f"move {prev:.0f}->{angle:.0f} deg speed={speed:.1f} deg/s "
f"{'CREAK' if play else 'skip:' + reason}")
prev, prev_t = angle, now
time.sleep(1.0 / POLL_HZ)
if __name__ == "__main__":
sys.exit(main() or 0)
#!/usr/bin/env bash
# Install lidcreak: makes FreeSlapMac's "Lid Creaking Sound" switch real.
# No root needed. Re-runnable (upgrades in place).
set -euo pipefail
BUNDLE="com.devesh.freeslapmac"
LABEL="${LIDCREAK_LABEL:-com.isaac.lidcreak}"
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEST="$HOME/Library/Application Support/FreeSlapLidCreak"
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
LOGDIR="$HOME/Library/Logs"
APP="${FREESLAP_APP:-}"
say() { printf '\033[1m%s\033[0m\n' "$*"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
bad() { printf ' \033[31m✗\033[0m %s\n' "$*"; }
say "lidcreak installer"
# 1. FreeSlapMac present?
if [ -z "$APP" ]; then
for c in "/Applications/FreeSlapMac.app" "$HOME/Applications/FreeSlapMac.app"; do
[ -d "$c" ] && APP="$c" && break
done
fi
if [ -z "$APP" ] || [ ! -d "$APP" ]; then
bad "FreeSlapMac.app not found — install the free app first (github.com/ibedevesh/freeslapmac)."
exit 1
fi
ok "app: $APP"
CREAK="$APP/Contents/Resources/Sounds/creaking.mp3"
if [ ! -f "$CREAK" ]; then
bad "missing $CREAK — the sound asset ships with the app; reinstall FreeSlapMac."
exit 1
fi
ok "sound: creaking.mp3 ($(wc -c <"$CREAK" | tr -d ' ') bytes)"
# 2. Python 3 (macOS ships one; Command Line Tools are enough)
if ! command -v python3 >/dev/null 2>&1; then
bad "python3 not found — run: xcode-select --install"
exit 1
fi
ok "python3: $(python3 -V 2>&1)"
# 3. Copy the helper out of the read-only download location
mkdir -p "$DEST" "$HOME/Library/LaunchAgents" "$LOGDIR"
install -m 0755 "$SRC/lidcreak.py" "$DEST/lidcreak.py"
ok "installed: $DEST/lidcreak.py"
# 4. LaunchAgent (user-level, starts at login, restarts if it dies)
launchctl bootout "gui/$(id -u)/$LABEL" >/dev/null 2>&1 || true
cat > "$PLIST" <<PLISTEOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>$LABEL</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>$DEST/lidcreak.py</string>
<string>--app</string>
<string>$APP</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>ThrottleInterval</key><integer>5</integer>
<key>StandardOutPath</key><string>$LOGDIR/lidcreak.out.log</string>
<key>StandardErrorPath</key><string>$LOGDIR/lidcreak.err.log</string>
</dict>
</plist>
PLISTEOF
plutil -lint "$PLIST" >/dev/null
ok "LaunchAgent: $PLIST"
# 5. Make sure the in-app switch reads ON (the app writes this pref itself)
defaults write "$BUNDLE" _creakingEnabled -bool true
ok "FreeSlapMac toggle '_creakingEnabled' = true"
# 6. Load it
if launchctl bootstrap "gui/$(id -u)" "$PLIST" 2>/dev/null; then
ok "agent bootstrapped"
else
launchctl unload "$PLIST" >/dev/null 2>&1 || true
launchctl load -w "$PLIST"
ok "agent loaded (legacy load -w)"
fi
sleep 1
# 7. Prove it: sensor read + one creak through the speakers
echo
say "self-test"
python3 "$DEST/lidcreak.py" --app "$APP" --self-test || {
echo
bad "the sensor did not answer. This Mac may not expose a lid-angle sensor"
bad "(it exists on Apple Silicon MacBooks). The agent stays installed but idle."
exit 2
}
echo
say "done"
cat <<EOF
Tilt the screen slowly — you should hear the creak, and the log should show it:
tail -f ~/Library/Logs/lidcreak.log
Turn it off any time with FreeSlapMac's own "Lid Creaking Sound" switch,
or remove everything with: bash "$SRC/uninstall.sh"
EOF
#!/usr/bin/env bash
# Remove lidcreak completely. Leaves FreeSlapMac untouched.
set -uo pipefail
LABEL="${LIDCREAK_LABEL:-com.isaac.lidcreak}"
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
DEST="$HOME/Library/Application Support/FreeSlapLidCreak"
UID_="$(id -u)"
printf 'Removing lidcreak…\n'
launchctl bootout "gui/$UID_/$LABEL" 2>/dev/null || true
launchctl unload "$PLIST" 2>/dev/null || true
pkill -f "FreeSlapLidCreak/lidcreak.py" 2>/dev/null || true
rm -f "$PLIST"
rm -rf "$DEST"
rm -f "$HOME/Library/Logs/lidcreak.log" "$HOME/Library/Logs/lidcreak.log.1" \
"$HOME/Library/Logs/lidcreak.out.log" "$HOME/Library/Logs/lidcreak.err.log"
if launchctl list | grep -q "$LABEL"; then
printf ' ! agent still listed — log out and back in, or run: launchctl bootout gui/%s/%s\n' "$UID_" "$LABEL"
else
printf ' ✓ agent unloaded, files removed\n'
printf " FreeSlapMac's \"Lid Creaking Sound\" switch is dead again (it always was).\n"
fi
# lidcreak — the lid-creaking sound FreeSlapMac forgot to implement FreeSlapMac (the free version of SlapMac) ships a **"Lid Creaking Sound"** switch that does nothing. It isn't a permissions problem and it isn't your Mac: the free app's binary contains the toggle, the description and the `_creakingEnabled` preference — and **no lid-sensor code at all**, no `creaking.mp3` reference, and no wake/clamshell hooks. The switch is dead UI carried over from the paid app, which does have a `LidAngleSensor` / `CreakAudioEngine` implementation. The `creaking.mp3` asset is sitting right there in the bundle, unused. This is a ~380-line user-level helper that reads the MacBook's hidden lid-angle sensor and plays that unused asset while the lid moves. The app's own switch becomes the on/off control. ``` lidcreak/ ├── lidcreak.py the daemon (IOHID feature-report read + afplay) ├── install.sh copies it, writes the LaunchAgent, self-tests it ├── uninstall.sh removes every trace ├── README.md └── LICENSE ``` ## Requirements | | | |---|---| | Mac | Apple Silicon MacBook (the lid-angle sensor is exposed on macOS 13+; verified on Mac14,2 / M2 Air, macOS 26.6) | | App | FreeSlapMac installed in `/Applications` (or `~/Applications`) — provides `creaking.mp3` | | Python | any `python3` (the one from Command Line Tools is enough) | | Root | **none** — feature-report HID reads are unprivileged | ## Install ```bash unzip freeslapmac-lidcreak-1.0.1.zip cd freeslapmac-lidcreak bash install.sh ``` It prints a step-by-step report and ends with a self-test that reads your real lid angle and plays one creak. Nothing to approve, no login items to click, no System Settings trip. If the self-test says `SYSTEM OUTPUT: volume=0 muted=True`, nothing on your Mac can be heard — not this helper, not music, not anything. Unmute first: ```bash osascript -e 'set volume output muted false' -e 'set volume output volume 60' python3 "$HOME/Library/Application Support/FreeSlapLidCreak/lidcreak.py" --prove ``` `--prove` plays the creak three times at rising gain so you can hear exactly what you are choosing between. ## Verify ```bash tail -f ~/Library/Logs/lidcreak.log # tilt the screen: move … CREAK / skip:slow python3 "$HOME/Library/Application Support/FreeSlapLidCreak/lidcreak.py" --self-test launchctl list | grep lidcreak # agent is loaded and alive ``` A real session looks like this — 20 Hz sampling, one creak per movement burst: ``` 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, ignored) ``` ## Uninstall ```bash bash uninstall.sh # from the extracted folder ``` Removes the agent, the helper, and the logs. FreeSlapMac is not touched. ## How it works The MacBook's lid angle is a HID sensor on **usage page `0x20`, usage `0x8A`**, read as **feature report 1** (3 bytes: report ID, then the angle as uint16 little-endian — `01 68 00` = 104°). `lidcreak.py` polls it at 20 Hz through `IOHIDManagerCreate` + `IOHIDDeviceGetReport`, computes `Δangle / Δt`, and: - fires when speed ≥ **8 °/s** (so slow drift and typing vibration stay silent), - holds a **1.4 s** retrigger gap so one lid movement gives one creak, - sets gain from speed: `clamp(1.0 + speed/40, 1.0, 2.0)` — `creaking.mp3` is mastered about 30 dB below the tap sounds (mean −30.5 dBFS, peak −6.2 dBFS), so a "reasonable" 0.35 volume was inaudible even at full system volume. 2.0 is the loudest setting that does not clip, - plays with `afplay -t 1.8` (truncated to the creak, not the whole 5.1 s file), - reopens the device if the lid closes (the sensor disappears on sleep) instead of dying, - obeys `_creakingEnabled` and nothing else: it creaks with FreeSlapMac open or quit, because the app was never the thing playing this sound (1.0.0 required the app to be running, which meant a closed app produced silence and no explanation), - logs *why* a movement did not creak (`skip:slow`, `skip:toggle off`, `skip:retrigger`), and warns once when the system output is muted or at zero. Tuning knobs (no reinstall needed, edit the `ProgramArguments`/constants or pass flags): ```bash lidcreak.py --watch # live angle + speed, to pick your own threshold lidcreak.py --trigger 5 # more sensitive lidcreak.py --volume 1.5 # fixed gain (1.0-2.0) instead of speed-scaled lidcreak.py --prove # three creaks, quiet to loud lidcreak.py --trigger-test # offline check of the trigger logic, no audio ``` ## Honest limitations - **Closing the lid will not creak.** macOS suspends the machine as the lid reaches ~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. This is physics, not a bug in the helper. - **It is not a patch of the app.** The toggle in FreeSlapMac is still dead UI; this helper just reads it. Uninstalling the helper (or turning the toggle off) restores silence. - **Audio goes through `afplay`**, i.e. routes wherever system output goes. There is no mixing into the app's own audio session — the app never had one for this. - The sensor exists on Apple Silicon MacBooks; on Intel Macs and desktops `install.sh` says so and leaves nothing running. - FreeSlapMac's slap detection itself is untouched. One quirk worth knowing: if several copies of the app's privileged wrapper are alive, a single slap can retrigger more than once. Quitting and reopening the app collapses them to one. ## License MIT — see `LICENSE`. The `creaking.mp3` asset is **not** redistributed here: it is read from your own FreeSlapMac installation. FreeSlapMac and SlapMac are the work of their respective authors; this helper is an independent, unofficial companion. Written by Isaac Chan, October 2026. ## Changelog **1.0.1** — the version that actually makes a noise. Three faults, all found by reading the log instead of trusting the code: 1. The daemon only played while FreeSlapMac was open. The app was never the thing playing the creak, so that gate only ever produced silence with the app quit — and the log said `trigger=no`, which reads like "your lid didn't move enough". 2. Gain. `0.35` on a file mastered at −30.5 dBFS mean is inaudible even at full system volume. Now `1.0–2.0`, measured against the file's −6.2 dBFS peak so it cannot clip. 3. No audio diagnostics. A muted Mac and a missing creak looked identical. `--self-test` now prints the output volume and mute state, the daemon warns once if the output is muted, and each skipped movement is tagged with its reason. **1.0.0** — first release: HID sensor read, speed trigger, retrigger gap, LaunchAgent, self-test.
MIT License Copyright (c) 2026 Isaac Chan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- This package reads the creaking.mp3 asset out of a locally installed copy of FreeSlapMac. That audio file is not part of this repository and is not redistributed under this license. FreeSlapMac and SlapMac remain the property of their respective authors; this is an unofficial companion utility.