Being a real desktop app
Freddy wants a quick-note window behind a shortcut, a File menu, an icon in the menu bar, and an app that opens a note from a link. None of it is a web page's business, and all of it is a few builder calls.
Features first
Most of this chapter is behind Cargo features, so an app only compiles in what it uses. Freddy adds them to the same dependency line:
elyra = { git = "https://github.com/kwhorne/elyra-framework", tag = "v0.16.0",
features = ["database", "tray", "shortcuts", "autostart", "system"] }
Windows, the menu, single-instance and deep links are core and need no feature. The framework runs on macOS, Windows and Linux; where a platform behaves differently, the guide says so, and so does this chapter.
Windows
The primary window is configured on App; more can be declared at
startup or opened at runtime, and all of them share one event loop, one protocol
handler and one event bus:
App::new()
.title("Freddy Notes")
.size(900.0, 640.0)
.min_size(420.0, 480.0)
.persist_window_state() // size, position and maximized, restored next launch
.run();
#[command]
async fn open_quick_note(ctx: Ctx) {
ctx.get::<Windows>().open(
WindowConfig::new("quick").title("Quick note").size(420.0, 260.0)
.always_on_top(true).path("quick"),
);
}
path opens a route of the Svelte app, so the quick-note window is
just another page. From the page, appWindow in
@elyra/runtime minimizes, sets the title, centers, closes and reports
size and focus; onFileDrop hands over the absolute paths of files
dropped on the window, which is how Freddy imports Markdown files. The app exits
when the last window closes.
The menu
App::new().menu(
Menu::new().submenu(
Submenu::new("File")
.item_accel("file.new", "New note", "CmdOrCtrl+N")
.separator()
.item("file.export", "Export…"),
),
)
A click emits elyra:menu with the item's id, and the page handles it
with onMenu. On macOS the submenus join the application menu bar next
to the built-in app and Edit menus, the Edit menu being what makes ⌘C and
⌘V reach the webview; on Windows and Linux they become a per-window menu bar
with a Help → About item.
The tray and a global shortcut
App::new()
.tray(TrayConfig::new().tooltip("Freddy Notes")
.item("quick", "Quick note").separator().quit("Quit"))
.global_shortcut("CmdOrCtrl+Shift+N")
A tray item's id arrives on the "tray" channel from chapter 4, and a
global shortcut, which fires even when the app is not focused, arrives through
onShortcut with its accelerator string. Freddy sends both to
open_quick_note. A shortcut that is invalid or already taken is
logged and skipped, and the others still register. The tray generates a plain
icon, so no image asset is needed to start.
One instance, and links that open a note
App::new().single_instance().deep_link("freddynotes").run()?;
With single_instance, a second launch hands its command line to the
first process and exits; the first raises its window and gets the payload on
elyra:second-instance. The handshake between them proves a random
per-install token with an HMAC instead of sending it, so another local process
cannot inject a fake deep link. With deep_link, Freddy reads the URL
the app was launched with through deepLink.initial() and later ones
through onDeepLink, and opens the note named in
freddynotes://…. Registering the scheme with the OS is the
bundle's job: without deep_link = "freddynotes" under
[bundle] in elyra.toml, the scheme never reaches the app,
which chapter 15 sets up.
Starting at login
import { autostart } from "@elyra/runtime";
if (!(await autostart.isEnabled())) await autostart.enable();
That is a LaunchAgent on macOS, the registry's Run key on Windows and a
.desktop entry on Linux. Freddy puts it behind a checkbox in his
settings, since starting at login is the user's decision.
Dialogs, the clipboard and the OS
The system feature adds what almost every app needs, driven from the
page with no Rust wiring: dialog.open and dialog.save
for files and folders, clipboard, notify,
paths() for the standard folders, and shell.openExternal.
That last one is policy-gated, because "open with the default app" must not become
"run this program": only http, https and
mailto by default, no file: URLs, and nothing executable.
A blocked call rejects with a message instead of doing nothing.
cargo run binary. If they seem not to work while
Freddy develops, that is why; chapter 15 bundles the app.
The guides are Windows, Application menu, System tray, Global shortcuts, Single-instance & deep-linking, Autostart and System integration.
What you learned
- Windows are configured on
Appor opened at runtime withWindows::open, each able to open a route of the Svelte app. - The menu, the tray and global shortcuts arrive on the page as events, and the platforms render menus differently.
single_instanceanddeep_linkmake one app that opens links, with an authenticated handshake.- The
systemfeature gives dialogs, the clipboard, notifications and paths, andopenExternalis policy-gated. - On macOS, notifications and autostart want a bundled app.