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.
UserDefaults.| 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:
or from Terminal:
xattr -dr com.apple.quarantine /Applications/DesktopOverlay.app
After that it launches normally every time.
open DesktopOverlay.xcodeproj
Select the DesktopOverlay scheme and press ⌘R.
./Scripts/install.sh
Builds 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:
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:
DispatchSourceTimer on a .utility queue (200 ms
leeway) drives every metric. Nothing polls faster than the chosen interval.Canvas-drawn and repaint only when a new
sample arrives — there is no animation loop.serious / critical thermal 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
MetricsCoordinator owns one background DispatchSourceTimer.SystemMetrics off the main thread, builds an
immutable frame, and hops to the main actor once to publish it.MetricsCoordinator and SettingsStore and re-render
only on change.SettingsStore
through 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.
MetricID in Metrics/MetricValue.swift
(shortLabel, displayName, and a spot in displayOrder).SystemMetricsService returning the raw
counters via a public API, nil on failure.SwapMetric.swift conforming to SystemMetric.
Keep the previous raw sample privately for delta values; put the pure math in a
Utilities/ helper so it can be unit-tested..swap: SwapMetric() to the metrics dictionary in
MetricsCoordinator.Toggle to MetricsSettingsTab in SettingsView.swift;
it appears in the menu automatically (menu = displayOrder minus GPU).OverlayRowView / MetricFormatter already render
percent / bytes / bytesPerSecond / celsius / rpm / text. Add a
shortDescription line 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.
—
and nothing else is affected.SMCService.swift, SMCTemperatureMetric.swift,
FanMetric.swift and the two MetricID cases, and the app is back to 100 %
documented APIs.The app makes zero network connections and stores nothing outside
UserDefaults.
project.yml, then
xcodegen generate..app.xcrun notarytool submit DesktopOverlay.zip \
--apple-id <id> --team-id <team> --password <app-specific-pw> --wait
xcrun stapler staple DesktopOverlay.app
.app or a DMG.For personal use none of this is needed — ./Scripts/install.sh is enough.
Run through these after UI changes:
MIT — see LICENSE.
20 commits
Swift
97.6%
Shell
2.4%
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.
UserDefaults.| 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:
or from Terminal:
xattr -dr com.apple.quarantine /Applications/DesktopOverlay.app
After that it launches normally every time.
open DesktopOverlay.xcodeproj
Select the DesktopOverlay scheme and press ⌘R.
./Scripts/install.sh
Builds 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:
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:
DispatchSourceTimer on a .utility queue (200 ms
leeway) drives every metric. Nothing polls faster than the chosen interval.Canvas-drawn and repaint only when a new
sample arrives — there is no animation loop.serious / critical thermal 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
MetricsCoordinator owns one background DispatchSourceTimer.SystemMetrics off the main thread, builds an
immutable frame, and hops to the main actor once to publish it.MetricsCoordinator and SettingsStore and re-render
only on change.SettingsStore
through 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.
MetricID in Metrics/MetricValue.swift
(shortLabel, displayName, and a spot in displayOrder).SystemMetricsService returning the raw
counters via a public API, nil on failure.SwapMetric.swift conforming to SystemMetric.
Keep the previous raw sample privately for delta values; put the pure math in a
Utilities/ helper so it can be unit-tested..swap: SwapMetric() to the metrics dictionary in
MetricsCoordinator.Toggle to MetricsSettingsTab in SettingsView.swift;
it appears in the menu automatically (menu = displayOrder minus GPU).OverlayRowView / MetricFormatter already render
percent / bytes / bytesPerSecond / celsius / rpm / text. Add a
shortDescription line 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.
—
and nothing else is affected.SMCService.swift, SMCTemperatureMetric.swift,
FanMetric.swift and the two MetricID cases, and the app is back to 100 %
documented APIs.The app makes zero network connections and stores nothing outside
UserDefaults.
project.yml, then
xcodegen generate..app.xcrun notarytool submit DesktopOverlay.zip \
--apple-id <id> --team-id <team> --password <app-specific-pw> --wait
xcrun stapler staple DesktopOverlay.app
.app or a DMG.For personal use none of this is needed — ./Scripts/install.sh is enough.
Run through these after UI changes:
MIT — see LICENSE.
20 commits
Swift
97.6%
Shell
2.4%