Years at a computer with my head pushed forward gave me osteochondrosis in my neck.
The doctor's advice was simple. Every 30-45 minutes:
- Stand up and walk for a minute or two.
- Slowly turn your head left and right.
- Relax your shoulders, move your shoulder blades.
- Look at something far away.
Not a workout. The point is to break up long sitting before the neck gets stiff.
I couldn't stick to it. Once I got into a task, 45 minutes turned into three hours.
Why timers didn't work for me
I tried break timers. They count wall-clock time and know nothing about what you're doing. You come back from the coffee machine, sit down, and five minutes later the timer tells you to stand up. Or you have to remember to reset it every time you leave the desk.
After a week I was dismissing the reminders without reading them. Then I closed the timer for good.
I needed a reminder that notices when I've already taken a break.
Getup
So I built Getup, a small Windows tray app on Electron.
- 3 minutes away from keyboard and mouse counts as a break, and the countdown starts over
- Locking the screen for over a minute or putting the PC to sleep counts too
- If I ignore a reminder, the tray icon turns red, the window counts the overtime, and the reminder comes back in 10 minutes
- Pause for calls (30 min, 1 hour, 2 hours)
- The reminder window appears without taking focus, so it doesn't eat keystrokes
- Daily stats: number of breaks, longest stretch without a break, a timeline of the day
No account, no network access. History stays in a JSON file on your machine.
How it knows you're away
Electron exposes system idle time through powerMonitor. On Windows it comes from GetLastInputInfo, so it's the time since the last keyboard or mouse input in the session, in any app.
const { powerMonitor } = require("electron");
const idleMs = powerMonitor.getSystemIdleTime() * 1000;
The app reads it every few seconds and passes it to a tracker. The core rule is short:
const threshold = this.isDue(now) && !this.isMuted(now) ? IDLE_BREAK_DUE : IDLE_BREAK;
if (idleMs >= threshold) {
this.endSitting(now - idleMs);
return false;
}
IDLE_BREAK is 3 minutes. Once a break is due, IDLE_BREAK_DUE lowers it to 90 seconds. If I go idle right after a reminder, I most likely got up.
Note the now - idleMs. The sitting stretch ends at the last input, not when the threshold is crossed. Coming back works the same way: input in the last 10 seconds starts a new stretch, backdated by the idle time. So the stats show when I actually left and came back.
Edge cases
Idle time alone wasn't enough:
-
Sleep. Electron fires
suspend, and that ends the stretch. As a fallback, if two ticks are more than 3 minutes apart, the tracker counts it as a break. That also covers the process hanging. -
Lock screen. There are
lock-screenandunlock-screenevents, but an event can be missed, for example when the app starts on the lock screen. So every tick also checkspowerMonitor.getSystemIdleState(60) === "locked". On Windows that covers the screensaver too. A lock shorter than a minute isn't a break. - Reminders nobody sees. No reminders on the lock screen. After a short lock the reminder shows up right away.
- Bumping the mouse. Walking past the desk and touching the mouse shouldn't end the break. Stretches under a minute aren't logged, and if one comes right after a break, the break just continues.
- Restarts. If the app restarts less than 3 minutes after it last saved its state, it continues the same stretch instead of logging a fake break.
Keeping it testable
All the break logic lives in src/tracker.js with no Electron imports. Current time and idle time come in as plain numbers, so a test can simulate a whole working day in milliseconds:
// "active" = mouse used every tick, "away" = idle time keeps growing
function run(ms, mode = "active") {
let idle = 0;
for (const end = now + ms; now < end;) {
now += TICK;
idle = mode === "away" ? idle + TICK : 0;
if (tracker.tick(now, idle)) {
reminders++;
}
}
}
test("reminds at the interval and repeats if ignored", () => {
run(30 * MIN);
assert.strictEqual(reminders, 1);
run(10 * MIN);
assert.strictEqual(reminders, 2);
});
It runs with plain node --test, no Electron test setup.
Did it help?
Since it stopped nagging me at the wrong moments, I stopped ignoring it. Now I actually take the breaks, and my neck feels better.
Getup is a reminder, not medical advice. If your neck hurts, see a doctor.
Try it
Free and open source. Get the installer from the latest release. It installs per user, no admin rights needed. The installer isn't code-signed yet, so SmartScreen may warn the first time (More info → Run anyway).
Source: https://github.com/dsheiko/getup
How do you deal with long sitting? If you've built something on idle detection, I'd like to hear what edge cases you ran into.

Top comments (0)