Platform APIs
Kael provides native platform integration matching (and exceeding) Electron’s capabilities. All APIs work cross-platform on macOS, Windows, and Linux.
File Dialogs
Native open/save file pickers:
#![allow(unused)]
fn main() {
// Open file dialog
let paths = cx.prompt_for_paths(PathPromptOptions {
files: true,
directories: false,
multiple: true,
prompt: Some("Open".into()),
}).await;
// Save file dialog
let path = cx.prompt_for_new_path(
&std::env::current_dir()?,
Some("document.txt"),
).await;
}
Native Menus
Application menu bar (macOS menu bar, Windows/Linux window menu):
#![allow(unused)]
fn main() {
cx.set_menus(vec![
Menu {
name: "File".into(),
items: vec![
MenuItem::action("New", menu_action::New),
MenuItem::action("Open...", menu_action::Open),
MenuItem::separator(),
MenuItem::action("Save", menu_action::Save),
MenuItem::action("Save As...", menu_action::SaveAs),
MenuItem::separator(),
MenuItem::action("Quit", menu_action::Quit),
],
},
Menu {
name: "Edit".into(),
items: vec![
MenuItem::action("Undo", menu_action::Undo),
MenuItem::action("Redo", menu_action::Redo),
MenuItem::separator(),
MenuItem::action("Cut", menu_action::Cut),
MenuItem::action("Copy", menu_action::Copy),
MenuItem::action("Paste", menu_action::Paste),
],
},
]);
}
System Tray
Tray icon with menu and click handling:
#![allow(unused)]
fn main() {
// Set tray menu
cx.set_tray_menu(vec![
TrayMenuItem::Action {
label: "Show Window".into(),
id: "show".into(),
},
TrayMenuItem::Separator,
TrayMenuItem::Action {
label: "Quit".into(),
id: "quit".into(),
},
]);
cx.set_tray_tooltip("My App — Running");
// Handle tray menu actions
cx.on_tray_menu_action(|action_id, cx| {
if action_id.as_ref() == "show" {
// bring window to front
} else if action_id.as_ref() == "quit" {
cx.quit();
}
});
// Handle tray icon clicks
cx.on_tray_icon_event(|event, cx| {
match event {
TrayIconEvent::LeftClick => { /* toggle window */ },
TrayIconEvent::DoubleClick => { /* show window */ },
_ => {}
}
});
}
Clipboard
Read and write text and images:
#![allow(unused)]
fn main() {
// Write text
cx.write_to_clipboard(ClipboardItem::new_string("Hello, clipboard!".into()));
// Write text with metadata
cx.write_to_clipboard(ClipboardItem::new_string_with_metadata(
"formatted text".into(),
json!({"source": "my_app"}).to_string(),
));
// Read
if let Some(item) = cx.read_from_clipboard() {
if let Some(text) = item.text() {
println!("Got: {}", text);
}
}
}
Global Hotkeys
System-wide keyboard shortcuts (work even when app is unfocused):
#![allow(unused)]
fn main() {
cx.register_global_hotkey(1, &Keystroke::parse("cmd-shift-k")?)?;
cx.on_global_hotkey(|id| {
match id {
1 => { /* Cmd+Shift+K pressed anywhere */ },
_ => {}
}
});
}
Notifications
OS-level notifications (not in-app toasts):
#![allow(unused)]
fn main() {
cx.show_notification("Build Complete", "All tests passed")?;
cx.show_notification_with_actions(
"Update Available",
"Version 2.0 is ready to install",
&[
NotificationAction { id: "install".into(), label: "Install Now".into() },
NotificationAction { id: "later".into(), label: "Remind Later".into() },
],
|action_id| {
println!("User clicked: {}", action_id);
},
)?;
}
Deep Linking
Register and handle custom URL schemes. These methods are called on Application before .run():
#![allow(unused)]
fn main() {
Application::new()
// Handle all opened URLs
.on_open_urls(|urls| {
for url in urls {
println!("Opened: {}", url);
}
})
// Handle specific scheme with app context
.on_deep_link("myapp", |url, cx| {
// Handle myapp://path/to/resource
})
.run(|cx| {
// ...
});
}
Multi-Window
Open multiple windows with independent views:
#![allow(unused)]
fn main() {
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|_window, cx| cx.new(|_| SettingsView::new()),
).unwrap();
}
Kael windows follow native platform conventions automatically:
- Scroll-to-focus — scrolling over an unfocused Kael window activates it, matching standard macOS/Windows behavior
- Smooth zoom — double-clicking the titlebar animates the window to fill the screen using native Core Animation transitions
- Live resize — content reflows smoothly during window drag-resizing
Auto-Update
Built-in application update pipeline:
#![allow(unused)]
fn main() {
let config = AutoUpdaterConfig {
feed_url: "https://releases.myapp.com/appcast.xml".into(),
..Default::default()
};
let updater = AutoUpdater::new(config, current_version, http_client);
// Check for updates
let status = updater.check_for_updates().await;
match status {
UpdateStatus::UpdateAvailable(info) => {
println!("New version: {}", info.version);
}
UpdateStatus::UpToDate => println!("Already up to date"),
_ => {}
}
}
Printing
Native print dialog and custom rendering:
#![allow(unused)]
fn main() {
let job = PrintJob::new("Document")
.orientation(PrintOrientation::Portrait)
.page(PrintPage::new(size(px(612.0), px(792.0)), |ctx| {
ctx.draw_text("Hello, printed world!", point(72.0, 72.0), style);
}));
window.show_print_dialog(job);
}
Power Management
Prevent sleep and detect power state:
#![allow(unused)]
fn main() {
// Prevent display sleep during video playback
let blocker = cx.start_power_save_blocker(PowerSaveBlockerKind::PreventDisplaySleep);
// Check power mode
match cx.power_mode() {
PowerMode::Performance => { /* full quality */ },
PowerMode::LowPower => { /* reduce effects */ },
_ => {}
}
// Detect idle time
if let Some(idle) = cx.system_idle_time() {
if idle > Duration::from_secs(300) { /* user is away */ }
}
// Listen for sleep/wake
cx.on_system_power_event(|event, cx| {
match event {
SystemPowerEvent::WillSleep => { /* save state */ },
SystemPowerEvent::DidWake => { /* refresh data */ },
_ => {}
}
});
}
Session Persistence
Save and restore window positions across launches:
#![allow(unused)]
fn main() {
let store = SessionStore::new("my-app")?;
// Save current window layout
store.save_window_states(&window_states)?;
// Restore on next launch
if let Ok(states) = store.load_window_states() {
for (id, state) in &states {
cx.open_window(WindowOptions {
window_bounds: Some(state.bounds),
..Default::default()
}, |_, cx| cx.new(|_| MyView::new()));
}
}
}
Display Information
Enumerate monitors and get DPI:
#![allow(unused)]
fn main() {
let displays = cx.displays();
let primary = cx.primary_display();
for display in &displays {
println!("Display {}: {:?}", display.id(), display.bounds());
}
}
Crash Reporting
Automatic crash capture with remote submission:
#![allow(unused)]
fn main() {
use kael::CrashReporter;
let mut reporter = CrashReporter {
app_id: "my-app".into(),
crash_dir: std::env::temp_dir().join("crashes"),
..Default::default()
};
reporter.install_hook();
}
App Lifecycle
Launch at login and update the dock/taskbar:
#![allow(unused)]
fn main() {
cx.set_auto_launch("com.example.app", true)?;
let enabled = cx.is_auto_launch_enabled("com.example.app");
cx.set_dock_badge(Some("3")); // None clears it
cx.set_dock_menu(dock_menu_items);
}
Enforce a single running instance — acquire a lock at startup and forward later launches to the existing process:
#![allow(unused)]
fn main() {
use kael::{SingleInstance, send_activate_to_existing};
match SingleInstance::acquire("com.example.app") {
Ok(instance) => {
instance.on_activate(Box::new(|| { /* focus the existing window */ }));
// ... run the app ...
}
Err(_already_running) => {
send_activate_to_existing("com.example.app")?;
return; // this duplicate launch exits
}
}
}
Biometric Authentication
Gate sensitive actions behind Touch ID / Face ID / Windows Hello. Check availability, then prompt with a reason string and a completion callback:
#![allow(unused)]
fn main() {
use kael::BiometricStatus;
if let BiometricStatus::Available(_kind) = cx.biometric_status() {
cx.authenticate_biometric("Unlock your vault", |success| {
if success { /* proceed */ }
});
}
}
BiometricStatus is Available(BiometricKind) or Unavailable; BiometricKind identifies the method (Touch ID, Face ID, fingerprint, Windows Hello).
Screen & Media Capture
Enumerate capturable displays/windows and stream frames (build with the screen-capture feature):
#![allow(unused)]
fn main() {
if cx.is_screen_capture_supported() {
// enumerate sources via cx.screen_capture_sources(..), then start a
// capture stream whose frames arrive as ScreenCaptureFrame values
}
}
See examples/capture_demo.rs.