Files
ps5upload/client/src-tauri/src/lib.rs
T
phantomptrandClaude Opus 5.5 47023ee256 Files: send a multi-file pick as one file-list job
The Files screen started one AVA1 job per picked file (~0.46 s each in the
engine; 5,000 small files took 44 min). Two or more local files of a pick now
go up as ONE POST /api/transfer/file-list job under a client-minted tx_id.

- state/fsUploadPlan: settles name conflicts before the job (Skip / Keep both
  / Replace, Merge for a folder onto a folder, "do the same for the other N")
  against a fresh listing, then groups the pick into steps. A single file and
  each folder keep their own job (folder jobs already are one job and carry
  excludes, empty dirs, module renames, remote sources); remote:// files stay
  per-file.
- state/fsUpload: runs steps. A list shows overall bytes, finished files and
  the file in flight; cancel ends the one job; a stop names the files not yet
  on the PS5; retry/resume/restart re-send the same list under the same id so
  the console journal skips finished files. An engine/shell without the route
  falls back to per-file jobs.
- ConflictAsk dialog replaces the all-or-nothing Overwrite confirm.
- startTransferFileList, Tauri transfer_file_list, browserInvoke mapping.
- 10 new strings in all 21 locales; retired fs_add_files_conflict_*.

Phat 13.60, release engine: 240 files/348 MB 137.7 s -> 4.6 s; 5,000 small
files 44 min -> 27.7 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
2026-10-18 20:15:34 -07:00

608 lines
30 KiB
Rust

//! PS5 Upload — Tauri desktop shell (Rust backend).
//!
//! The renderer (React + Vite) calls into Rust via `tauri::invoke`
//! against the `#[command]` handlers in `commands`. Engine lifecycle,
//! file dialogs, and app-data persistence live on this side.
//!
//! Architecture:
//!
//! Tauri main (this crate) ── spawns ──> ps5upload-engine (Rust sibling)
//! │ │
//! ├ tauri::invoke commands │ HTTP :19113
//! │ (fs, config, dialogs, status) │
//! │ ▼
//! └ WebView (React renderer) ── fetch ──> engine HTTP API
//!
//! We keep the engine as its own process so its HTTP surface stays a
//! stable, language-agnostic interface. A later phase can link
//! `ps5upload-core` directly and remove the HTTP hop entirely;
//! nothing in the command contract needs to change for that.
/// Fixed loopback URL the renderer talks to. Both the desktop sidecar
/// (`engine.rs`) and the mobile in-process server (`engine_mobile.rs`)
/// serve here.
pub(crate) const DEFAULT_ENGINE_URL: &str = "http://127.0.0.1:19113";
mod linux_gpu;
mod commands;
// The engine runs as a spawned sidecar binary on desktop, but in-process
// (linked library) on mobile, where Tauri has no sidecar model. Same
// `start`/`stop`/`url` surface either way; see engine.rs / engine_mobile.rs.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod engine;
#[cfg(any(target_os = "android", target_os = "ios"))]
#[path = "engine_mobile.rs"]
mod engine;
// HTTP client for talking to either engine. `pub` only so
// tests/engine_http_proxy.rs can drive it with proxy variables set.
pub mod engine_http;
// Desktop management calls ride the sidecar engine's AVA1 session (see the module doc).
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod mgmt_forward;
// One-time 6.0 upgrade clean-up of the app's data folder (see the module doc).
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mod migrate_6;
// Background watcher: job notifications and, on Android, the transfer service (#457).
#[cfg(target_os = "android")]
mod android_bg;
mod bg_activity;
// The JavaVM / Application context every Android JNI caller shares.
#[cfg(target_os = "android")]
mod android_jni;
/// Build and run the Tauri application. The desktop `main.rs` calls this
/// directly; on mobile the `tauri::mobile_entry_point` macro generates
/// the JNI/Obj-C entry symbol the Android/iOS harness invokes (without
/// it the built `.so` fails Tauri's "missing required runtime symbols"
/// validation).
/// Open the main window centred and fully on-screen, shrinking it first if it
/// would overflow a small / low-res display ("crappy computers"). The
/// tauri.conf centre/size hints are applied at window creation and are
/// unreliable in practice (notably on macOS and under `tauri dev`), leaving
/// the window off-screen / off-centre — re-centring at runtime is reliable.
///
/// Desktop-only: the monitor / `center` / `set_size` window APIs don't exist
/// on mobile (where the app is full-screen anyway), so the mobile build gets a
/// no-op stub. (A previous version called these unconditionally and broke the
/// Android cross-compile: `no method named 'center' found`.)
#[cfg(desktop)]
fn center_main_window<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
use tauri::Manager;
if let Some(win) = app.get_webview_window("main") {
let monitor = win
.current_monitor()
.ok()
.flatten()
.or_else(|| win.primary_monitor().ok().flatten());
if let (Some(monitor), Ok(size)) = (monitor, win.outer_size()) {
let mon = monitor.size();
let max_w = (mon.width as f64 * 0.95) as u32;
let max_h = (mon.height as f64 * 0.90) as u32;
if size.width > max_w || size.height > max_h {
let _ = win.set_size(tauri::PhysicalSize::new(
size.width.min(max_w),
size.height.min(max_h),
));
}
}
let _ = win.center();
}
}
#[cfg(mobile)]
fn center_main_window<R: tauri::Runtime>(_app: &tauri::AppHandle<R>) {}
/// Started as the one-shot screenshot decoder (a child of the app, see
/// commands::screenshot_convert): converts, then exits the process. False for a normal launch.
pub fn screenshot_decoder_child() -> bool {
commands::screenshot_convert::child_mode()
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
// Linux WebKitGTK white-screen rescue, matched to the graphics stack.
//
// The shipped `PS5Upload.sh` launcher exports these too, but a user who
// double-clicks the bare AppImage (or runs the .deb / .rpm / folder
// build) never goes through that wrapper. Setting them here — at the top
// of process startup, before the webview's child web process is spawned
// and inherits our environment — covers every launch method.
//
// Which variables, and why not simply all of them, is in linux_gpu.rs.
// Short version: disabling accelerated compositing costs everyone their
// scrolling smoothness, so it is applied only to the stack that needs it
// (NVIDIA on Wayland, issue #285) rather than to all Linux users.
//
// Single-threaded at this point (no Tauri runtime, no engine thread
// yet), so set_var is sound; edition 2021 keeps it safe (non-unsafe).
// Only ever set when unset, so an explicit user choice wins in either
// direction — including forcing a rescue back OFF.
#[cfg(target_os = "linux")]
for (key, value) in linux_gpu::webkit_workarounds(linux_gpu::probe()) {
if std::env::var_os(key).is_none() {
std::env::set_var(key, value);
}
}
let builder = tauri::Builder::default();
// Single-instance MUST run before any other setup (window creation,
// engine spawn). When a second launch hits, the runtime calls our
// callback in the ORIGINAL process with the second instance's argv,
// then exits the duplicate. This prevents the double-orphan-reap
// race where launch #2's `engine::start()` would otherwise kill
// launch #1's engine and leave its UI talking to a dead port.
//
// Desktop-only: a mobile app already has a single instance by
// construction, and the plugin's `init` API isn't available on
// Android/iOS. Rebinding `builder` behind a cfg keeps the fluent
// chain below untouched.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let builder = builder.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
// Focus the existing main window so the user sees something
// happen rather than a silent no-op when they re-launch.
// `get_webview_window("main")` matches the window label
// declared in tauri.conf.json.
use tauri::Manager;
if let Some(window) = app.get_webview_window("main") {
let _ = window.unminimize();
let _ = window.show();
let _ = window.set_focus();
}
}));
// Drag a file from Files onto the desktop (desktop only; the renderer
// stages the download first, see commands/dragout.rs).
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let builder = builder.plugin(tauri_plugin_drag::init());
let app = builder
.plugin(tauri_plugin_dialog::init())
// Opener: the cross-platform way to open external URLs / reveal files.
// It uses an Android Intent rather than spawning a system opener
// process, so in-app links AND the self-update "open the APK/release
// in the browser" flow work on mobile.
.plugin(tauri_plugin_opener::init())
// Native OS notifications. The renderer mirrors important in-app
// inbox entries to the system notification center / Android shade
// (see lib/osNotify.ts). Works on every platform.
.plugin(tauri_plugin_notification::init())
.setup(|app| {
// Before anything can call into core: management calls forward to the sidecar.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
mgmt_forward::install();
// Open the main window centred + fully on-screen. Desktop-only —
// the centre/monitor/size window APIs don't exist on mobile, so
// this is a no-op there (see center_main_window).
center_main_window(app.handle());
// One-time 6.0 upgrade clean-up of this app's data folder (old files are moved
// aside, never reset). Desktop-only: mobile installs start on 6.x.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
use tauri::Manager;
if let Ok(dir) = app.path().app_data_dir() {
for line in migrate_6::run(&dir) {
eprintln!("[tauri] 6.0 upgrade: {line}");
}
}
}
// Spawn the Rust engine binary as a sidecar. On failure we log and
// keep the window open so the user can see diagnostic info — the
// UI's passive status polling will flag the unreachable engine
// without requiring a re-launch.
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
match engine::start(&handle).await {
Ok(url) => {
eprintln!("[tauri] engine ready at {url}");
// Tell the renderer where the engine actually
// landed, so its DIRECT fetches (job polling,
// cover-art img-src, streaming) target the right
// port even when we fell back off 19113 because
// something was squatting it.
use tauri::Emitter;
let _ = handle.emit("ps5upload-engine-ready", &url);
// Watches the engine for running and finished jobs from here on,
// whatever the WebView is doing.
bg_activity::spawn(&handle);
}
Err(e) => {
let message = format!("engine failed to start: {e:#}");
{
use tauri::Emitter;
let _ = handle.emit("ps5upload-engine-startup-error", &message);
}
eprintln!("[tauri] {message}");
}
}
});
Ok(())
})
.invoke_handler(tauri::generate_handler![
// ── Engine proxies ──────────────────────────────────────
// Thin wrappers over the ps5upload-engine HTTP API
// (localhost:19113). Used by the Upload/Library/Volumes/
// FileSystem/Hardware surfaces.
commands::engine_url_set,
commands::engine_url_get,
commands::engine_diagnose,
commands::engine_restart,
commands::ps5_volumes,
commands::ps5_helper_stop,
commands::pkg_scan_external,
commands::pkg_metadata_console,
commands::ps5_list_dir,
commands::ps5_apps_installed,
commands::ps5_readiness,
commands::transfer_file,
commands::transfer_dir,
commands::transfer_file_list,
commands::transfer_zip,
commands::zip_inspect,
commands::zip_inspect_stream,
commands::transfer_7z,
commands::sevenz_inspect,
commands::sevenz_inspect_stream,
commands::transfer_rar,
commands::rar_inspect,
commands::profile_info,
commands::profile_set_username,
commands::profile_rename_user,
commands::profile_activate,
commands::user_create,
commands::user_delete,
commands::backup_snapshot,
commands::backup_list,
commands::backup_restore,
commands::backup_delete,
commands::remoteplay_request,
commands::remoteplay_status,
commands::health_scan,
commands::health_junk,
commands::health_fix,
commands::remoteplay_readiness,
commands::remoteplay_enable,
commands::remoteplay_cancel,
commands::notif_list,
commands::notif_clear,
commands::activity_reset,
commands::local_image_attach,
commands::local_image_detach,
commands::local_image_status,
commands::cheats_list,
commands::cheats_get,
commands::cheats_toggle,
commands::cheats_delete,
commands::cheats_reload,
commands::cheats_status,
commands::cheats_engine_set,
commands::cheats_repos_list,
commands::cheats_repos_search,
commands::cheats_repos_download,
commands::activity_get,
commands::activity_db_query,
commands::sdk_scan,
commands::sdk_patch,
commands::sdk_restore,
commands::fw_spoof_status,
commands::profile_avatar_preview,
commands::profile_avatar_current,
commands::profile_apply_avatar,
commands::transfer_dir_reconcile,
commands::transfer_dir_diff_preview,
commands::transfer_download,
commands::transfer_download_zip,
commands::ps5_fs_delete,
commands::ps5_fs_move,
commands::ps5_fs_copy,
commands::ps5_fs_op_status,
commands::ps5_fs_op_cancel,
commands::ps5_fs_mount,
commands::ps5_fs_unmount,
commands::ps5_fs_chmod,
commands::ps5_fs_mkdir,
commands::ps5_hw_info,
commands::ps5_hw_temps,
commands::ps5_hw_power,
commands::ps5_focus,
commands::cache_artwork_stats,
commands::cache_artwork_clear,
commands::ps5_app_icon_data,
commands::ps5_game_icon_data,
commands::collection_cover_data,
commands::ps5_appinfo_query,
commands::ps5_syslog_tail,
commands::ps5_hw_storage,
commands::ps5_hw_drive_sensors,
commands::ps5_time_get,
commands::ps5_time_sync,
commands::ps5_hw_set_fan_threshold,
commands::ps5_smp_meta_control,
commands::ps5_smp_meta_stats,
commands::ps5_app_launch,
commands::ps5_app_register,
commands::ps5_app_unregister,
commands::fpkg_inspect,
commands::game_inspect,
commands::game_inspect_image,
commands::game_inspect_files,
commands::fpkg_build,
commands::fpkg_delete,
commands::fpkg_estimate,
commands::ps5_elfldr_health,
commands::ps5_elfldr_ensure,
commands::ffpfsc_compress,
commands::exfat_build,
commands::folder_build,
commands::fpkg_extract,
commands::fpkg_extract_cleanup,
commands::job_status,
commands::job_cancel,
commands::engine_logs_tail,
// ── PKG install (parse + host + install via BGFT) ─────────
// Engine proxies for the new Install Package tab. Single
// path: parse local .pkg → host over HTTP → tell payload to
// call sceBgftService* → poll status.
commands::pkg_metadata_split,
// Read-only UFS2 image inspector (browse a local .ffpkg
// before uploading). See ps5upload_pkg::ufs2.
commands::ffpkg_inspect,
commands::ffpkg_extract,
commands::pkg_installed_inventory,
commands::pkg_install_preflight,
commands::pkg_remote_download_start,
commands::pkg_remote_download_status,
commands::pkg_remote_download_cancel,
commands::pkg_remote_probe,
commands::link_probe,
commands::link_download,
commands::rar_packages,
commands::speed_test_prepare,
commands::speed_test_cleanup,
commands::pkg_console_probe,
commands::pkg_install,
commands::pkg_install_status_v2,
commands::pkg_install_stop,
commands::host_net_open_settings,
commands::host_net_allow_firewall,
// ── Scene-tool integration ──────────────────────────────
// `companion_probe` checks which well-known scene tools
// are alive on the PS5 host.
commands::companion_probe,
// ── LAN discovery (mDNS-SD + TCP probe) ─────────────────
// `discover_ps5` browses well-known mDNS service types
// and TCP-probes :9021/:9120 on each discovered host so
// the Connection screen's "Find PS5s" button can populate
// the IP field automatically. Read-only (we don't
// advertise ourselves). See commands/discover.rs.
commands::discover_ps5,
// ── Payload Library (curated catalogue + GitHub fetch) ──
// The Payloads tab uses these to list third-party
// homebrew payloads (kstuff, SMP, etaHEN, …),
// fetch their latest releases from GitHub, cache to disk,
// and re-send via the existing payload_send flow. See
// commands/payloads.rs.
commands::payloads_catalog,
commands::payloads_release,
commands::payloads_releases,
commands::payloads_local_inventory,
commands::payloads_local_path,
commands::payloads_download,
commands::payloads_add_custom_repo,
commands::payloads_remove_custom_repo,
// ── ShadowMount+ awareness (read-only) ──────────────────
// `smp_status` collects the on-console SMP state — config,
// autotune, mounted images, debug-log tail — so the
// Library tab can surface it without making the user FTP
// into the console. We never write SMP's files. See
// commands/smp.rs.
commands::smp_status,
// Edit sessions: check an image out of ShadowMount+ so its
// contents can be changed, then put it back. See
// `ps5upload_core::smp_checkout` for why an image has to be moved
// out of SMP's scan folders rather than simply unmounted.
commands::smp_checkout_status,
commands::smp_checkout_begin,
commands::smp_checkout_finish,
commands::smp_image_rw_status,
commands::smp_image_rw_begin,
commands::smp_image_rw_finish,
// ── USB autoloader wizard ───────────────────────────────
// Enumerate removable drives + write a curated
// ps5_autoloader/ folder to one. Cross-platform; pulls
// payloads from the Phase 2 cache, never re-downloads
// here. See commands/usb_autoloader.rs.
commands::usb_list_removable,
commands::usb_autoloader_install,
// ── Game metadata healing ───────────────────────────────
// `heal_appmeta` restores missing /user/appmeta/<TID>/
// entries (icon0.png, param.json, snd0.at9) from the
// game source's sce_sys/. See commands/heal_appmeta.rs.
commands::heal_appmeta,
// ── PS5 power control (reboot/shutdown/standby/tick) ────
// Sends SYSTEM_CONTROL frames; the destructive actions
// tear down the network so a connection-drop is treated
// as success in core. See commands/system_power.rs.
commands::power_reboot,
commands::power_shutdown,
commands::power_standby,
commands::power_tick,
commands::power_telemetry_get,
commands::process_list_get,
commands::process_kill_pid,
// ── Save data + screenshot listing ──────────────────────
commands::saves_list,
commands::screenshots_list,
commands::videos_list,
commands::screenshot_convert,
// ── Save data .zip backup/restore ───────────────────────
// The Saves screen uses these to wrap a downloaded save
// folder into <title_id>.zip (and reverse on restore). The
// download/upload bytes themselves still go through the
// engine's transfer jobs; these commands only manage the
// temp scratch dir and the zip envelope around it.
commands::save_archive_make_temp,
commands::save_archive_cleanup_temp,
commands::save_archive_zip,
commands::save_auto_backup_slot,
commands::save_archive_unzip,
// Format-aware backup/restore: strip `sdimg_` prefix from
// PS4-style images and drop Sony's nested bookkeeping so
// backups are clean and cross-tool compatible.
commands::save_archive_backup_finalize,
commands::save_archive_restore_prepare,
// Generic text read/write at a user-picked path — backs the
// bug-report bundle + settings export/import + search/stats
// exports, which can't use plugin-fs read/writeTextFile
// (dialog paths fall outside the fs scope). See
// commands/save_text_file.rs.
commands::save_text_file,
commands::dragout_stage_dir,
commands::read_text_file,
// ── Automatic crash/error report collection ─────────────
// See commands/crash_reports.rs + src/lib/crashReporter.ts.
commands::crash_reports::crash_report_save,
commands::crash_reports::crash_reports_zip,
// ── Persistent on-disk app log (bug-report time windows) ─
// The renderer batches its unified log to ~/.ps5upload/logs/
// so "package the last N minutes" survives a crash. See
// commands/diag_log.rs + src/state/logs.ts.
commands::app_events::app_events_append,
commands::app_events::app_events_read,
commands::host_platform::host_platform,
commands::diag_log::diag_log_append,
commands::diag_log::diag_log_open_dir,
// ── Bug-report bundle (zip: logs + ps5 snapshot + images) ─
// Assembles the one-click bug report. See commands/bug_report.rs
// + src/screens/BugReport.
commands::bug_report::bug_report_build,
// ── In-app screenshot capture (bug-report gallery) ──────
commands::bug_screenshots::screenshot_save,
commands::bug_screenshots::screenshot_list,
commands::bug_screenshots::screenshot_delete,
commands::bug_screenshots::screenshot_open_dir,
// ── App lifecycle (suspend/resume/kill/list) + toast ────
commands::app_suspend,
commands::app_resume,
commands::app_kill,
commands::app_list_running,
commands::toast_push,
// ── Diagnostics: klog stream, net interfaces, peripheral
// control (disc, BD power, beep, LED), loaded modules ──
commands::klog_chunk,
commands::net_interfaces_get,
commands::peripheral_eject,
commands::peripheral_bd_off,
commands::peripheral_bd_on,
commands::peripheral_beep,
commands::peripheral_led,
commands::peripheral_led_dim,
commands::proc_modules_get,
commands::proc_list_get,
// Shell + CRC32 + app.db query
commands::shell_run_cmd,
commands::crc32_file_get,
commands::appdb_query_get,
commands::fs_write_bytes_run,
commands::fs_read_preview,
commands::fs_blake3_hash,
// ── Persistence (Payloads → Send recent history) ────────
commands::send_payload_history_load,
commands::send_payload_history_add,
commands::send_payload_history_clear,
// ── Persistence (cross-session resume tx_ids) ───────────
// The client generates a stable tx_id for each upload and
// remembers it here; on Resume after a failed run (or even
// after closing the app), the same tx_id is reused so the
// payload's journal can surface last_acked_shard.
commands::resume_txid_lookup,
commands::resume_txid_remember,
commands::resume_txid_forget,
// ── Persistence (upload queue + payload playlists) ──────
// Whole-document JSON stores. Renderer owns the shape.
commands::upload_queue_load,
commands::upload_queue_save,
commands::payload_playlists_load,
commands::payload_playlists_save,
// ── FAQ + changelog content (bundled markdown) ──────────
commands::faq_load,
commands::changelog_load,
// ── Host-local helpers (in-process, no HTTP hop) ────────
commands::inspect_folder, // param.json + disk-footprint walk
commands::path_kind, // "file" | "folder" for drag-drop routing
commands::payload_bundled_path, // resolve the bundled ps5upload.elf
commands::keep_awake_set, // spawn/kill platform sleep inhibitor (manual toggle)
commands::keep_awake_state,
commands::keep_awake_acquire, // refcounted hold by reason (auto: active transfer)
commands::keep_awake_release,
commands::user_config_load, // ~/.ps5upload/settings.json read
commands::user_config_save, // ~/.ps5upload/settings.json atomic write
commands::user_config_path_resolved, // show-the-path for the Settings UI
commands::app_data_reset, // factory-reset: wipe all local data/metadata
// ── Connectivity + payload lifecycle probes ─────────────
// Used by the Connection + Send payload tabs for
// reachability checks and payload delivery.
commands::port_check,
commands::payload_send,
commands::payload_check,
commands::payload_probe,
// ── Self-update ─────────────────────────────────────────
// Check the GitHub release manifest, download the
// platform-specific archive, reveal it so the user can
// install manually. No installer / no signing needed. See
// commands/updates.rs for the full flow.
commands::update_check,
commands::update_download,
// ── Title metadata fetch (cover art + display title) ────
// Server-side fetch (Rust/reqwest) of the public PS5
// title-info site, bypassing renderer CSP/CORS. Hostname
// allowlist enforced in commands/title_meta.rs.
commands::title_meta_fetch,
// ── Local filesystem browse + Android all-files access ──
// Backs the in-app file/folder picker used on Android (where
// native dialogs return content:// URIs the engine can't
// read). Harmless helpers on desktop.
commands::local_list_dir,
commands::local_storage_roots,
commands::storage_access_granted,
commands::request_storage_access,
// ── Background work (#457) ──────────────────────────────
// The renderer's queued-work hold, its visibility / language,
// and whether the host posts job notifications itself.
bg_activity::bg_work_hold,
bg_activity::bg_configure,
bg_activity::bg_capabilities,
])
.build(tauri::generate_context!())
.expect("tauri runtime failed to build");
// App-level run loop. We use `RunEvent::Exit` (fires once when the
// Tauri runtime is about to leave its run loop, regardless of how
// many windows are open / closed) instead of the previous
// per-window `CloseRequested` so multi-window scenarios don't kill
// the engine on first-window-close, and so the kill always runs
// before the process exits. Combined with the engine's stdin-EOF
// parent watcher (which catches the case where this process dies
// without running to here at all — segfault, taskkill /F, OOM,
// etc.), the engine never gets orphaned holding 19113.
app.run(|_handle, event| {
if let tauri::RunEvent::Exit = event {
// block_on is fine here: we're on the main thread, the
// tauri runtime has already finished its event loop, and
// the kill is a couple of syscalls + a short wait. The
// tokio runtime stays alive until we return from this
// closure, so the async kill path completes.
tauri::async_runtime::block_on(async {
// Release the keep-awake inhibitor first. Its handle lives
// in a `static` that isn't dropped at process exit, so an
// enabled caffeinate/systemd-inhibit child would otherwise
// outlive the app and block the machine from idle-sleeping
// until reboot.
commands::keep_awake_release_on_exit().await;
engine::stop().await;
});
}
});
}