Skip to content

Commit 9309426

Browse files
committed
chore: small hook
1 parent 172fe02 commit 9309426

2 files changed

Lines changed: 185 additions & 1 deletion

File tree

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#!/usr/bin/env python3
2+
"""
3+
play_system_sound.py
4+
5+
Cross-platform "play a short system sound" helper.
6+
- Windows: uses winsound + known system .wav
7+
- macOS: uses afplay on a known .aiff; falls back to `say`
8+
- Linux: tries paplay/aplay/ffplay and common sound theme files; falls back to terminal bell
9+
10+
Usage:
11+
python play_system_sound.py # play default for this OS
12+
python play_system_sound.py /path/to/file.wav # play a specific file if you prefer
13+
14+
Exit codes:
15+
0 = played successfully (or reasonable fallback like bell)
16+
1 = hard failure
17+
"""
18+
19+
import os
20+
import sys
21+
import platform
22+
import subprocess
23+
import shutil
24+
from pathlib import Path
25+
26+
def which(cmd: str) -> str | None:
27+
return shutil.which(cmd)
28+
29+
def try_run(cmd: list[str]) -> bool:
30+
try:
31+
# Use DEVNULL to keep CLI quiet
32+
with open(os.devnull, "wb") as devnull:
33+
subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
34+
return True
35+
except Exception:
36+
return False
37+
38+
def pick_default_sound(system: str) -> Path | None:
39+
p = Path
40+
41+
if system == "Windows":
42+
candidates = [
43+
p(r"C:\Windows\Media\Windows Notify.wav"),
44+
p(r"C:\Windows\Media\Windows Balloon.wav"),
45+
p(r"C:\Windows\Media\Alarm01.wav"),
46+
]
47+
for c in candidates:
48+
if c.exists():
49+
return c
50+
return None
51+
52+
if system == "Darwin": # macOS
53+
candidates = [
54+
p("/System/Library/Sounds/Glass.aiff"),
55+
p("/System/Library/Sounds/Submarine.aiff"),
56+
p("/System/Library/Sounds/Pop.aiff"),
57+
]
58+
for c in candidates:
59+
if c.exists():
60+
return c
61+
return None
62+
63+
# Linux & everything else POSIX-like
64+
linux_candidates = [
65+
# Freedesktop/Ubuntu themes (paplay likes .oga/.ogg)
66+
p("/usr/share/sounds/freedesktop/stereo/complete.oga"),
67+
p("/usr/share/sounds/freedesktop/stereo/message.oga"),
68+
p("/usr/share/sounds/ubuntu/stereo/dialog-information.ogg"),
69+
# ALSA samples for aplay (wav)
70+
p("/usr/share/sounds/alsa/Front_Center.wav"),
71+
# speech-dispatcher test (wav)
72+
p("/usr/share/sounds/speech-dispatcher/test.wav"),
73+
]
74+
for c in linux_candidates:
75+
if c.exists():
76+
return c
77+
return None
78+
79+
def play_file(path: Path) -> bool:
80+
"""Try a few players appropriate to the file extension/OS."""
81+
system = platform.system()
82+
ext = path.suffix.lower()
83+
84+
if system == "Windows":
85+
try:
86+
import winsound
87+
winsound.PlaySound(str(path), winsound.SND_FILENAME)
88+
return True
89+
except Exception:
90+
pass
91+
# Fallback to start (may open a UI player)
92+
return try_run(["powershell", "-NoProfile", "-Command", f'Start-Process -FilePath "{path}"'])
93+
94+
if system == "Darwin": # macOS
95+
if which("afplay"):
96+
return try_run(["afplay", str(path)])
97+
# fallback: QuickTime via 'open' (will show UI), or 'say' if no file
98+
if which("open"):
99+
return try_run(["open", str(path)])
100+
return False
101+
102+
# Linux / other POSIX
103+
# Prefer paplay (PulseAudio/PipeWire), then aplay (ALSA), then ffplay
104+
if which("paplay"):
105+
return try_run(["paplay", str(path)])
106+
if which("aplay"):
107+
return try_run(["aplay", str(path)])
108+
if which("ffplay"):
109+
# -autoexit to quit when done, -nodisp to avoid window
110+
return try_run(["ffplay", "-autoexit", "-nodisp", str(path)])
111+
if which("play"): # SoX
112+
return try_run(["play", "-q", str(path)])
113+
return False
114+
115+
def fallback_tone() -> bool:
116+
"""Last-ditch: try to emit a short beep on the current platform."""
117+
system = platform.system()
118+
try:
119+
if system == "Windows":
120+
import winsound
121+
winsound.MessageBeep()
122+
return True
123+
elif system == "Darwin":
124+
# Speak a short chirp if no audio players are available
125+
if which("say"):
126+
return try_run(["say", "-v", "Boing", "boop"])
127+
else:
128+
# ANSI bell (works in some terminals)
129+
sys.stdout.write("\a")
130+
sys.stdout.flush()
131+
return True
132+
except Exception:
133+
pass
134+
return False
135+
136+
def main():
137+
# If a path is provided, try that first.
138+
if len(sys.argv) > 1:
139+
user_path = Path(sys.argv[1]).expanduser()
140+
if user_path.exists():
141+
if play_file(user_path):
142+
return 0
143+
if fallback_tone():
144+
return 0
145+
return 1
146+
else:
147+
print(f"File not found: {user_path}", file=sys.stderr)
148+
return 1
149+
150+
system = platform.system()
151+
sound = pick_default_sound(system)
152+
153+
# If we found a system sound, try to play it
154+
if sound and play_file(sound):
155+
return 0
156+
157+
# If we couldn't play a file, try a platform-specific fallback
158+
if system == "Darwin" and which("say"):
159+
# make a tiny chirp without needing a file
160+
if try_run(["say", "ding"]):
161+
return 0
162+
163+
if fallback_tone():
164+
return 0
165+
166+
print("Unable to play any sound on this system.", file=sys.stderr)
167+
return 1
168+
169+
if __name__ == "__main__":
170+
raise SystemExit(main())

Website/.claude/settings.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,26 @@
77
"Bash(npx tsc:*)",
88
"Bash(npm test:*)",
99
"Bash(git status)",
10-
"Bash(git diff*)"
10+
"Bash(git diff*)",
11+
"Bash(python:*)"
1112
],
1213
"deny": [
1314
"Bash(rm -rf*)",
1415
"Bash(git push --force*)"
1516
],
1617
"ask": []
18+
},
19+
"hooks": {
20+
"Stop": [
21+
{
22+
"matcher": "",
23+
"hooks": [
24+
{
25+
"type": "command",
26+
"command": "python .claude/hooks/stop-audio.py"
27+
}
28+
]
29+
}
30+
]
1731
}
1832
}

0 commit comments

Comments
 (0)