Skip to content

Commit b5a3fba

Browse files
committed
feat: implement headless mode support with deep linking, context menu isolation, and robust OAuth process lifecycle management.
1 parent 09e6288 commit b5a3fba

9 files changed

Lines changed: 65 additions & 10 deletions

File tree

src-tauri/src/core/lifecycle/shutdown.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ pub async fn handle_shutdown(app_handle: AppHandle) {
107107
}
108108
}
109109
}
110+
111+
// Kill the OAuth subprocess if it's still alive.
112+
let state = app_handle.state::<RcloneState>();
113+
if let Some(mut child) = state.oauth_process.lock().await.take() {
114+
info!("Killing OAuth process during shutdown");
115+
let _ = child.kill().await;
116+
let _ = child.wait().await;
117+
}
110118
}
111119

112120
// Clear the in-memory config password so late-spawned processes can't read it.

src-tauri/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,12 @@ fn setup_app(
378378
initialization(app_handle_clone).await;
379379
});
380380

381+
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
382+
{
383+
use tauri_plugin_deep_link::DeepLinkExt;
384+
app.deep_link().register_all()?;
385+
}
386+
381387
#[cfg(feature = "web-server")]
382388
{
383389
use crate::server::start_web_server;

src-tauri/src/rclone/commands/remote.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,23 @@ pub async fn update_remote(
320320
return Err("Missing remote type".into());
321321
};
322322

323-
let mut body = json!({ "name": name, "parameters": parameters });
323+
let mut params_map = parameters;
324+
if !params_map.contains_key("config_template_file")
325+
&& !params_map.contains_key("config_template")
326+
{
327+
let paths_res = crate::core::paths::AppPaths::from_app_handle(&app);
328+
if let Ok(paths) = paths_res {
329+
let oauth_tmpl = paths.oauth_template_path();
330+
if oauth_tmpl.exists() {
331+
params_map.insert(
332+
"config_template_file".to_string(),
333+
Value::String(oauth_tmpl.to_string_lossy().to_string()),
334+
);
335+
}
336+
}
337+
}
338+
339+
let mut body = json!({ "name": name, "parameters": params_map });
324340

325341
if let Some(ref extra) = opt {
326342
body["opt"] = extra.clone();

src-tauri/src/rclone/engine/process.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl RcApiEngine {
7070

7171
let mut kill_error: Option<EngineError> = None;
7272

73-
if self.is_running() && child.id().is_some() {
73+
if child.id().is_some() {
7474
let state = app.state::<RcloneState>();
7575
let quit_request = backend.inject_auth(state.client.post(backend.url_for(core::QUIT)));
7676

src-tauri/src/utils/app/send_to/linux.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,12 @@ pub fn register(
5858
let home = get_home_dir()?;
5959
let paths = LinuxPaths::new(&home);
6060
let exec_path = if cfg!(feature = "flatpak") {
61-
let app_id = std::env::var("FLATPAK_ID")
62-
.unwrap_or_else(|_| crate::utils::app::platform::APP_ID.to_string());
61+
let default_app_id = if cfg!(feature = "web-server") {
62+
format!("{}.headless", crate::utils::app::platform::APP_ID)
63+
} else {
64+
crate::utils::app::platform::APP_ID.to_string()
65+
};
66+
let app_id = std::env::var("FLATPAK_ID").unwrap_or(default_app_id);
6367
format!("flatpak run {app_id}")
6468
} else {
6569
format!("\"{}\"", current_exe.to_string_lossy())

src-tauri/src/utils/app/send_to/windows.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
use std::path::{Path, PathBuf};
22

3+
/// Display label shown in the Windows context menu submenu header.
4+
const CONTEXT_MENU_LABEL: &str = if cfg!(feature = "web-server") {
5+
"RClone Manager Headless"
6+
} else {
7+
"RClone Manager"
8+
};
9+
10+
/// Registry sub-key name — must differ between desktop and headless so both can coexist.
11+
const REGISTRY_KEY: &str = if cfg!(feature = "web-server") {
12+
"RCloneManagerHeadless"
13+
} else {
14+
"RCloneManager"
15+
};
16+
317
struct WindowsPaths {
418
send_to: PathBuf,
519
}
@@ -25,13 +39,13 @@ fn register_context_menu_entry(
2539
icon_path: &str,
2640
command: &str,
2741
) -> Result<(), String> {
28-
let parent_path = format!(r"{root_path}\shell\RCloneManager");
42+
let parent_path = format!(r"{root_path}\shell\{REGISTRY_KEY}");
2943

3044
let (parent_key, _) = hkcu
3145
.create_subkey(&parent_path)
3246
.map_err(|e| format!("Failed creating registry key '{parent_path}': {e}"))?;
3347
parent_key
34-
.set_value("MUIVerb", &"RClone Manager")
48+
.set_value("MUIVerb", &CONTEXT_MENU_LABEL)
3549
.map_err(|e| format!("Failed creating registry key '{parent_path}': {e}"))?;
3650
parent_key
3751
.set_value("Icon", &icon_path)
@@ -61,7 +75,7 @@ fn register_context_menu_entry(
6175
fn unregister_context_menu_entry(hkcu: &winreg::RegKey, root_path: &str, name: &str) {
6276
use winreg::enums::KEY_ALL_ACCESS;
6377

64-
let parent_path = format!(r"{root_path}\shell\RCloneManager");
78+
let parent_path = format!(r"{root_path}\shell\{REGISTRY_KEY}");
6579
let Ok(parent_key) = hkcu.open_subkey_with_flags(&parent_path, KEY_ALL_ACCESS) else {
6680
return;
6781
};
@@ -74,7 +88,7 @@ fn unregister_context_menu_entry(hkcu: &winreg::RegKey, root_path: &str, name: &
7488
if shell_key.enum_keys().count() == 0 {
7589
drop(shell_key);
7690
drop(parent_key);
77-
let _ = hkcu.delete_subkey_all(&parent_path);
91+
let _ = hkcu.delete_subkey_all(format!(r"{root_path}\shell\{REGISTRY_KEY}"));
7892
}
7993
}
8094

@@ -189,7 +203,7 @@ pub fn is_registered(name: &str) -> Result<bool, String> {
189203
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
190204

191205
let registered_under = |root_path: &str| {
192-
hkcu.open_subkey(format!(r"{root_path}\shell\RCloneManager\shell\{name}"))
206+
hkcu.open_subkey(format!(r"{root_path}\shell\{REGISTRY_KEY}\shell\{name}"))
193207
.is_ok()
194208
};
195209

src-tauri/src/utils/process/command.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ impl Command {
2020

2121
cmd.stdout(Stdio::piped())
2222
.stderr(Stdio::piped())
23-
.stdin(Stdio::piped());
23+
.stdin(Stdio::piped())
24+
.kill_on_drop(true);
2425

2526
#[cfg(windows)]
2627
{

src-tauri/windows/hooks.nsh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
!macro NSIS_HOOK_PREUNINSTALL
22
Delete "$APPDATA\Microsoft\Windows\SendTo\* (RClone Manager).lnk"
3+
Delete "$APPDATA\Microsoft\Windows\SendTo\* (RClone Manager Headless).lnk"
34
DeleteRegKey HKCU "Software\Classes\*\shell\RCloneManager"
45
DeleteRegKey HKCU "Software\Classes\Directory\shell\RCloneManager"
6+
DeleteRegKey HKCU "Software\Classes\*\shell\RCloneManagerHeadless"
7+
DeleteRegKey HKCU "Software\Classes\Directory\shell\RCloneManagerHeadless"
58
!macroend

src-tauri/windows/wix-fragment.wxs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<DirectoryRef Id="SendToFolder">
1515
<Component Id="CleanupSendToShortcuts" Guid="F90B1E63-8CA6-4DDF-9C1D-F8E3B6897EFA">
1616
<RemoveFile Id="RemoveSendToShortcuts" Name="* (RClone Manager).lnk" On="uninstall" />
17+
<RemoveFile Id="RemoveSendToShortcutsHeadless" Name="* (RClone Manager Headless).lnk" On="uninstall" />
1718

1819
<!-- Clean up the intermediate directories we declared under AppDataFolder -->
1920
<RemoveFolder Id="RemoveSendToFolder" On="uninstall" />
@@ -23,6 +24,8 @@
2324
<!-- Cleanup registry keys on uninstallation -->
2425
<RegistryKey Root="HKCU" Key="Software\Classes\*\shell\RCloneManager" ForceDeleteOnUninstall="yes" />
2526
<RegistryKey Root="HKCU" Key="Software\Classes\Directory\shell\RCloneManager" ForceDeleteOnUninstall="yes" />
27+
<RegistryKey Root="HKCU" Key="Software\Classes\*\shell\RCloneManagerHeadless" ForceDeleteOnUninstall="yes" />
28+
<RegistryKey Root="HKCU" Key="Software\Classes\Directory\shell\RCloneManagerHeadless" ForceDeleteOnUninstall="yes" />
2629

2730
<!-- KeyPath dummy value to make the component self-contained -->
2831
<RegistryValue Root="HKCU" Key="Software\Classes\*\shell\RCloneManager" Name="WixCleanupHelper" Value="1" Type="integer" KeyPath="yes" />

0 commit comments

Comments
 (0)