Skip to content

Instantly share code, notes, and snippets.

@pachacamac
Last active September 8, 2025 13:25
Show Gist options
  • Select an option

  • Save pachacamac/ba362aaa4bfce42c27f243a16a1601e3 to your computer and use it in GitHub Desktop.

Select an option

Save pachacamac/ba362aaa4bfce42c27f243a16a1601e3 to your computer and use it in GitHub Desktop.
get lidangle for macbooks call with -c for continuous
#!/bin/bash
if [[ "$OSTYPE" != "darwin"* ]]; then
echo "Error: This script only works on macOS" >&2
exit 1
fi
if [ ! -x ./lidangler ]; then
echo "Lidangler not found. Compiling..."
cat > .lidangler.c << 'EOF'
#include <IOKit/IOKitLib.h>
#include <IOKit/hid/IOHIDManager.h>
#include <IOKit/hid/IOHIDDevice.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int get_lid_angle(IOHIDDeviceRef device) {
uint8_t report[8] = {0};
CFIndex reportLength = sizeof(report);
// Try different report IDs
for (int reportId = 1; reportId <= 3; reportId++) {
IOReturn result = IOHIDDeviceGetReport(device,
kIOHIDReportTypeFeature,
reportId,
report,
&reportLength);
if (result == kIOReturnSuccess && reportLength >= 3) {
// Try to interpret as angle data
uint16_t rawValue = (report[2] << 8) | report[1];
if (rawValue >= 0 && rawValue <= 36000) { // Reasonable angle range
return rawValue;
}
}
}
return -1; // Error or no valid reading
}
int main(int argc, char *argv[]) {
int continuous_mode = 0;
// Parse command line arguments
if (argc > 1 && strcmp(argv[1], "-c") == 0) {
continuous_mode = 1;
}
IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if (!manager) {
return 1;
}
if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
CFRelease(manager);
return 1;
}
// Look for Apple devices with sensor capabilities
CFDictionaryRef matchingDict = CFDictionaryCreate(kCFAllocatorDefault,
(const void**)&(CFStringRef[]){CFSTR("VendorID")},
(const void**)&(CFNumberRef[]){CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &(int32_t){0x05AC})},
1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
IOHIDManagerSetDeviceMatching(manager, matchingDict);
CFSetRef devices = IOHIDManagerCopyDevices(manager);
if (!devices || CFSetGetCount(devices) == 0) {
if (devices) CFRelease(devices);
CFRelease(matchingDict);
IOHIDManagerClose(manager, kIOHIDOptionsTypeNone);
CFRelease(manager);
return 1;
}
// Try to find a device that responds to feature report requests
const void **deviceArray = malloc(sizeof(void*) * CFSetGetCount(devices));
CFSetGetValues(devices, deviceArray);
IOHIDDeviceRef targetDevice = NULL;
for (CFIndex i = 0; i < CFSetGetCount(devices); i++) {
IOHIDDeviceRef device = (IOHIDDeviceRef)deviceArray[i];
if (IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone) == kIOReturnSuccess) {
int angle = get_lid_angle(device);
if (angle >= 0) {
targetDevice = device;
break;
}
IOHIDDeviceClose(device, kIOHIDOptionsTypeNone);
}
}
if (!targetDevice) {
free(deviceArray);
CFRelease(devices);
CFRelease(matchingDict);
IOHIDManagerClose(manager, kIOHIDOptionsTypeNone);
CFRelease(manager);
return 1;
}
if (continuous_mode) {
// Continuous monitoring mode
int lastAngle = -1;
while (1) {
int currentAngle = get_lid_angle(targetDevice);
if (currentAngle >= 0 && currentAngle != lastAngle) {
printf("%d\n", currentAngle);
fflush(stdout);
lastAngle = currentAngle;
}
usleep(100000); // Sleep for 100ms
}
} else {
// Single reading mode
int angle = get_lid_angle(targetDevice);
if (angle >= 0) {
printf("%d\n", angle);
} else {
IOHIDDeviceClose(targetDevice, kIOHIDOptionsTypeNone);
free(deviceArray);
CFRelease(devices);
CFRelease(matchingDict);
IOHIDManagerClose(manager, kIOHIDOptionsTypeNone);
CFRelease(manager);
return 1;
}
}
IOHIDDeviceClose(targetDevice, kIOHIDOptionsTypeNone);
free(deviceArray);
CFRelease(devices);
CFRelease(matchingDict);
IOHIDManagerClose(manager, kIOHIDOptionsTypeNone);
CFRelease(manager);
return 0;
}
EOF
gcc -o lidangler .lidangler.c -framework IOKit -framework CoreFoundation
rm -f .lidangler.c
fi
./lidangler "$@"
#!/usr/bin/env python3
"""
pip install pyaudio numpy
---
Lid Angle Monitor with Audio Feedback
Continuously monitors the MacBook lid angle using lidangler.sh and plays
sound blips with frequency based on the current angle. Higher angles produce
higher frequency sounds.
"""
import subprocess
import sys
import time
import threading
import pyaudio
import numpy as np
import signal
import os
class LidAngleMonitor:
def __init__(self):
self.current_angle = None
self.last_angle = None
self.running = True
self.audio_thread = None
self.audio_queue = []
# Audio parameters
self.sample_rate = 44100
self.duration = 0.1 # 100ms blip duration
self.volume = 0.3
# Frequency mapping: angle (0-130) -> frequency (200-2000 Hz)
self.min_freq = 200
self.max_freq = 2000
# Initialize PyAudio
self.audio = pyaudio.PyAudio()
# Set up signal handler for graceful shutdown
signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
def signal_handler(self, signum, frame):
"""Handle interrupt signals for graceful shutdown"""
print("\nShutting down...")
self.running = False
def angle_to_frequency(self, angle):
"""Convert angle (0-130) to frequency (200-2000 Hz)"""
if angle is None:
return self.min_freq
# Normalize angle to 0-1 range
normalized = min(max(angle / 130.0, 0.0), 1.0)
# Map to frequency range
frequency = self.min_freq + (self.max_freq - self.min_freq) * normalized
return frequency
def generate_tone(self, frequency, duration):
"""Generate a sine wave tone"""
samples = int(self.sample_rate * duration)
t = np.linspace(0, duration, samples, False)
wave = np.sin(2 * np.pi * frequency * t)
# Apply envelope to avoid clicks
envelope = np.exp(-t * 8) # Exponential decay
wave = wave * envelope * self.volume
return wave.astype(np.float32)
def play_sound(self, frequency):
"""Play a sound blip at the given frequency"""
try:
# Generate the tone
tone = self.generate_tone(frequency, self.duration)
# Open audio stream
stream = self.audio.open(
format=pyaudio.paFloat32,
channels=1,
rate=self.sample_rate,
output=True
)
# Play the tone
stream.write(tone.tobytes())
# Clean up
stream.stop_stream()
stream.close()
except Exception as e:
print(f"Error playing sound: {e}", file=sys.stderr)
def monitor_angle(self):
"""Monitor lid angle using lidangler.sh -c"""
try:
# Start the lidangler process
process = subprocess.Popen(
['./lidangler.sh', '-c'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
print("Monitoring lid angle... (Press Ctrl+C to stop)")
print("Angle changes will trigger sound blips with frequency based on angle")
print("Higher angles = higher frequency sounds")
print()
while self.running:
# Read a line from the process
line = process.stdout.readline()
if not line:
# Process ended
break
try:
# Parse the angle value
angle = int(line.strip())
self.current_angle = angle
# Check if angle changed
if self.last_angle is not None and angle != self.last_angle:
frequency = self.angle_to_frequency(angle)
print(f"Angle changed: {self.last_angle} -> {angle} (freq: {frequency:.1f} Hz)")
# Play sound in a separate thread to avoid blocking
sound_thread = threading.Thread(
target=self.play_sound,
args=(frequency,)
)
sound_thread.daemon = True
sound_thread.start()
self.last_angle = angle
except ValueError:
# Skip invalid lines
continue
except Exception as e:
print(f"Error processing angle data: {e}", file=sys.stderr)
continue
# Clean up the process
process.terminate()
process.wait()
except FileNotFoundError:
print("Error: lidangler.sh not found. Make sure it's in the current directory.", file=sys.stderr)
return False
except Exception as e:
print(f"Error monitoring angle: {e}", file=sys.stderr)
return False
return True
def run(self):
"""Main run method"""
# Check if lidangler.sh exists
if not os.path.exists('./lidangler.sh'):
print("Error: lidangler.sh not found in current directory", file=sys.stderr)
return False
# Make sure lidangler.sh is executable
if not os.access('./lidangler.sh', os.X_OK):
print("Making lidangler.sh executable...")
os.chmod('./lidangler.sh', 0o755)
# Start monitoring
success = self.monitor_angle()
# Clean up audio resources
self.audio.terminate()
return success
def main():
"""Main entry point"""
monitor = LidAngleMonitor()
success = monitor.run()
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment