Focus App CPU Bug on Dual-Monitor Mac: Root Cause & Fix
The Problem
The Focus productivity timer (com.ftustudio.focustime v1.6) was consuming ~55% CPU sustained on a dual-monitor Mac, while running at 0% on an identical single-monitor setup.
Root Cause
Focus uses the deprecated NSStatusItem.view API with a custom StatusItemView containing a CAShapeLayer (circular progress indicator). On dual-display setups with "Displays have separate Spaces" enabled, macOS replicates the menu bar across both screens. This triggers an infinite redraw loop:
NSStatusBarButtonCell drawWithFrame:inView:
→ NSStatusItemScene updateSettings:transition:
→ _windowNeedsReplicantUpdate:
→ performSelector:withObject:afterDelay:0
→ (immediate redraw, loop repeats)
Each redraw causes a replicant update via XPC to FrontBoardServices, which triggers another redraw — an infinite feedback loop that pegs Focus at 50-60% CPU and WindowServer at ~52%.
Key evidence: disconnecting the second monitor → 0% CPU instantly. Reconnecting → immediate spike back to 55%.
Workaround Tried: Background QOS Throttle
Before finding the root cause, we deployed a launch agent that auto-throttles Focus:
taskpolicy -b -p <FOCUS_PID> # background QOS class
renice 20 -p <FOCUS_PID> # lowest scheduling priority
This reduced CPU from 55% to ~4% — usable but not a real fix. The daemon (~/.local/bin/throttle-focus.sh) polls every 5 seconds and auto-applies throttling when Focus launches.
Solution
System Settings → Desktop & Dock → Mission Control → disable "Displays have separate Spaces"
This eliminates menu bar replication entirely. Without replication, the _windowNeedsReplicantUpdate: feedback loop never triggers. Result: 0% CPU.
Trade-off: you lose per-display Spaces (fullscreen apps can span both monitors, Dock shows on all displays). For most workflows this is fine.
Debugging Notes
- Binary hashes matched between affected and healthy machines — not a corrupted install
- Fresh reinstall reproduced the bug — not a preferences issue
- Profiled with
sample <PID> 5to identify the hot path in NSStatusBar drawing code - The bug is entirely in Focus's use of the deprecated
NSStatusItem.view; modern apps usingNSStatusItem.buttonare unaffected