Last active
September 8, 2025 19:55
-
-
Save mastermakrela/023b17f9547ac661ef051b6882543a3e to your computer and use it in GitHub Desktop.
Utilities to get the current tilt of an Apple MacBook
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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); })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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