Skip to main content

Crate muri

Crate muri 

Source
Expand description

§muri — Menu Utilities for Rust Interfaces

muri is a cross-platform, fully-styleable tray-icon + popup-menu system for Rust: a custom-drawn replacement for the muda + tray-icon pairing. Unlike native menus (which delegate pixels to AppKit / Win32 USER / GTK and can’t be restyled), muri draws one consistent custom appearance on every OS, enabling true alignment, arbitrary colors/fonts, embedded logos, and flush right-aligned values with no reserved chevron column.

muri owns the whole stack: the tray icon, the styled popup, and the anchoring (where the popup appears relative to the icon). A consuming app drives it with a small builder API:

use muri::{Tray, Menu, Row, Icon};

let menu = Menu::new()
    .row(Row::new("open").label("Open"))
    .separator()
    .row(Row::new("quit").label("Quit"));

let _tray = Tray::new(Icon::from_png(icon_png))
    .tooltip("My App")
    .menu(menu)
    .on_click(|id| println!("clicked {}", id.as_str()));
// let _m = muri::MainThreadMarker::new().unwrap(); // call this on the real main thread
// _tray.run(_m); // installs the tray icon and enters the platform event loop

§Native API: the one obvious way

The native surface deliberately has one canonical call per task, with alternatives kept only as clearly-labeled thin sugar or full-control escape hatches (issue #62). When in doubt, reach for the canonical path:

TaskCanonical native callEscape hatch
Build a menuMenu::new + Menu::row / separator / section_header / submenu / contentMenu::item with a hand-built Item
An interactive rowRow::new(id)
A header / label / info rowRow::label_only(text)Row::default + segments
Row textRow::label / Row::label_valueRow::segments (hand-built Segments)
Bold a row’s labelRow::bolda whole-label StyleRun with Weight::Bold
Color a row’s valueRow::value_colorper-substring StyleRuns
An iconIcon::from_png / Icon::from_rgba / Icon::from_svg
Choose the lookMenuOptions (carrying a ThemeSource)Tray::theme / TrayHandle::set_theme (derived conveniences that set the MenuOptions theme)

Per-run StyleRun styling (via Segment::run/Segment::runs) is the one styling system; Row::bold/Row::value_color are ergonomic front doors onto it and render identically to hand-built runs. MenuOptions is the single source of truth for “which look”.

§Status

0.9.0 testing release — all three backends implemented. macOS draws a real styled popup (NSStatusItem + non-activating NSPanel), with flyout submenus, keyboard navigation (keynav), and an accessibility tree (a11y) exposed to VoiceOver via AccessKit (a11y feature). Windows anchors a WS_EX_NOACTIVATE layered popup and exposes UIA via accesskit_windows. Linux installs an SNI/AppIndicator native menu and supports pointer-anchored popups via X11 override-redirect. ContextMenu::open_at / Popup::anchored_to / TrayHandle::open work on all three platforms; on-device verification (real hardware, real screen readers) is what this testing release is for. See the README for the honest platform matrix.

§Crate layout

  • menu — the declarative SegmentRowItemMenu tree, identifiers, events, and icons (pure data model).
  • style / theme — visual primitives (Color, Font) and the Theme surface with pure semantic-color resolution.
  • layout — pure Flex/Align width resolution (the flush-right layout).
  • flyout — pure flyout-submenu placement (right/left flip, clamp) and hover-stack transitions.
  • geometry — logical points/sizes/rects and Insets/Edge.
  • render — the SceneDrawer interface shared by the one CPU-raster backend on every OS.
  • platform — the single per-OS Platform seam (tray anchor, popup event loop, environment) selected once.
  • errorError / Unsupported.

§Rendering stack

Text is shaped/rasterized with fontdb + harfrust + swash; everything else is composited by muri’s own CPU Framebuffer blitter — a tiny binary with no GPU warm-up. Windowing is native per-OS: macOS uses a non-activating NSPanel/CALayer; Windows a WS_EX_NOACTIVATE layered window; Linux an SNI tray plus an X11 override-redirect window for ContextMenu::open_at.

§Platform support (honest matrix)

Legend: ✅ working (automated-tested / live) · 🔬 code-complete, on-device verification pending (this is what the 0.9.0 testing release is for) · ❌ not offered (by design).

OSTray iconStyled anchored popupContext menu (open_at)Screen reader
macOS🔬 NSStatusItem🔬 non-activating NSPanel, N-level flyouts, mouse + keyboard nav🔬 open_at + Popup (shared PopupSession)🔬 VoiceOver (per-window AccessKit adapters wired)
Windows🔬 Shell_NotifyIcon🔬 WS_EX_NOACTIVATE layered popup, WH_MOUSE_LL dismiss🔬 open_at + Popup (reuses the layered popup)🔬 NVDA + Narrator (UIA via accesskit_windows)
Linux🔬 SNI/AppIndicator native menu❌ tray-anchored (by design — see below); use pointer ContextMenu🔬 X11 override-redirect open_at (Wayland: Unsupported::ClientPositioning)🔬 Orca (AT-SPI via the native menu)

The 🔬 cells are muri code that builds and is clippy-clean on its target in CI but hasn’t been exercised on real hardware yet — verifying them, and the four screen readers, is exactly what the 0.9.0 testing release is for; each flips to ✅ as it’s confirmed on the road to 1.0. The Linux tray-anchored styled popup stays ❌ permanently.

Linux caveat: the SNI/AppIndicator tray host owns the icon in its own process, so the app never gets the icon’s rect or click coordinate; Wayland also forbids a client from positioning its own toplevel. A tray-anchored styled popup is therefore architecturally impossible there. Tray::run still installs a native SNI/AppIndicator menu, but reports Error::Unsupported(Unsupported::TrayAnchor) for the anchor rect — use that native menu or a pointer-anchored ContextMenu instead. See Unsupported.

Re-exports§

pub use a11y::announcement;
pub use a11y::build_tree;
pub use a11y::focused_id;
pub use a11y::locate;
pub use a11y::AxId;
pub use a11y::AxNode;
pub use a11y::AxRole;
pub use a11y::AxTree;
pub use anchor::place_popup;
pub use error::Error;
pub use error::Result;
pub use error::Unsupported;
pub use event::MenuEventReceiver;
pub use flyout::next_flyout;
pub use flyout::place_flyout;
pub use flyout::FlyoutPlacement;
pub use flyout::FlyoutSide;
pub use flyout::HoverTarget;
pub use geometry::Edge;
pub use geometry::Insets;
pub use geometry::LogicalPoint;
pub use geometry::LogicalRect;
pub use geometry::LogicalSize;
pub use keynav::handle_key;
pub use keynav::FlyoutFocus;
pub use keynav::MenuFocus;
pub use keynav::NavAction;
pub use keynav::NavKey;
pub use menu::Align;
pub use menu::Axis;
pub use menu::ClickHandler;
pub use menu::Content;
pub use menu::Flex;
pub use menu::Icon;
pub use menu::Item;
pub use menu::Menu;
pub use menu::MenuEvent;
pub use menu::MenuId;
pub use menu::Row;
pub use menu::Segment;
pub use menu::Stack;
pub use menu::StyleRun;
pub use menu::TextContent;
pub use platform::Appearance;
pub use platform::Platform;
pub use platform::PlatformEvent;
pub use render::render_menu_to_png;
pub use render::render_menu_to_rgba;
pub use style::Color;
pub use style::Font;
pub use style::FontFamily;
pub use style::Rgba;
pub use style::Weight;
pub use theme::GutterPolicy;
pub use theme::MenuOptions;
pub use theme::OsFamily;
pub use theme::Preset;
pub use theme::Theme;
pub use theme::ThemeMode;
pub use theme::ThemeSource;
pub use theme::TrailingGutterPolicy;

Modules§

a11y
Pure accessibility-tree model: the parallel structure a screen reader walks.
anchor
Pure popup-placement math shared by every OS backend.
compat
muda + tray-icon compatibility facade (spec 02, 60) — behind the muda-compat feature.
error
Error types for muri’s fallible operations.
event
The process-global MenuEvent channel — muri’s single, unified event source (spec 03 §3).
flyout
Pure, platform-independent flyout-submenu geometry and hover-stack logic.
geometry
DPI-independent (“logical”) geometry primitives shared by the layout engine, the scene drawer, and the per-OS anchoring shims.
keynav
Pure keyboard-navigation state machine for the menu and its N-level flyout stack.
layout
Pure, platform-independent row layout: distributing a row’s available width across its segments according to their Flex behavior, then placing each segment’s text within its box according to its Align.
menu
The declarative menu data model: identifiers and events, icons, and the SegmentRowItemMenu tree a consumer builds. This layer is entirely pure (no I/O, no platform calls) and is what both the renderer and the accessibility tree are derived from.
platform
The single platform seam: one Platform trait, one implementation module per OS behind it, selected exactly once here.
render
The scene-drawer interface and its one real implementation — the CPU raster backend (a muri-owned raster blitter plus a muri-owned text layer built directly on fontdb / harfrust / swash) described in the design. The same drawer paints the menu on macOS, Windows, and Linux; per-OS code is confined to the anchoring/dismiss shims (see crate::platform), never to drawing.
style
Visual style primitives: colors, fonts, and weights. These are the building blocks a consumer attaches to segments and rows; they are resolved against a Theme into concrete pixels by the renderer.
theme
Theming: the Theme surface, where it comes from (ThemeSource), the per-popup MenuOptions, and the pure resolution of a semantic Color into a concrete Rgba.

Structs§

ContextMenu
A free-standing styled menu shown at an explicit screen point. Unlike a tray-anchored popup, this works anywhere a pointer coordinate is available — including Linux/Wayland via xdg_positioner relative to the caller’s own surface — so it is muri’s portable styled-menu primitive.
MainThreadMarker
A zero-cost, !Send + !Sync proof that the calling code is running on the thread that obtained it — required by Tray::run and Tray::spawn because installing an NSStatusItem is only safe from AppKit’s main thread on macOS (issue #46); Windows/Linux take the same proof for one uniform contract across backends.
Popup
A styled dropdown popup anchored to an arbitrary caller rectangle (e.g. a toolbar button), rather than the tray icon or a bare point. It reuses the exact place_popup math the tray uses; Tray, ContextMenu, and Popup differ only in how the anchor rectangle is obtained, and all funnel into one shared PopupSession (spec 01 §5.3, spec 20 §3).
SurfaceId
A process-global, monotonically increasing identifier for one surface instance — a Tray, ContextMenu, or Popup — so a MenuEvent consumer can tell which surface an activation came from (issue #51).
Tray
A live tray icon with an attached styled menu.
TrayHandle
A cheap, Clone + Send remote control for a running Tray.