From 491831508c062b125186ae1c111f775ff86781ab Mon Sep 17 00:00:00 2001 From: trussosw Date: Tue, 10 Nov 2026 23:34:15 -0500 Subject: [PATCH] Sleep the pointer timer when the mouse is still With the pointer on and the mouse still, the overlay kept ticking at 20 Hz forever, and resting over the Dock kept the 120 Hz timer running for as long as the mouse sat there. - After 3 s without movement, stop the timer entirely. The idle monitors (mouse moved, button down, modifier changed) already wake it. - Stop treating a still mouse over the Dock as busy after 3 s. - Give the 20 Hz idle timer tolerance so macOS can batch wakeups. - Start a fresh TinyHand.log once it passes 1 MB. Pointer off is unchanged (no timers ran there already). Co-Authored-By: Claude Opus 5.5 --- Sources/TinyHand/Log.swift | 5 +++++ Sources/TinyHand/OverlayController.swift | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Sources/TinyHand/Log.swift b/Sources/TinyHand/Log.swift index fd2a1dd..0165b87 100644 --- a/Sources/TinyHand/Log.swift +++ b/Sources/TinyHand/Log.swift @@ -14,6 +14,11 @@ enum Log { guard let data = line.data(using: .utf8) else { return } + // Keep the log small: once it passes 1 MB, start a fresh one. + if let size = (try? FileManager.default.attributesOfItem(atPath: url.path))?[.size] as? Int, size > 1_000_000 { + try? data.write(to: url) + return + } if let handle = try? FileHandle(forWritingTo: url) { handle.seekToEndOfFile() handle.write(data) diff --git a/Sources/TinyHand/OverlayController.swift b/Sources/TinyHand/OverlayController.swift index b0eae63..45b2d83 100644 --- a/Sources/TinyHand/OverlayController.swift +++ b/Sources/TinyHand/OverlayController.swift @@ -33,6 +33,9 @@ final class OverlayController { private static let busyInterval: TimeInterval = 1.0 / 120.0 private static let idleInterval: TimeInterval = 1.0 / 20.0 private static let idleAfter: TimeInterval = 0.5 + /// After this long without the mouse moving, stop the timer altogether. The idle monitors + /// wake it the moment the mouse moves, a button is pressed or a modifier changes. + private static let sleepAfter: TimeInterval = 3 private static let menuScanFast: TimeInterval = 0.05 private static let menuScanSlow: TimeInterval = 0.25 private var timerInterval: TimeInterval = 0 @@ -639,6 +642,9 @@ final class OverlayController { let t = Timer(timeInterval: interval, repeats: true) { [weak self] _ in self?.tick() } + if interval == OverlayController.idleInterval { + t.tolerance = interval * 0.5 // let macOS batch idle wakeups + } RunLoop.main.add(t, forMode: .common) timer = t timerInterval = interval @@ -663,6 +669,13 @@ final class OverlayController { if timerInterval == OverlayController.idleInterval { if busy { wake() + return + } + let asleep = Date().timeIntervalSince(stillSince) > OverlayController.sleepAfter + if asleep && !toolbar.revealed && downPoint == nil && !armView.isSettling { + timer?.invalidate() + timer = nil + timerInterval = 0 } return } @@ -739,7 +752,10 @@ final class OverlayController { let local = CGPoint(x: mouse.x - screen.frame.origin.x, y: mouse.y - screen.frame.origin.y) armView.aim(cursor: local) guard downPoint != nil else { - let busy = overDock || (!normalCursor && urgent) || CursorHider.isSettling + // Resting over the Dock kept the full-speed timer running for as long as the mouse sat + // there. Give up after a few still seconds; moving again wakes everything. + let lingering = Date().timeIntervalSince(stillSince) > OverlayController.sleepAfter + let busy = (!lingering && (overDock || (!normalCursor && urgent))) || CursorHider.isSettling updatePace(busy: busy) return }