Skip to content

Instantly share code, notes, and snippets.

@mastermakrela
Last active September 8, 2025 19:55
Show Gist options
  • Select an option

  • Save mastermakrela/023b17f9547ac661ef051b6882543a3e to your computer and use it in GitHub Desktop.

Select an option

Save mastermakrela/023b17f9547ac661ef051b6882543a3e to your computer and use it in GitHub Desktop.
Utilities to get the current tilt of an Apple MacBook
/**
* Async generator that yields the MacBook lid tilt angle,
* but only when the angle changes (debounced).
* If the device is unavailable, it yields 0 once and stops.
*
* @returns {AsyncGenerator<number, void, unknown>}
*/
async function* getMacBookTilt() {
/** @type {HIDDevice | undefined} */
let device;
try {
// Request the lid angle sensor with destructuring
const [dev] = await navigator.hid.requestDevice({
filters: [
{
vendorId: 0x05ac, // Apple
productId: 0x8104, // Lid angle sensor
usagePage: 0x0020,
usage: 0x008a,
},
],
});
if (!dev) {
yield 0; // Device unavailable
return;
}
device = dev;
await device.open();
/** @type {number[]} */
const queue = [];
/** @type {(() => void) | null} */
let notify = null;
let lastAngle = null; // store last yielded angle
/**
* @param {HIDInputReportEvent} e
*/
const onReport = (e) => {
const angle = e.data.getUint16(0, true);
queue.push(angle);
if (notify) {
const n = notify;
notify = null;
n();
}
};
device.addEventListener("inputreport", onReport);
try {
while (device.opened) {
if (queue.length === 0) {
await new Promise((resolve) => {
notify = resolve;
});
if (!device.opened) break;
}
while (queue.length) {
const angle = queue.shift();
if (angle !== lastAngle) {
yield angle;
lastAngle = angle;
}
}
}
} finally {
device.removeEventListener("inputreport", onReport);
}
} catch (err) {
yield 0; // Error or device couldn't be opened
return;
} finally {
if (device && device.opened) {
await device.close();
}
}
}
// --- Example usage in the console ---
// (async () => { for await (const angle of getMacBookTilt()) console.log("Tilt:", angle); })();
/**
* MacBook tilt sensor class with Svelte 5 reactivity.
* Uses Symbol.dispose for automatic cleanup with using declarations.
*/
class MacBookTiltSensor {
#angle = $state(0);
#device = undefined;
#isInitialized = false;
constructor() {
this.#init();
}
/**
* Current tilt angle in degrees
* @returns {number}
*/
get current() {
return this.#angle;
}
/**
* Initialize the HID device and start listening
*/
async #init() {
if (this.#isInitialized) return;
this.#isInitialized = true;
try {
const [device] = await navigator.hid.requestDevice({
filters: [
{
vendorId: 0x05ac, // Apple
productId: 0x8104, // Lid angle sensor
usagePage: 0x0020,
usage: 0x008a,
},
],
});
if (!device) {
this.#angle = 0; // Device unavailable
return;
}
this.#device = device;
await device.open();
let lastAngle = null;
/**
* @param {HIDInputReportEvent} e
*/
const onReport = (e) => {
const newAngle = e.data.getUint16(0, true);
if (newAngle !== lastAngle) {
this.#angle = newAngle; // Triggers Svelte reactivity
lastAngle = newAngle;
}
};
device.addEventListener("inputreport", onReport);
} catch (err) {
console.error("Failed to initialize MacBook tilt sensor:", err);
this.#angle = 0;
}
}
/**
* Cleanup method for Symbol.dispose
*/
async [Symbol.dispose]() {
if (this.#device && this.#device.opened) {
await this.#device.close();
console.log("MacBook tilt sensor disposed");
}
this.#device = undefined;
this.#isInitialized = false;
}
/**
* Manual cleanup method (if you need it)
*/
async dispose() {
await this[Symbol.dispose]();
}
}
// Usage in Svelte component with automatic cleanup:
// using tiltSensor = new MacBookTiltSensor();
//
// <!-- In template: -->
// <p>MacBook tilt: {tiltSensor.current}°</p>
// Or manual usage:
// const tiltSensor = new MacBookTiltSensor();
// onDestroy(() => tiltSensor.dispose());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment