How Shake It On Moves Your Cursor
Jake MarshThe cursor motion in Shake It On is one of those details I’m probably a little too proud of. Most mouse jigglers move the pointer a few pixels one way, then back. It works, but it looks like a glitch, and if you’re looking at the screen when it happens it’s a little jarring. I wanted each shake to look like a hand bumping the mouse, so I made a short video showing the difference and wanted to write up how it works.
What I Wanted#
I had a short list of goals for a single shake. It should be smooth, with no jumps. It should be a little different every time, the way a real hand never moves the same way twice. It should ease in and ease out, so the cursor doesn’t start or stop abruptly. And the cursor has to end up exactly where it started, down to the pixel, because you might be about to click on something and the app shouldn’t move your target.
Two Sine Waves per Axis#
Each shake is a little closed path made of 30 steps spread over about 400 milliseconds. The x and y offsets for each step come from adding two sine waves together: a main one, and a faster harmonic at about twice its frequency that gives the path some texture. Here’s the heart of MouseShaker.swift:
// Random parameters for organic feel, different each shake
let phaseX1 = Double.random(in: 0...(2 * .pi))
let phaseY1 = Double.random(in: 0...(2 * .pi))
let phaseX2 = Double.random(in: 0...(2 * .pi))
let phaseY2 = Double.random(in: 0...(2 * .pi))
let freqX1 = Double.random(in: 1.8...2.2)
let freqY1 = Double.random(in: 1.8...2.2)
let freqX2 = Double.random(in: 3.5...4.5) // Higher harmonic for texture
let freqY2 = Double.random(in: 3.5...4.5)
for i in 0...steps {
let t = Double(i) / Double(steps) // 0.0 → 1.0
// Envelope: 0 → 1 → 0 so motion ramps up then settles back
let envelope = sin(t * .pi)
let angle = t * 2 * .pi
let dx = (sin(angle * freqX1 + phaseX1) * 0.7 +
sin(angle * freqX2 + phaseX2) * 0.3) * amplitude * envelope
let dy = (sin(angle * freqY1 + phaseY1) * 0.7 +
sin(angle * freqY2 + phaseY2) * 0.3) * amplitude * envelope
// post a mouseMoved event at startPos + (dx, dy), then wait one step
}A few things are going on there.
The phases are random for every shake, and each axis gets its own. That’s what makes one shake a lopsided loop up and to the left and the next one a little figure eight down and to the right. The frequencies are randomized too, in a narrow band, and x and y never quite match, so the path doesn’t trace a neat circle or a straight diagonal line.
The 0.7 and 0.3 weights keep the slow wave in charge of the overall shape and let the fast one add wobble on top.
The envelope is half a sine wave, sin(t * π), which goes from 0 up to 1 at the halfway point and back down to 0. Multiplying the offsets by it means the cursor starts barely moving, swings out, and then settles back in. That ease in and out does most of the work of making it feel like a hand.
Ending Exactly Where It Started#
The envelope is also what brings the cursor home. At t = 0 and t = 1 it’s zero, so the offset on the first and last steps is zero no matter what the sine waves are doing. On top of that, after the loop finishes, MouseShaker posts one more event right at the starting position, just to be sure nothing got rounded along the way. Your pointer is back on the exact pixel it left.
The size of the shake comes from the distance slider in Settings. The amplitude is 40% of that distance, with a floor of 2 pixels, so even the smallest setting still produces real movement.
Posting It Like a Real Mouse#
Where those events go matters as much as the path they trace. Shake It On makes its event source with the HID system state and posts each mouseMoved at the HID event tap:
guard let source = CGEventSource(stateID: .hidSystemState),
let startPos = CGEvent(source: nil)?.location else { return }
source.localEventsSuppressionInterval = 0.0
let event = CGEvent(mouseEventSource: source, mouseType: .mouseMoved,
mouseCursorPosition: pos, mouseButton: .left)
event?.post(tap: .cghidEventTap).hidSystemState makes the events look like they came from hardware, and .cghidEventTap is where events from a real mouse enter the system, so every app and every idle timer downstream sees ordinary user input. That’s why it keeps remote sessions alive and resets the idle clock on Macs where the Energy Saver settings are locked by IT.
Setting localEventsSuppressionInterval to zero matters too. By default, after an app posts events macOS briefly suppresses local input from the real mouse and keyboard. That would be a bad trade for a utility that’s supposed to stay out of your way, so Shake It On turns it off for the length of the shake and restores the old value afterward, and your real mouse and keyboard keep working the whole time.
Small Details#
The whole path runs on its own serial dispatch queue at .userInteractive quality of service, so the 13-millisecond sleeps between steps never touch the main thread. A simple flag keeps two shakes from ever overlapping, which could happen if you hit “Shake Now” right as the timer fired. And the starting position comes from CGEvent(source: nil)?.location, the cursor’s current position in global display coordinates.
The whole file is about 80 lines of Swift, and it’s the part of the app I’ve watched the most.