Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Augani/kael/llms.txt
Use this file to discover all available pages before exploring further.
Kael exposes a unified platform API through the App context so your application can integrate deeply with the host operating system without writing platform-specific code yourself. From a system tray icon with a full context menu to Touch ID / Windows Hello authentication, every capability follows the same pattern: configure a struct, call a method on cx, and register a callback.
System tray
Set the tray icon
Pass raw PNG bytes to cx.set_tray_icon. Pass None to remove the icon.use kael::prelude::*;
cx.set_tray_icon(Some(include_bytes!("../assets/tray.png")));
Build the tray menu
Construct a Vec<TrayMenuItem> using the four variants below and hand it to cx.set_tray_menu.use kael::{TrayMenuItem};
cx.set_tray_menu(vec![
TrayMenuItem::Action {
label: "Open Window".into(),
id: "open".into(),
},
TrayMenuItem::Toggle {
label: "Enable Notifications".into(),
checked: true,
id: "notifications".into(),
},
TrayMenuItem::Submenu {
label: "Theme".into(),
items: vec![
TrayMenuItem::Action { label: "Light".into(), id: "theme-light".into() },
TrayMenuItem::Action { label: "Dark".into(), id: "theme-dark".into() },
],
},
TrayMenuItem::Separator,
TrayMenuItem::Action {
label: "Quit".into(),
id: "quit".into(),
},
]);
| Variant | Fields | Description |
|---|
Action | label, id | A clickable item. id is returned in the action callback. |
Separator | — | A visual divider. |
Submenu | label, items | A nested menu with its own Vec<TrayMenuItem>. |
Toggle | label, checked, id | A checkmark item whose state you manage. |
Handle menu actions
Register cx.on_tray_menu_action to receive the id of whichever item the user clicks.cx.on_tray_menu_action(|id, cx| match id.as_ref() {
"open" => { /* open your main window */ }
"quit" => cx.quit(),
_ => {}
});
React to tray icon clicks
cx.on_tray_icon_event fires on left-click, right-click, and double-click.use kael::TrayIconEvent;
cx.on_tray_icon_event(|event, cx| {
if event == TrayIconEvent::LeftClick {
// show a panel centered on the tray icon
if let Some(bounds) = cx.tray_icon_bounds() {
// use bounds to position your window
let _ = bounds;
}
}
});
Call cx.set_tray_panel_mode(true) to receive TrayIconEvent::LeftClick instead of showing the native menu on left-click. This is useful if you want to display a custom panel rather than a system context menu.
Global hotkeys
Register a hotkey with an integer ID and a Keystroke, then listen for presses with cx.on_global_hotkey.
use kael::Keystroke;
// Register Cmd/Ctrl+Shift+Space as hotkey ID 1
let keystroke = Keystroke::parse("cmd-shift-space").unwrap();
cx.register_global_hotkey(1, &keystroke).unwrap();
cx.on_global_hotkey(|id| {
if id == 1 {
// toggle your command palette
}
});
// Release events are available separately
cx.on_global_hotkey_up(|id| {
if id == 1 {
// handle key release
}
});
To unregister a hotkey — for example, when the user reconfigures it:
cx.unregister_global_hotkey(1);
Global hotkeys are system-wide and will intercept keystrokes even when your app is in the background. Always provide a way for the user to disable or reconfigure them.
Native file and save dialogs
cx.prompt_for_paths and cx.prompt_for_new_path open the native file picker. Both are async and return via a oneshot::Receiver.
use kael::PathPromptOptions;
// Open dialog — pick one or more files
cx.prompt_for_paths(
PathPromptOptions {
files: true,
directories: false,
multiple: true,
prompt: Some("Choose files to import".into()),
},
cx.to_async(),
|paths, _cx| {
if let Some(paths) = paths {
for path in paths {
// handle each selected path
let _ = path;
}
}
},
);
// Save dialog — choose a destination path
cx.prompt_for_new_path(
&std::path::PathBuf::from("."),
|path, _cx| {
if let Some(path) = path {
// write to `path`
let _ = path;
}
},
);
PathPromptOptions fields:
| Field | Type | Description |
|---|
files | bool | Allow file selection. |
directories | bool | Allow directory selection. |
multiple | bool | Allow selecting more than one item. |
prompt | Option<SharedString> | Label shown in the dialog title bar. |
Desktop notifications
Send a simple notification:
cx.show_notification("Build complete", "Your project compiled successfully.").unwrap();
Add action buttons with NotificationAction:
use kael::NotificationAction;
cx.show_notification_with_actions(
"New message",
"You have an unread message from Alice.",
&[
NotificationAction { id: "reply".into(), label: "Reply".into() },
NotificationAction { id: "dismiss".into(), label: "Dismiss".into() },
],
|action_id| match action_id.as_str() {
"reply" => { /* open reply view */ }
"dismiss" => {}
_ => {}
},
)
.unwrap();
NotificationAction has two fields:
| Field | Type | Description |
|---|
id | String | Returned in the callback when the user clicks this button. |
label | String | Button text displayed in the notification. |
Showing notifications requires the Notification capability. The call returns Err if the capability has been denied.
Printing
Build a PrintJob, attach one or more PrintPage values, then either print silently or show the native print dialog.
use kael::{PrintJob, PrintPage, PrintOrientation, px};
let job = PrintJob::new("Invoice #1042")
.orientation(PrintOrientation::Portrait)
.page(PrintPage::new(size(px(612.), px(792.)), |ctx, _cx| {
// use ctx to issue drawing commands for this page
let _ = ctx;
}));
// Show the native print dialog
window.show_print_dialog(job, cx).unwrap();
// Or print directly without a dialog
// window.print(job, cx).unwrap();
All pages in a single PrintJob must use the same page size. PrintJob::new returns an error if you try to mix sizes.
Biometric authentication
Check whether Touch ID, Windows Hello, or a fingerprint reader is available before attempting authentication:
use kael::{BiometricStatus, BiometricKind};
match cx.biometric_status() {
BiometricStatus::Available(BiometricKind::TouchId) => {
cx.authenticate_biometric("Authenticate to unlock secrets", |success| {
if success {
// grant access
}
});
}
BiometricStatus::Available(BiometricKind::WindowsHello) => {
cx.authenticate_biometric("Verify your identity", |success| {
let _ = success;
});
}
BiometricStatus::Available(BiometricKind::Fingerprint) => {
cx.authenticate_biometric("Use your fingerprint to continue", |success| {
let _ = success;
});
}
BiometricStatus::Unavailable => {
// fall back to a password prompt
}
}
BiometricStatus | Meaning |
|---|
Available(BiometricKind::TouchId) | macOS Touch ID is enrolled and available. |
Available(BiometricKind::WindowsHello) | Windows Hello is configured. |
Available(BiometricKind::Fingerprint) | A generic fingerprint reader is available. |
Unavailable | No biometric hardware or it is not enrolled. |
Auto-launch at login
// Enable launch at login
cx.set_auto_launch("com.example.myapp", true).unwrap();
// Disable launch at login
cx.set_auto_launch("com.example.myapp", false).unwrap();
// Query the current state
let enabled = cx.is_auto_launch_enabled("com.example.myapp");
Pass your app’s bundle identifier on macOS or the app name on Windows and Linux.
Single instance enforcement
Kael’s platform::single_instance module uses a Unix domain socket (macOS / Linux) or a named mutex (Windows) to detect whether another instance is already running.
use kael::platform::single_instance;
match single_instance::acquire("com.example.myapp") {
Ok(guard) => {
// We are the primary instance. `guard` must be kept alive for
// the lifetime of the process.
Application::new().run(move |cx| {
let _guard = guard;
// start your app
});
}
Err(_) => {
// Another instance is running. Notify it and exit.
eprintln!("Another instance is already running.");
std::process::exit(0);
}
}
Dock and taskbar control
// Set the badge label on the macOS dock icon (or taskbar overlay on Windows)
cx.set_dock_badge(Some("3"));
cx.set_dock_badge(None); // clear the badge
// Request user attention (bounces the dock icon on macOS)
use kael::AttentionType;
cx.request_user_attention(AttentionType::Informational);
cx.cancel_user_attention();
// Set a taskbar/dock progress bar
use kael::ProgressBarState;
window.set_progress_bar(ProgressBarState::Normal(0.75));
window.set_progress_bar(ProgressBarState::None);