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.