Case Study / macOS Development

ToolbarWidget — System Monitor

A zero-UI macOS menu bar widget showing GPU utilization and RAM pressure in real time. No dock icon, no popover, no window — just two numbers. Built with Swift 6.3, NSStatusBar, IOKit, and Apple's memory pressure formula.

Rolle
Solo Developer
Dauer
6 Iterationen (Mai–Juni 2026)
Tools
Swift 6.3, AppKit, IOKit, Combine, Mach API
Problemstellung

Why Build a Menu Bar Monitor?

macOS Activity Monitor is heavy — it's a full window, multiple tabs, CPU overhead. When you're rendering in Blender or running local LLMs on an M4/32GB, you need to know GPU and RAM pressure at a glance, not by switching windows.

Existing menu bar tools (iStat Menus, Stats) are feature-heavy and pull system-wide metrics. ToolbarWidget does exactly one thing: GPU% over RAM% in monospaced digits. Click to refresh. That's it.

The constraint: No SwiftUI MenuBarExtra (always opens popup on click). Must use raw NSStatusBar + NSImage rendering. Must be concurrency-safe under Swift 6 strict mode. Must build from the workspace, never leaving the folder.

Vorgehen

The Build Process

01

Data Layer

IOKit AGXAccelerator for GPU utilization (direct from driver). host_statistics64 for RAM pressure using Apple's memorystatus_get_level formula (#453): pressure = 100 − (free+inactive+speculative)/total × 100. M4 page size discovered to be 16384B (not 4096B as Intel docs suggest). Timer-driven polling every 10 seconds.

02

UI Layer

NSStatusBar.system.statusItem with NSImage label. GPUXX over RAMXX in monospaced digits. No SwiftUI — MenuBarExtra always opens popup on click. Klick triggers monitor.tick() for instant refresh. Dark/light mode auto-color via effectiveAppearance.

03

Display Logic

99-cap on both values for constant 2-digit width (no layout jumps). 5-second Rot-Flash via resolveColor(): when either metric hits 99, gpuHit99At/ramHit99At timestamp is set, and text renders in .systemRed for 5 seconds — a pulse, not a steady alarm.

04

Delivery Pipeline

swift build -c release → manual .app bundle (Info.plist + LSUIElement:true) → codesign ad-hoc → hdiutil create DMG. Single build.sh script. Drag & drop install to /Applications. Right-click → Open for first launch (unsigned).

Ergebnis

What Shipped

v0.6 — Production Ready

v0.6 running in /Applications on M4/32GB. Tested: Python 30GB allocation (no false pressure), Gemma4:26B via Ollama — RAM pressure rises from ~30% to 97% — widget reacts visibly within one polling cycle.

Formula Iteration

The RAM formula took 4 iterations to get right — wired+compressor was useless because macOS shifts to inactive under load instead of compressing. The final formula matches Apple's own Activity Monitor calculation.

0
Dock Icons
0
Windows / Popovers
10s
Polling Interval
99
Wert-Cap (konstante Breite)
5s
Rot-Flash Dauer
<1s
Build Time
Erkenntnisse

What This Taught Me

  • AppKit NSStatusBar gives you control that SwiftUI abstractions take away. For minimal UI, go lower-level.
  • Swift 6 strict concurrency forces you to think about thread safety at compile time. vm_kernel_page_size is not Sendable — but sysconf(_SC_PAGESIZE) is.
  • The menu bar has a horizontal layout engine. VStack → HStack. Multi-line text requires NSAttributedString rendered to NSImage.
  • A solo developer with a build.sh script and hdiutil can ship native macOS software without Xcode, without code signing certificates, without App Store.
  • Version 0.6 exists because 5 prior versions learned something each time. Ship, test, iterate — not plan, perfect, stall.