A tiny, native macOS floating panel that shows live system metrics — CPU, memory, disk I/O, network throughput, thermal state, and (on Intel Macs) CPU temperature and fan speed — always on top of your other windows, so you never need to open Activity Monitor.
A lightweight, open-source alternative to iStat Menus / MenuMeters / Stats.
- 100% native. Swift + SwiftUI + AppKit. No Electron, no web view, no JavaScript, no Python, no bundled runtime, no third-party dependencies.
- Local only. Zero network connections, no database, no telemetry, no
analytics. Every setting lives in
UserDefaults. - Light. ~0.3% CPU and ~45 MB RAM while idle (see Performance).
- Public APIs only — the one clearly-labelled, optional exception is the SMC sensor module for CPU °C / fan RPM (see Known limitations).
| macOS | 15.0 or later (developed and tested on macOS 26, Intel) |
| Xcode | 16 or later (developed with Xcode 26.5) |
| Dependencies | None. Apple system frameworks only. |
| Signing | None for personal use — the app runs ad-hoc signed. App Sandbox is off (see Known limitations). |
The Xcode project is generated from project.yml with
XcodeGen, but the generated
DesktopOverlay.xcodeproj is committed, so you do not need XcodeGen to build
or run.
Grab DesktopOverlay-x.y.zip from the
latest release, unzip,
and move DesktopOverlay.app to /Applications.
The build is ad-hoc signed but not notarized, so on first launch macOS will refuse to open it normally. Do this once:
- right-click the app ▸ Open ▸ Open in the dialog,
or from Terminal:
xattr -dr com.apple.quarantine /Applications/DesktopOverlay.appAfter that it launches normally every time.
open DesktopOverlay.xcodeprojSelect the DesktopOverlay scheme and press ⌘R.
./Scripts/install.shBuilds Release, copies the app to /Applications, ad-hoc signs it, removes the
quarantine flag, and launches it. Installing to a stable, signed location is what
lets Launch at Login register correctly.
./Scripts/package.sh produces dist/DesktopOverlay-<version>.zip for a release.
On first launch:
- the overlay appears immediately, centred on the main screen (~220×140 pt);
- a gauge icon appears in the menu bar;
- nothing appears in the Dock — the app is an accessory (
LSUIElement).
| Action | How |
|---|---|
| Move | Drag anywhere on the panel |
| Resize | Drag the grip in the bottom-right corner |
| Open Settings | Menu bar ▸ Settings… |
| Hide / show | Menu bar ▸ Hide Overlay / Show Overlay |
| Keep above everything | Menu bar ▸ Always on Top (on by default) |
| Let clicks pass through | Menu bar ▸ Click Through — the overlay becomes non-interactive until you turn it back off |
| Choose metrics | Menu bar ▸ Metrics, or Settings ▸ Metrics |
| CPU °C / fan RPM | Settings ▸ Metrics ▸ Sensors (SMC) — off by default, Intel Macs only |
| Refresh rate | Menu bar ▸ Update Interval (1 / 2 / 5 s) |
| Appearance | Menu bar ▸ Appearance (System / Light / Dark) |
| Start with macOS | Menu bar ▸ Launch at Login, or Settings ▸ General |
| Recenter if lost | Menu bar ▸ Reset Position |
| Learn what a value or word means | Settings ▸ Guide |
In Normal size the overlay also shows the raw figures behind the percentages
(13.6 / 32 GB for RAM, the user/system split for CPU) and a one-word status for
sensors (cool / warm / hot, idle / moderate / high). Compact size
drops those to keep the panel small.
Every setting — position, size, opacity, corner radius, font size, selected metrics, update interval, appearance, always-on-top, click-through, launch-at-login — persists across relaunches. Multi-display is handled: if a saved position ends up off every screen, the overlay is recentred on the main display.
Measured on a 2019 MacBook Pro (8-core i9), overlay visible with 7 metrics at a 1-second interval:
| Resource | Cost |
|---|---|
| CPU, idle | 0.2–0.3 % |
| Memory (RSS) | ~45 MB |
| Threads | 7 |
| Network | none — zero sockets open |
| Disk writes | UserDefaults only, and only when a setting changes |
How it stays cheap:
- One timer. A single
DispatchSourceTimeron a.utilityqueue (200 ms leeway) drives every metric. Nothing polls faster than the chosen interval. - Off-main sampling. Collectors run on the background queue; the UI is touched once per tick with an immutable snapshot.
- No idle rendering. Sparklines are
Canvas-drawn and repaint only when a new sample arrives — there is no animation loop. - Paused when hidden. Hiding the overlay stops collection entirely.
- Thermal backoff. At
serious/criticalthermal state the interval is stretched ×2 / ×4 and sparkline updates pause — the app does less work when the Mac is hot.
The samples above were stable across repeated readings (no growth). A multi-hour Instruments leak / energy run has not been done — contributions welcome.
DesktopOverlay/
├── App/ DesktopOverlayApp (@main), AppDelegate (lifecycle + wiring)
├── Overlay/ OverlayPanel (borderless NSPanel), OverlayWindowController,
│ DraggableHostingView, ResizeHandleView,
│ OverlayView / OverlayRowView / SparklineView
├── Metrics/ MetricValue, SystemMetric (protocol), MetricsCoordinator,
│ CPUMetric, MemoryMetric, DiskMetric, NetworkMetric,
│ ThermalMetric, BatteryMetric, SMCTemperatureMetric, FanMetric
├── Settings/ SettingsStore (UserDefaults — single source of truth),
│ SettingsView (General / Appearance / Metrics / Update / Guide),
│ SettingsWindowController
├── MenuBar/ MenuBarController (NSStatusItem + NSMenu, rebuilt on open)
├── Services/ SystemMetricsService (Mach / IOKit / POSIX wrappers),
│ SMCService (optional SMC reader), LaunchAtLoginService
└── Utilities/ RingBuffer, RateCalculator, CPUCalculator, MemoryCalculator,
DecayingMax, OverlayGeometry, MetricFormatter
Data flow
MetricsCoordinatorowns one backgroundDispatchSourceTimer.- Each tick samples the enabled
SystemMetrics off the main thread, builds an immutable frame, and hops to the main actor once to publish it. - SwiftUI views observe
MetricsCoordinatorandSettingsStoreand re-render only on change. - Menu, overlay level, click-through and appearance react to
SettingsStorethrough Combine.
Testing. The pure helpers (CPUCalculator, MemoryCalculator,
RateCalculator, OverlayGeometry, RingBuffer, the SMC value decoders) have no
I/O and are covered by 40 unit tests in DesktopOverlayTests/:
xcodebuild -project DesktopOverlay.xcodeproj -scheme DesktopOverlay \
-destination 'platform=macOS' test| Metric | Source (all public unless noted) |
|---|---|
| CPU | Mach host_statistics(HOST_CPU_LOAD_INFO) — tick deltas |
| Memory | Mach host_statistics64(HOST_VM_INFO64); pressure via sysctl kern.memorystatus_vm_pressure_level |
| Disk I/O | IOKit IOBlockStorageDriver Statistics — byte deltas |
| Network | getifaddrs if_data — byte deltas, loopback excluded |
| Temperature | ProcessInfo.thermalState — Nominal / Fair / Serious / Critical |
| CPU °C, Fan RPM | SMC (undocumented) — optional, off by default, Intel only |
| Battery | IOKit Power Sources (IOPSCopyPowerSourcesInfo) — optional |
Example: a Swap metric.
- Identifier — add a case to
MetricIDinMetrics/MetricValue.swift(shortLabel,displayName, and a spot indisplayOrder). - Raw reader — add a function to
SystemMetricsServicereturning the raw counters via a public API,nilon failure. - Metric object — create
SwapMetric.swiftconforming toSystemMetric. Keep the previous raw sample privately for delta values; put the pure math in aUtilities/helper so it can be unit-tested. - Register — add
.swap: SwapMetric()to themetricsdictionary inMetricsCoordinator. - Expose — add a
ToggletoMetricsSettingsTabinSettingsView.swift; it appears in the menu automatically (menu =displayOrderminus GPU). - Display —
OverlayRowView/MetricFormatteralready renderpercent/bytes/bytesPerSecond/celsius/rpm/text. Add ashortDescriptionline for the Guide.
Nothing else needs to change.
Everything here is a deliberate consequence of using public APIs only.
| Metric | Public API status | What the app does |
|---|---|---|
| Thermal state | ProcessInfo.thermalState — public. |
Shown as Nominal / Fair / Serious / Critical (default). |
| CPU temperature (°C) | No documented API. Readable from the SMC on Intel Macs via undocumented keys. | Optional Sensors (SMC) metric, off by default. Apple Silicon reports "unavailable". |
| Fan RPM | SMC only, undocumented. | Optional Sensors (SMC) metric, off by default. |
| GPU usage | No public API for system-wide GPU load. | Architecture-ready; the Settings toggle is disabled with an explanation. |
| CPU frequency | No reliable public API. | Not implemented. |
| Battery | IOPSCopyPowerSourcesInfo — public. |
Implemented, off by default. Desktops report "unavailable". |
| Disk I/O | IOKit IOBlockStorageDriver — public, but blocked by App Sandbox. |
App Sandbox is off. Re-enable it and the Disk row degrades to —; everything else still works. |
Services/SMCService.swift reads CPU temperature and fan speed from the System
Management Controller. This is not a documented Apple API — the 4-character
keys (TC0P, F0Ac, …) and their encodings (sp78, fpe2, flt) are
community-reverse-engineered, the same way iStat Menus, TG Pro and Stats do it.
- Off by default. Enable per metric in Settings ▸ Metrics ▸ Sensors (SMC).
- No special privileges. No root, no SIP / Gatekeeper changes, no kext.
- Intel only. On Apple Silicon the classic keys are absent, so the metrics report "unavailable" and their toggles are disabled.
- May break on a future macOS. If a key stops responding the row shows
—and nothing else is affected. - Isolated. Delete
SMCService.swift,SMCTemperatureMetric.swift,FanMetric.swiftand the twoMetricIDcases, and the app is back to 100 % documented APIs.
The app makes zero network connections and stores nothing outside
UserDefaults.
- Set a real bundle identifier / Developer Team in
project.yml, thenxcodegen generate. - Product ▸ Archive (Release configuration).
- Organizer ▸ Distribute App ▸ Direct Distribution for a Developer
ID-signed
.app. - For sharing with other Macs, notarize:
xcrun notarytool submit DesktopOverlay.zip \ --apple-id <id> --team-id <team> --password <app-specific-pw> --wait xcrun stapler staple DesktopOverlay.app
- Package the
.appor a DMG.
For personal use none of this is needed — ./Scripts/install.sh is enough.
Run through these after UI changes:
- Light / Dark / System appearance
- Retina and non-Retina displays
- Two or more displays; disconnect one → overlay returns to the primary screen
- Full-screen app in front → overlay stays visible, Dock / menu bar don't appear
- Drag to each screen edge; quit and relaunch → position restored
- Resize to the minimum and maximum
- Click Through on → clicks reach the app behind; off → overlay interactive again (toggle it a few times)
- Always on Top on / off changes whether other windows can cover the overlay
- Opacity, corner radius, font size, Compact / Normal all apply live
- Update interval 1 / 2 / 5 s changes the refresh rate
- Launch at Login on → appears in System Settings ▸ General ▸ Login Items
- Leave running for hours → CPU ≈ idle, memory flat, Energy = Low in Activity Monitor / Instruments
MIT — see LICENSE.

