Back to Work
2026Native Desktop Application

CCTV Menubar - macOS Menu Bar Surveillance App

CCTV Menubar - macOS Menu Bar Surveillance App
About

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.

Technologies
Tauri v2RustAxumHLS.jsHTML/CSS/JSmacOS APIs
Live ProjectVisit Website

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::Accessory to 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: NSVisualEffectView with 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 MediaRecorder with MP4/WebM codec chain.
  • Canvas Workaround: Draws video frames onto an offscreen <canvas> at 25fps since WKWebView doesn't support captureStream() 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, and User-Agent headers.
  • 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.

Next ProjectView All Work