Windows system tray app that tracks how long you use each application. Companion to FocusTab — FocusTab tracks browser tabs, FocusDesk tracks desktop apps. Both sync to the same GitHub Gist for a unified dashboard.
- App tracking — detects the foreground window every 3 seconds, maps exe names to friendly names
- Idle detection — pauses tracking after configurable idle time (default 5 min)
- System tray — right-click menu shows today's top 3 apps and total screen time
- Health indicators — icon color changes with status (green = recording, amber = idle, red = error); tooltip shows current app + total time
- GitHub Gist sync — bidirectional merge with FocusTab data, auto-discovers gist ID
- Secure token storage — GitHub token encrypted via Windows Credential Manager (DPAPI)
- Auto-start — optional "Start with Windows" toggle in tray menu
- Heartbeat — writes
heartbeat.jsonevery 30s for external health monitoring - Log file — append-only log with auto-rotation at 1 MB
- Windows 10/11
- Rust toolchain (for building from source)
cargo build --release
./target/release/focusdesk.exe
The app runs as a tray icon (no console window). Only one instance can run at a time.
All data lives in %APPDATA%\desktop_monitor\. The app creates this directory on first run.
{
"device_name": "Desktop",
"idle_threshold_seconds": 300,
"sync_interval_minutes": 30
}| Field | Default | Description |
|---|---|---|
device_name |
"Desktop" |
Label for this device in multi-device sync |
idle_threshold_seconds |
300 |
Seconds of no input before pausing tracking |
sync_interval_minutes |
30 |
How often to sync with GitHub Gist |
- Create a GitHub personal access token with
gistscope - Add
"github_token": "ghp_..."toconfig.json - FocusDesk will auto-discover the gist (looks for one containing
focustab_data.json) - On first run, the token is migrated from config.json to Windows Credential Manager
After migration,
github_tokenis removed from config.json automatically. You only need to add it once.
Custom exe-to-friendly-name mappings. Place in %APPDATA%\desktop_monitor\:
{
"Code.exe": "vs code",
"firefox.exe": "firefox",
"photoshop.exe": "adobe photoshop"
}If this file is missing, built-in defaults cover ~24 common apps. Names are case-insensitive and should be lowercase.
| File | Purpose |
|---|---|
tracking_YYYY-MM-DD.json |
App name to milliseconds map |
timeline_YYYY-MM-DD.json |
Timestamped app switch events |
heartbeat.json |
Last-known status (for external monitors) |
focusdesk.log |
Event log (startup, sync, errors) |
config.json |
User settings |
Files older than 90 days are automatically cleaned up on startup.
main thread monitor thread
| |
| Arc<Mutex<State>> |
|<------------------->|
| |
Win32 msg loop 3s poll loop
tray menu/icon idle detection
menu events window tracking
save/sync/heartbeat
- Main thread: Windows message pump, tray icon, menu events
- Monitor thread: polls foreground window, accumulates time, saves to disk, syncs to Gist
- Shared state:
Arc<Mutex<AppState>>— tracking data, timeline, health status
FocusDesk writes to the same Gist format as FocusTab. Data appears in the FocusTab dashboard grouped by device name:
{
"Desktop": {
"tracking_2026-03-08": { "vs code": 3600000, "firefox": 1800000 }
},
"Laptop": {
"tracking_2026-03-08": { "chrome": 7200000 }
}
}Merge strategy: max(local, remote) per app per day — same as FocusTab.
- GitHub token stored via DPAPI (Windows Credential Manager), not plaintext
- Single-instance mutex prevents data corruption from concurrent writes
- Atomic file writes via
MoveFileExW(no partial JSON on crash) - Config values sanitized (device name length, date format validation)
- App name mappings loaded from APPDATA, not CWD (prevents hijack)
- Response size capped at 5 MB (prevents memory exhaustion from malicious gist)
Private project.