A lightweight macOS menu bar application for quick-glance CCTV monitoring of Kota Malang cameras, built with Tauri v2 and Rust. Features frosted glass vibrancy, HLS streaming, and a local proxy server.
CCTV Menubar - macOS Menu Bar Surveillance App
CCTV Menubar brings live CCTV monitoring of Kota Malang directly to the macOS menu bar. Click the camera icon, pick a camera, and get a live stream in a small frosted-glass popup — no browser, no dock icon, no friction. Built as a companion to the CCTV Kota Malang web app.
Project Overview
The web dashboard is great for multi-camera monitoring, but sometimes you just want a quick glance at one camera without opening a browser tab. CCTV Menubar fills that gap — it lives entirely in the menu bar as a tray icon and shows a single live stream in a 400x380px popup window that dismisses when you click away.
Key Features
Menu Bar Native
A true macOS citizen that follows platform conventions:
- Tray Icon: Template image that adapts to light/dark mode automatically.
- No Dock Icon: Uses
ActivationPolicy::Accessoryto stay out of the Dock and Cmd+Tab switcher. - Tray Positioning: Popup anchors directly below the tray icon using
tauri-plugin-positioner. - Auto-Hide: Popup dismisses on focus loss — click anywhere else and it's gone.
Frosted Glass Vibrancy
The popup uses macOS native vibrancy effects for a polished look:
- Popover Effect:
NSVisualEffectViewwith the Popover material for translucent background blur. - Rounded Corners: 12px corner radius on the popup window.
- No Title Bar: Decorations disabled for a clean, chromeless appearance.
- Transparent Background: Window transparency enabled so the vibrancy effect shows through.
HLS Live Streaming
Reliable video playback despite the constraints of a Tauri webview:
- HLS.js: Fetches stream segments via JavaScript
fetch(), bypassing WKWebView's mixed-content restrictions. - Camera Selector: Custom dropdown with search filter and scrollable list of all city cameras.
- Camera Persistence: Last selected camera saved to localStorage across app restarts.
Capture Tools
Built-in screenshot and recording capabilities:
- Screenshot: Captures the current video frame as a PNG file.
- 10s Video Recording: Canvas-based recording using
MediaRecorderwith MP4/WebM codec chain. - Canvas Workaround: Draws video frames onto an offscreen
<canvas>at 25fps since WKWebView doesn't supportcaptureStream()on HLS elements.
Local Rust Proxy
An embedded HTTP proxy server that handles the upstream CCTV server's requirements:
- Header Injection: Adds required
Host,Referer, andUser-Agentheaders. - TLS Bypass: Accepts the upstream's self-signed SSL certificate via
danger_accept_invalid_certs. - CORS Headers: Enables the webview to fetch streams from the local proxy.
- Static Fallback: Serves bundled camera data if the upstream API is unreachable.
Technical Architecture
Application Stack
- Runtime: Tauri v2 with WKWebView on macOS.
- Backend: Rust with Axum for the HTTP proxy, Reqwest for upstream requests, Tower-HTTP for CORS.
- Frontend: Single-file vanilla HTML/CSS/JS — no build step, no framework, no bundler.
- Streaming: HLS.js loaded from CDN for HTTP Live Streaming playback.
Why These Choices?
Tauri over Electron: The final binary is ~10MB vs Electron's ~150MB+. Tauri uses the system's WKWebView instead of bundling Chromium, resulting in minimal memory footprint.
Vanilla JS over React: The UI is a single page with a video player and a dropdown. A framework would add build complexity and bundle size for zero benefit.
Embedded Rust Proxy: The upstream CCTV server blocks requests without specific headers and uses a self-signed certificate. A local proxy on localhost:9877 handles this transparently — the frontend just fetches from localhost.
HLS.js over Native Video: Tauri loads the frontend from tauri://localhost (a secure context). Native <video> blocks HTTP sources as mixed content. HLS.js fetches segments via fetch() which bypasses this restriction.
Canvas-Based Recording: WKWebView doesn't support HTMLMediaElement.captureStream() on HLS streams. The workaround draws frames to an offscreen canvas at 25fps, then uses canvas.captureStream() with MediaRecorder.
Implementation Highlights
Tray Icon & Popup Window
// Popup anchors below the tray icon on click
.on_tray_icon_event(|tray, event| {
tauri_plugin_positioner::on_tray_event(tray.app_handle(), &event);
// Toggle popup visibility on left click
if let TrayIconEvent::Click { button: MouseButton::Left, .. } = event {
if popup.is_visible() { popup.hide() }
else { popup.move_window(Position::TrayBottomCenter); popup.show(); }
}
})
macOS Vibrancy Effect
// Frosted glass popover with rounded corners
WebviewWindowBuilder::new(app, "popup", url)
.inner_size(400.0, 380.0)
.decorations(false)
.transparent(true)
.effects(
EffectsBuilder::new()
.effect(Effect::Popover)
.radius(12.0)
.build(),
)
.build()?;
Embedded Proxy Server
// Proxy starts on app launch, runs on a background Tokio task
tauri::async_runtime::spawn(async {
proxy::start_proxy_server().await; // Axum on localhost:9877
});
The proxy adds upstream headers and handles TLS:
fn build_client() -> Client {
Client::builder()
.danger_accept_invalid_certs(true)
.use_rustls_tls()
.build()
.expect("Failed to build HTTP client")
}
Project Structure
├── ui/
│ └── index.html # Single-file frontend (HTML + CSS + JS)
└── src-tauri/
├── tauri.conf.json # Tauri config (tray only, no windows)
├── Cargo.toml # Rust dependencies
└── src/
├── main.rs # Entry point
├── lib.rs # Tray icon, popup, vibrancy setup
├── proxy.rs # Axum HTTP proxy (port 9877)
└── data/
└── cameras.json # Static fallback camera data
Build & Distribution
The app builds to a native .app bundle and .dmg installer:
cargo tauri build
# Output: CCTV Menubar.app + CCTV Menubar_1.0.0_aarch64.dmg
Pre-built DMGs are published on GitHub Releases. Built for Apple Silicon — Intel Macs run it via Rosetta 2.
Results
CCTV Menubar demonstrates that a native desktop app doesn't need Electron's overhead. With Tauri v2 and Rust, the app starts instantly, uses minimal memory, and feels like a first-class macOS citizen. It fills a real gap — quick, always-available CCTV monitoring without context-switching to a browser.
