- 1001
std::process::exit(vak_delivery::worker::run_stdio()); - 1002
} - 1003
// One line, always, before any plugin can bail out. The - 1004
// single-instance plugin calls `std::process::exit(0)` from inside its - 1005
// own setup when another instance already owns the socket, so without - 1006
// this a launchd start that hands over leaves `desktop.log` completely - 1007
// empty and indistinguishable from a job that never ran at all. - 1008
eprintln!( - 1009
"[vak-desktop] starting pid={} tray={} args={:?}", - 1010
std::process::id(), - 1011
tray_only_start(), - 1012
std::env::args().skip(1).collect::<Vec<_>>() - 1013
); - 1014
let launch_project = requested_project(std::env::args_os()); - 1015
tauri::Builder::default() - 1016
// Exactly one instance ever runs: a second launch hands its argv to - 1017
// the live process and refocuses that window instead of starting a - 1018
// rival shell with its own backend and its own project state. - 1019
// Must be registered first so it can bail out before any other - 1020
// plugin or the setup hook does work. - 1021
// A handover from a `--tray` launch must stay silent. The - 1022
// `com.vak.desktop` LaunchAgent fires at every login, and if the - 1023
// app is already up (macOS reopened it, or the user never quit) - 1024
// that agent's process hands its argv here and exits 0. Showing - 1025
// the window then would throw one on screen at login — exactly - 1026
// what `--tray` exists to prevent. Every human second launch - 1027
// (Dock, Finder, `open`) still carries no flag and still reveals. - 1028
.plugin(tauri_plugin_single_instance::init(|app, argv, _cwd| { - 1029
if let Some(project) = requested_project(&argv) { - 1030
let handle = app.clone(); - 1031
tauri::async_runtime::spawn(async move { - 1032
let state = handle.state::<BackendState>(); - 1033
if let Err(e) = start_project_backend( - 1034
handle.clone(), - 1035
&state, - 1036
project.to_string_lossy().into_owned(), - 1037
true, - 1038
None, - 1039
) - 1040
.await - 1041
{ - 1042
eprintln!("project launch failed: {e}"); - 1043
} - 1044
}); - 1045
} - 1046
if !is_tray_launch(&argv) { - 1047
show_main_window(app); - 1048
} - 1049
})) - 1050
.plugin(tauri_plugin_dialog::init()) - 1051
.plugin(tauri_plugin_notification::init()) - 1052
.manage(BackendState { - 1053
running: Mutex::new(None), - 1054
switching: tokio::sync::Mutex::new(()), - 1055
}) - 1056
.manage(TrayState { - 1057
watchdog: Arc::new(AtomicBool::new(load_watchdog())), - 1058
autostart: Arc::new(AtomicBool::new( - 1059
vak_ops::services::is_service_autostart_enabled("com.vak.desktop"), - 1060
)), - 1061
last_rendered: Mutex::new(None), - 1062
}) - 1063
.manage(pty::PtyMap::default()) - 1064
.setup(move |app| { - 1065
install_tray(app)?; - 1066
// The window ships hidden so a `--tray` login launch never - 1067
// flashes one; an ordinary launch reveals it right here. - 1068
if !tray_only_start() { - 1069
show_main_window(app.handle()); - 1070
} - 1071
let handle = app.handle().clone(); - 1072
tauri::async_runtime::spawn(async move { - 1073
// Same secret-loading contract as the CLI: the Shared - 1074
// secret scope always; the picked workspace's own too (the - 1075
// folder was explicitly chosen, so it is trusted). - 1076
vak_config::replace_env_files(&[vak_home().join(".env").as_path()]); - 1077
let explicit_project = launch_project; - 1078
// Vakyartha opens its canonical personal workspace on first launch. - 1079
// Choosing a folder is a later workspace switch, not an - 1080
// onboarding prerequisite; the default can be changed from - 1081
// the workspace controls whenever the person wants. - 1082
let cwd = startup_workspace(explicit_project.clone(), last_project()); - 1083
if !cwd.exists() - 1084
&& let Err(error) = std::fs::create_dir_all(&cwd) - 1085
{ - 1086
let message = format!( - 1087
"could not create default workspace {}: {error}", - 1088
cwd.display() - 1089
); - 1090
eprintln!("{message}"); - 1091
let state = handle.state::<BackendState>(); - 1092
set_boot_error(&state, Some(message)); - 1093
return; - 1094
} - 1095
{ - 1096
let state = handle.state::<BackendState>(); - 1097
if let Err(e) = start_project_backend( - 1098
handle.clone(), - 1099
&state, - 1100
cwd.to_string_lossy().into_owned(), - 1101
true, - 1102
None, - 1103
) - 1104
.await - 1105
{ - 1106
eprintln!("backend boot failed: {e}"); - 1107
// Surface it to the project gate; a bundled app has - 1108
// no stderr to show. - 1109
set_boot_error(&state, Some(e)); - 1110
} - 1111
} - 1112
}); - 1113
Ok(()) - 1114
}) - 1115
.invoke_handler(tauri::generate_handler![ - 1116
backend_info, - 1117
forget_workspace_desktop, - 1118
get_desktop_autostart, - 1119
set_desktop_autostart, - 1120
open_admin, - 1121
review_workspace, - 1122
start_backend, - 1123
export_file, - 1124
open_workspace_file, - 1125
pty::spawn_pty, - 1126
pty::pty_write, - 1127
pty::pty_resize, - 1128
pty::pty_close - 1129
]) - 1130
.build(tauri::generate_context!()) - 1131
.map(|app| { - 1132
app.run(|app, event| match event { - 1133
#[cfg(target_os = "macos")] - 1134
RunEvent::Reopen { .. } => show_main_window(app), - 1135
RunEvent::WindowEvent { - 1136
label, - 1137
event: WindowEvent::CloseRequested { api, .. }, - 1138
.. - 1139
} if label == "main" => { - 1140
api.prevent_close(); - 1141
if let Some(window) = app.get_webview_window("main") { - 1142
let _ = window.hide(); - 1143
} - 1144
} - 1145
_ => {} - 1146
}) - 1147
}) - 1148
.inspect_err(|e| eprintln!("fatal: {e}")) - 1149
.ok(); - 1150
} - 1151
- 1152
#[cfg(test)] - 1153
#[allow(clippy::unwrap_used, clippy::expect_used)] - 1154
mod tests { - 1155
use super::{TRAY_FLAG, is_tray_launch, requested_project, startup_workspace}; - 1156
- 1157
/// The single-instance plugin hands a second process's whole argv - 1158
/// (program name included) to the live app. A login launch from the - 1159
/// `com.vak.desktop` LaunchAgent carries `--tray` and must not be - 1160
/// mistaken for a user asking for the window. - 1161
#[test] - 1162
fn tray_flag_is_recognised_in_a_handed_over_argv() { - 1163
assert!(is_tray_launch([ - 1164
"/Applications/Vakyartha.app/Contents/MacOS/vak-desktop", - 1165
TRAY_FLAG - 1166
])); - 1167
assert!(is_tray_launch(["vak-desktop", "--tray"])); - 1168
} - 1169
- 1170
/// Dock, Finder and `open` launches carry no flag; those are the ones - 1171
/// that must reveal the window. - 1172
#[test] - 1173
fn an_ordinary_launch_is_not_a_tray_launch() { - 1174
assert!(!is_tray_launch(["vak-desktop"])); - 1175
assert!(!is_tray_launch(Vec::<String>::new())); - 1176
assert!( - 1177
!is_tray_launch(["vak-desktop", "--traypad"]), - 1178
"the flag must match whole arguments, not prefixes" - 1179
); - 1180
} - 1181
- 1182
#[test] - 1183
fn project_argument_is_selected_without_treating_flags_as_paths() { - 1184
let project = std::env::current_dir().expect("test has a current directory"); - 1185
let project_text = project.to_string_lossy().into_owned(); - 1186
assert_eq!( - 1187
requested_project(["vak-desktop", "--tray", project_text.as_str()]), - 1188
Some(project) - 1189
); - 1190
} - 1191
- 1192
#[test] - 1193
fn first_launch_uses_default_workspace_and_precedence_is_explicit() { - 1194
let explicit = std::path::PathBuf::from("/tmp/explicit-vak-workspace"); - 1195
let remembered = std::path::PathBuf::from("/tmp/remembered-vak-workspace"); - 1196
assert_eq!( - 1197
startup_workspace(None, None), - 1198
vak_config::paths::default_workspace() - 1199
); - 1200
assert_eq!( - 1201
startup_workspace(None, Some(remembered.clone())), - 1202
remembered - 1203
); - 1204
assert_eq!( - 1205
startup_workspace(Some(explicit.clone()), Some(remembered)), - 1206
explicit - 1207
); - 1208
} - 1209
} - 1210
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.