Skip to main content

How It All Works

For the curious. Skip this if you just want the thing running.


The Randomness Is Deliberate

The jiggler waits a random interval between 45 and 90 seconds before each nudge. This is intentional. A perfectly regular interval — say, exactly 60 seconds every time — is detectable. Some corporate monitoring tools, remote desktop systems, and even certain screensaver implementations look for suspiciously regular input patterns. Random intervals look like a human who's just not moving much.

local delay = math.random(45, 90)

The Movement Is Tiny and Reversible

The cursor moves between 2 and 8 pixels in a random direction — horizontal and vertical independently, each with a random sign (positive or negative). Half a second later it moves back to the exact original position.

local pos = hs.mouse.absolutePosition()
local dx = math.random(2, 8) * (math.random(2) == 1 and 1 or -1)
local dy = math.random(2, 8) * (math.random(2) == 1 and 1 or -1)

The return move uses the saved pos value — not a calculation — so it lands exactly where it started regardless of what else might have moved in the 0.5 second gap.

Why Both Caffeinate and Mouse Movement?

hs.caffeinate.set("displayIdle", true) is the clean way to prevent display sleep on macOS. It tells the OS directly: don't sleep the display because of inactivity. For most purposes this is all you need.

The mouse movement is the belt-and-suspenders part. Some applications — video conferencing tools, remote access software, certain productivity trackers — don't respond to the caffeinate state. They watch for actual input events: keystrokes, mouse movement. The jiggle satisfies those checks.

Using both together means you're covered in either case.

The Self-Scheduling Loop

The jiggler doesn't use a repeating timer. Instead each jiggle schedules the next one:

local function scheduleJiggle()
-- wait a random delay...
jigglerTimer = hs.timer.doAfter(delay, function()
-- do the jiggle...
hs.timer.doAfter(0.5, function()
-- return cursor...
scheduleJiggle() -- schedule the next one
end)
end)
end

This means the interval is measured from the completion of the previous jiggle, not from a fixed clock. It also makes it trivial to stop: just set jiggling = false and the next scheduled call exits immediately at the top of the function without doing anything or rescheduling.

The Wake Watcher

When macOS wakes from sleep it resets caffeinate state. Without the watcher, you'd open your lid, find the jiggler icon showing ☕🐭, but the underlying caffeinate state would be off and the jiggler loop would have died. The watcher catches the wake event and re-applies everything:

local caffeineWatcher = hs.caffeinate.watcher.new(function(event)
if event == hs.caffeinate.watcher.systemDidWake then
if caffeineState == 1 or caffeineState == 2 then
hs.caffeinate.set("displayIdle", true)
end
if caffeineState == 2 then
startJiggler()
end
end
end):start()

The icon state (caffeineState) is preserved in memory across a sleep/wake cycle as long as Hammerspoon stays running. Only the OS-level caffeinate state needs to be re-applied.


That's the whole thing. About 80 lines of Lua, no dependencies, no network calls, no background services beyond Hammerspoon itself.