ashlar_native/lib.rs
1//! Native OS features behind one small, safe API: file dialogs, the menu bar, the clipboard,
2//! keep-awake, files opened from the file manager, single instance, notifications, dock / taskbar
3//! progress, the OS keychain, system preferences and the process's memory footprint.
4//!
5//! Every feature is behind a cargo feature and degrades cleanly: on a platform where it is not
6//! implemented (or on the web) a call returns [`NativeError::Unsupported`]; nothing panics. The
7//! crate never depends on a UI toolkit, so apps wire it to their own event loop: activation events
8//! arrive on channels, and callbacks take a waker so an idle app wakes only when something
9//! happened.
10//!
11//! | Module | macOS | Windows | Linux | Web |
12//! |---|---|---|---|---|
13//! | [`dialogs`] | rfd | rfd | rfd (portal) | rfd (bytes only) |
14//! | [`menu`] (bar, context menus) | muda | muda (per window) | spec only | spec only |
15//! | [`clipboard`] | arboard + NSPasteboard types | arboard | arboard | – |
16//! | [`power`] | NSProcessInfo activity | SetThreadExecutionState | logind inhibit | – |
17//! | [`open_files`] | Apple events + argv | argv | argv | – |
18//! | [`single_instance`] | Unix socket | loopback + token | Unix socket | – |
19//! | [`notify`] | UNUserNotificationCenter (bundled apps) | – | D-Bus notifications | – |
20//! | [`progress`] | NSDockTile | ITaskbarList3 | – | – |
21//! | [`secrets`] | Keychain | Credential Manager | Secret Service | – |
22//! | [`system`] | AppKit | registry / SPI | portal settings | media queries |
23//! | [`cli_tool`] | link + admin prompt | – | link (no prompt) | – |
24//! | [`display`] | always | window station check | always | – |
25//! | [`tray`] | NSStatusItem | notification area | StatusNotifierItem (D-Bus, no GTK) | – |
26//! | [`hotkey`] | Carbon hot keys | RegisterHotKey | X11 (not Wayland) | – |
27//! | [`mac_shell`] | appearance guard, Dock reopen | – | – | – |
28//! | [`drag_out`] | NSDraggingSession | – | – | – |
29//! | [`memory`] | phys_footprint, malloc pressure relief | private commit | /proc status, malloc_trim | – |
30//!
31//! [`modal`] (always compiled) is a hook around every blocking dialog, for hang watchdogs.
32//! [`terminate`] (always compiled) runs a hook when the process is asked to stop (SIGINT, SIGTERM,
33//! SIGHUP).
34//!
35//! `unsafe` is denied in this crate and allowed only in the FFI modules that need it, each block
36//! with a `// SAFETY:` comment (docs/adr/0001-native-ffi.md).
37
38mod error;
39pub use error::{NativeError, Result};
40pub mod modal;
41pub mod terminate;
42
43#[cfg(feature = "cli-tool")]
44pub mod cli_tool;
45#[cfg(feature = "clipboard")]
46pub mod clipboard;
47#[cfg(feature = "dialogs")]
48pub mod dialogs;
49#[cfg(feature = "display")]
50pub mod display;
51#[cfg(feature = "drag-out")]
52pub mod drag_out;
53#[cfg(feature = "hotkey")]
54pub mod hotkey;
55#[cfg(any(feature = "menu", feature = "hotkey"))]
56#[allow(dead_code)]
57mod key_code;
58#[cfg(feature = "mac-shell")]
59pub mod mac_shell;
60#[cfg(feature = "memory")]
61pub mod memory;
62#[cfg(feature = "menu")]
63pub mod menu;
64#[cfg(all(not(target_arch = "wasm32"), any(feature = "tray", all(feature = "menu", any(target_os = "macos", target_os = "windows")))))]
65mod menu_events;
66#[cfg(any(feature = "tray", feature = "menu"))]
67#[allow(dead_code)]
68mod menu_tree;
69#[cfg(feature = "notify")]
70pub mod notify;
71#[cfg(feature = "open-files")]
72pub mod open_files;
73#[cfg(feature = "power")]
74pub mod power;
75#[cfg(feature = "progress")]
76pub mod progress;
77#[cfg(feature = "secrets")]
78pub mod secrets;
79pub mod session_end;
80#[cfg(feature = "single-instance")]
81pub mod single_instance;
82#[cfg(feature = "system")]
83pub mod system;
84#[cfg(feature = "tray")]
85pub mod tray;
86
87/// Something that wakes the app's event loop (an egui `request_repaint`, a winit proxy event).
88/// Called from background threads after a new event was queued.
89pub type Waker = std::sync::Arc<dyn Fn() + Send + Sync>;
90
91/// A waker that does nothing, for apps that poll.
92pub fn no_waker() -> Waker {
93 std::sync::Arc::new(|| {})
94}
95
96/// Run `f`, turning a panic inside a third-party binding into an error instead of unwinding into
97/// the app.
98#[allow(dead_code)]
99pub(crate) fn guarded<T>(what: &str, f: impl FnOnce() -> Result<T>) -> Result<T> {
100 std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_else(|_| Err(NativeError::Os(format!("{what} failed inside the OS binding"))))
101}
102
103/// The OS's own words for a failure, made safe to show: one line, at most 200 characters.
104#[allow(dead_code)]
105pub(crate) fn os_text(e: impl std::fmt::Display) -> String {
106 let text: String = e.to_string().chars().map(|c| if c.is_control() { ' ' } else { c }).take(200).collect();
107 let text = text.trim();
108 if text.is_empty() { "no reason given".into() } else { text.to_string() }
109}