Last active
December 23, 2025 19:55
-
-
Save windoze95/18d4e792619217dd6dfd166a2f2bbdad to your computer and use it in GitHub Desktop.
A no sleep script for macOS that moves the cursor in the shape of a heart every two minutes.
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
| /* | |
| -------------------------------------------------------------------- | |
| No Sleep (macOS) v1.1 - 2025 | |
| - Warps the cursor in the shape of a heart every 120s of idle time. | |
| - Pauses after a set time of true user absence to allow sleep. | |
| - Default pause time is 2 hours, override with an argument: | |
| osascript -l JavaScript till_brooklyn.js 1.5 | |
| - Uses only built-in macOS tools: ioreg + CoreGraphics (no installs). | |
| -------------------------------------------------------------------- | |
| */ | |
| ObjC.import("Foundation"); | |
| ObjC.import("CoreGraphics"); | |
| ObjC.import("stdlib"); | |
| // --- CONFIG --- | |
| const jiggleThresholdMs = 118000; // 118s: idle time before drawing heart | |
| const heartPoints = 60; | |
| const traceSeconds = 2.0; // total time to trace heart | |
| const stepDelaySeconds = traceSeconds / heartPoints; | |
| // --- Helpers --- | |
| function sh(cmd) { | |
| const task = $.NSTask.alloc.init; | |
| task.setLaunchPath("/bin/sh"); | |
| task.setArguments(["-lc", cmd]); | |
| const pipe = $.NSPipe.pipe; | |
| task.setStandardOutput(pipe); | |
| task.setStandardError(pipe); | |
| task.launch; | |
| task.waitUntilExit; | |
| const data = pipe.fileHandleForReading.readDataToEndOfFile; | |
| const str = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding); | |
| if (!str) return ""; | |
| return ObjC.unwrap(str).trim(); | |
| } | |
| function getIdleSeconds() { | |
| // HIDIdleTime is nanoseconds. Newer macOS often needs `ioreg -l` to show it. :contentReference[oaicite:2]{index=2} | |
| const out = sh("/usr/sbin/ioreg -l -c IOHIDSystem | /usr/bin/perl -ne 'if(/HIDIdleTime[^0-9]*([0-9]+)/){print $1; exit}'"); | |
| if (!out) return NaN; | |
| // Use BigInt for safety; convert to seconds. | |
| let ns; | |
| try { ns = BigInt(out); } catch (e) { return NaN; } | |
| const s = Number(ns) / 1e9; // safe for “hours” ranges | |
| return s; | |
| } | |
| function sleepSeconds(s) { | |
| $.NSThread.sleepForTimeInterval(s); | |
| } | |
| function fastIdleSeconds() { | |
| // CoreGraphics event source is fast enough to call per point. | |
| return $.CGEventSourceSecondsSinceLastEventType( | |
| $.kCGEventSourceStateCombinedSessionState, | |
| $.kCGAnyInputEventType | |
| ); | |
| } | |
| function currentMouseLocation() { | |
| const evt = $.CGEventCreate(null); | |
| const loc = $.CGEventGetLocation(evt); // CoreGraphics global display coords | |
| return { x: loc.x, y: loc.y }; | |
| } | |
| function warpMouse(x, y) { | |
| // Warps cursor without generating/posting events. :contentReference[oaicite:3]{index=3} | |
| $.CGWarpMouseCursorPosition($.CGPointMake(x, y)); | |
| } | |
| function postMouseMoved(x, y) { | |
| // Generates a real mouse-move event to reset idle timer. :contentReference[oaicite:4]{index=4} | |
| const pos = $.CGPointMake(x, y); | |
| const ev = $.CGEventCreateMouseEvent(null, $.kCGEventMouseMoved, pos, $.kCGMouseButtonLeft); | |
| $.CGEventPost($.kCGHIDEventTap, ev); | |
| } | |
| // --- Heart path precompute --- | |
| function buildHeart() { | |
| const pts = []; | |
| for (let i = 0; i < heartPoints; i++) { | |
| const t = 2 * Math.PI * i / heartPoints; | |
| const x = 16 * Math.pow(Math.sin(t), 3); | |
| const y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t); | |
| // Match your Windows behavior: scale + invert Y so the heart is upright in a "Y down" space. | |
| pts.push({ dx: Math.round(x * 4), dy: Math.round(-y * 4) }); | |
| } | |
| return pts; | |
| } | |
| // --- Entry point --- | |
| function run(argv) { | |
| const heart = buildHeart(); | |
| // Pause threshold (hours) | |
| let pauseThresholdHours = 2.0; | |
| if (argv && argv.length > 0) { | |
| const v = parseFloat(argv[0]); | |
| if (Number.isFinite(v) && v > 0) { | |
| pauseThresholdHours = v; | |
| console.log(`Override detected. Setting pause time to ${pauseThresholdHours} hours.`); | |
| } else { | |
| console.log(`Invalid input '${argv[0]}'. Using default pause time.`); | |
| } | |
| } | |
| let pauseThresholdSecs = Math.floor(pauseThresholdHours * 3600); | |
| const jiggleThresholdSecs = jiggleThresholdMs / 1000; | |
| // Sanity: pause threshold must exceed jiggle threshold | |
| if (pauseThresholdSecs <= jiggleThresholdSecs) { | |
| console.log(`Pause time (${pauseThresholdSecs}s) can't be <= jiggle time (${jiggleThresholdSecs}s). Auto-adjusting.`); | |
| pauseThresholdSecs = Math.floor(jiggleThresholdSecs + 2); | |
| pauseThresholdHours = Math.round((pauseThresholdSecs / 3600) * 10000) / 10000; | |
| console.log(`Adjusted pause time: ${pauseThresholdSecs}s (~${pauseThresholdHours}h).`); | |
| } | |
| console.log(`Script started. Jiggle threshold: ${jiggleThresholdSecs}s. Pause threshold: ${pauseThresholdHours}h (${pauseThresholdSecs}s).`); | |
| // Nudge the cursor immediately to trigger permissions on first run. | |
| const startPos = currentMouseLocation(); | |
| postMouseMoved(startPos.x + 1, startPos.y); | |
| warpMouse(startPos.x, startPos.y); | |
| // State | |
| let triggered = false; | |
| let ignoreNextUpdate = false; | |
| let lastPhysicalActivity = Date.now(); | |
| while (true) { | |
| const idleSecs = getIdleSeconds(); | |
| if (!Number.isFinite(idleSecs)) { | |
| console.log("Could not read idle time (HIDIdleTime). Exiting."); | |
| $.exit(1); | |
| } | |
| // If idle is very low, user is physically active — unless it was our own injected event. | |
| if (idleSecs < 1.0) { | |
| if (!ignoreNextUpdate) { | |
| lastPhysicalActivity = Date.now(); | |
| } | |
| ignoreNextUpdate = false; | |
| } | |
| const trueIdleSecs = (Date.now() - lastPhysicalActivity) / 1000; | |
| // PAUSE after true-idle exceeds threshold (lets Mac sleep naturally) | |
| if (trueIdleSecs >= pauseThresholdSecs) { | |
| const now = new Date(); | |
| console.log(`${now.toLocaleTimeString()} - True-idle of ${Math.round(trueIdleSecs)}s exceeded pause threshold. Pausing...`); | |
| sleepSeconds(15); | |
| continue; | |
| } | |
| // JIGGLE by tracing heart if system idle exceeds jiggle threshold | |
| if (!triggered && idleSecs * 1000 >= jiggleThresholdMs) { | |
| const origin = currentMouseLocation(); | |
| let aborted = false; | |
| const startMs = Date.now(); | |
| const now = new Date(); | |
| console.log(`${now.toLocaleTimeString()} - Jiggle threshold reached (${Math.round(idleSecs * 1000)}ms). Drawing heart...`); | |
| for (let i = 0; i < heart.length; i++) { | |
| const pt = heart[i]; | |
| warpMouse(origin.x + pt.dx, origin.y + pt.dy); | |
| const targetMs = startMs + ((i + 1) * stepDelaySeconds * 1000); | |
| const remainingMs = targetMs - Date.now(); | |
| if (remainingMs > 0) sleepSeconds(remainingMs / 1000.0); | |
| // Because warps do not generate events, idle will only reset if the user actually moves/presses something. | |
| const checkIdle = fastIdleSeconds(); | |
| if (Number.isFinite(checkIdle) && checkIdle < 1.0) { | |
| console.log(`${new Date().toLocaleTimeString()} - User activity detected. Aborting draw.`); | |
| aborted = true; | |
| break; | |
| } | |
| } | |
| if (!aborted) { | |
| warpMouse(origin.x, origin.y); | |
| // One real event to reset idle timer / prevent sleep | |
| postMouseMoved(origin.x, origin.y); | |
| ignoreNextUpdate = true; | |
| triggered = true; | |
| } | |
| } | |
| // Reset heart trigger once user returns to an active state (or after our injected reset) | |
| if (triggered && idleSecs < jiggleThresholdSecs) triggered = false; | |
| // Sleep intelligently until the next check | |
| const msToGo = jiggleThresholdMs - (idleSecs * 1000) - 500; | |
| const sleepMs = (msToGo > 500) ? msToGo : 500; | |
| sleepSeconds(sleepMs / 1000.0); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment