feat(cloud-files): Explorer-Sidebar-Integration fuer Windows
Registriert den Sync-Ordner als Shell-Namespace-Extension unter
HKEY_CURRENT_USER (kein Admin noetig), sodass er mit eigenem Icon
in der linken Leiste des Datei-Explorers erscheint - wie bei
OneDrive oder Dropbox.
- Neues Modul cloud_files::shell_integration mit install/uninstall
- Registry-Eintraege unter HKCU\Software\Classes\CLSID\{GUID} und
HKCU\...\Explorer\Desktop\NameSpace\{GUID}
- Nutzt die laufende .exe als Icon-Quelle (fallback: imageres.dll)
- SHChangeNotify(SHCNE_ASSOCCHANGED) damit Explorer sofort aktualisiert
- install/uninstall werden aus register_sync_root/unregister aufgerufen
- winreg-Crate fuer sauberen Registry-Zugriff
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2937082ba2
commit
4026defe79
|
|
@ -42,8 +42,10 @@ windows = { version = "0.58", features = [
|
|||
"Win32_System_CorrelationVector", # gate fuer CF_CALLBACK_INFO / CfExecute / CfConnectSyncRoot
|
||||
"Win32_UI_Shell",
|
||||
"Win32_Security",
|
||||
"Win32_System_Registry",
|
||||
] }
|
||||
widestring = "1"
|
||||
winreg = "0.52"
|
||||
|
||||
# Linux: FUSE-basiertes Virtual-Filesystem (optional, cargo build --features linux_fuse)
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ pub enum SyncState {
|
|||
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
#[cfg(windows)]
|
||||
pub mod shell_integration;
|
||||
#[cfg(all(target_os = "linux", feature = "linux_fuse"))]
|
||||
pub mod linux;
|
||||
pub mod sync_loop;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
//! Explorer-Sidebar-Integration fuer Windows (ohne Admin-Rechte).
|
||||
//!
|
||||
//! Registriert den Sync-Ordner als Shell-Namespace-Extension unter
|
||||
//! HKEY_CURRENT_USER, sodass er mit eigenem Icon in der Navigation
|
||||
//! des Datei-Explorers erscheint (wie OneDrive/Dropbox).
|
||||
//!
|
||||
//! Anders als die eigentliche Cloud Files API ist das reine Registry-
|
||||
//! Kosmetik - der Ordner funktioniert auch ohne Sidebar-Eintrag,
|
||||
//! nur sieht man ihn dann nicht in der linken Leiste.
|
||||
|
||||
#![cfg(windows)]
|
||||
|
||||
use std::path::Path;
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
|
||||
// Stabile GUID fuer Mini-Cloud - gleiche wie in windows.rs als ProviderId.
|
||||
const CLSID_GUID: &str = "{4D696E69-436C-6F75-6444-7566667944AB}";
|
||||
|
||||
// Standard-CLSID fuer "Generic Shell Folder Implementation".
|
||||
const SHELL_FOLDER_CLSID: &str = "{0E5AAE11-A475-4c5b-AB00-C66DE400274E}";
|
||||
|
||||
/// Registriere den Mount-Ordner in der Explorer-Navigation.
|
||||
/// `icon_source`: Pfad zu ICO oder EXE mit Icon-Index (z.B. "C:\\app.exe,0")
|
||||
pub fn install(
|
||||
display_name: &str,
|
||||
mount_point: &Path,
|
||||
icon_source: &str,
|
||||
) -> Result<(), String> {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
|
||||
// 1) CLSID-Eintrag unter Software\Classes\CLSID\{GUID}
|
||||
let clsid_path = format!("Software\\Classes\\CLSID\\{}", CLSID_GUID);
|
||||
let (clsid, _) = hkcu
|
||||
.create_subkey(&clsid_path)
|
||||
.map_err(|e| format!("create CLSID: {e}"))?;
|
||||
clsid
|
||||
.set_value("", &display_name.to_string())
|
||||
.map_err(|e| format!("set displayname: {e}"))?;
|
||||
clsid
|
||||
.set_value("System.IsPinnedToNameSpaceTree", &1u32)
|
||||
.map_err(|e| format!("set pinned: {e}"))?;
|
||||
clsid
|
||||
.set_value("SortOrderIndex", &0x42u32)
|
||||
.map_err(|e| format!("set sortorder: {e}"))?;
|
||||
|
||||
// 2) DefaultIcon
|
||||
let (icon_key, _) = clsid
|
||||
.create_subkey("DefaultIcon")
|
||||
.map_err(|e| format!("create DefaultIcon: {e}"))?;
|
||||
icon_key
|
||||
.set_value("", &icon_source.to_string())
|
||||
.map_err(|e| format!("set icon: {e}"))?;
|
||||
|
||||
// 3) InProcServer32 -> shell32.dll (Standard Shell-Folder-Host)
|
||||
let (inproc, _) = clsid
|
||||
.create_subkey("InProcServer32")
|
||||
.map_err(|e| format!("create InProcServer32: {e}"))?;
|
||||
inproc
|
||||
.set_value("", &"%SystemRoot%\\system32\\shell32.dll".to_string())
|
||||
.map_err(|e| format!("set shell32: {e}"))?;
|
||||
inproc
|
||||
.set_value("ThreadingModel", &"Both".to_string())
|
||||
.map_err(|e| format!("set threading: {e}"))?;
|
||||
|
||||
// 4) Instance -> zeigt auf generischen Shell-Folder
|
||||
let (instance, _) = clsid
|
||||
.create_subkey("Instance")
|
||||
.map_err(|e| format!("create Instance: {e}"))?;
|
||||
instance
|
||||
.set_value("CLSID", &SHELL_FOLDER_CLSID.to_string())
|
||||
.map_err(|e| format!("set inst clsid: {e}"))?;
|
||||
|
||||
let (pb, _) = instance
|
||||
.create_subkey("InitPropertyBag")
|
||||
.map_err(|e| format!("create InitPropertyBag: {e}"))?;
|
||||
pb.set_value("Attributes", &0x11u32)
|
||||
.map_err(|e| format!("set attrs pb: {e}"))?;
|
||||
pb.set_value(
|
||||
"TargetFolderPath",
|
||||
&mount_point.to_string_lossy().into_owned(),
|
||||
)
|
||||
.map_err(|e| format!("set target: {e}"))?;
|
||||
|
||||
// 5) ShellFolder-Flags
|
||||
let (sf, _) = clsid
|
||||
.create_subkey("ShellFolder")
|
||||
.map_err(|e| format!("create ShellFolder: {e}"))?;
|
||||
sf.set_value("FolderValueFlags", &0x28u32)
|
||||
.map_err(|e| format!("set folderflags: {e}"))?;
|
||||
sf.set_value("Attributes", &0xF080004Du32)
|
||||
.map_err(|e| format!("set attrs sf: {e}"))?;
|
||||
|
||||
// 6) In die Navigation einhaengen
|
||||
let ns_path = format!(
|
||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\{}",
|
||||
CLSID_GUID
|
||||
);
|
||||
let (ns, _) = hkcu
|
||||
.create_subkey(&ns_path)
|
||||
.map_err(|e| format!("create NameSpace: {e}"))?;
|
||||
ns.set_value("", &display_name.to_string())
|
||||
.map_err(|e| format!("set ns name: {e}"))?;
|
||||
|
||||
// 7) Explorer informieren (SHChangeNotify)
|
||||
notify_shell();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Entferne die Shell-Integration wieder.
|
||||
pub fn uninstall() -> Result<(), String> {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
|
||||
let ns_path = format!(
|
||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\{}",
|
||||
CLSID_GUID
|
||||
);
|
||||
let _ = hkcu.delete_subkey_all(&ns_path);
|
||||
|
||||
let clsid_path = format!("Software\\Classes\\CLSID\\{}", CLSID_GUID);
|
||||
let _ = hkcu.delete_subkey_all(&clsid_path);
|
||||
|
||||
notify_shell();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Teilt Explorer mit, dass sich die Shell-Namespace-Liste geaendert hat.
|
||||
/// Ohne das sieht man den neuen Eintrag erst nach Explorer-Neustart.
|
||||
fn notify_shell() {
|
||||
use windows::Win32::UI::Shell::{SHChangeNotify, SHCNE_ASSOCCHANGED, SHCNF_IDLIST};
|
||||
unsafe {
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard-Icon-Quelle: die laufende .exe mit Index 0.
|
||||
pub fn default_icon_source() -> String {
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.to_str().map(|s| format!("{},0", s)))
|
||||
.unwrap_or_else(|| "%SystemRoot%\\system32\\imageres.dll,2".to_string())
|
||||
}
|
||||
|
|
@ -136,11 +136,22 @@ pub fn register_sync_root(
|
|||
log_msg(mount_point, "CfRegisterSyncRoot OK");
|
||||
connect_callbacks(mount_point)?;
|
||||
log_msg(mount_point, "callbacks connected");
|
||||
|
||||
// Explorer-Sidebar-Eintrag mit Wolken-Icon
|
||||
let icon = super::shell_integration::default_icon_source();
|
||||
match super::shell_integration::install(provider_name, mount_point, &icon) {
|
||||
Ok(()) => log_msg(mount_point, "shell integration installed"),
|
||||
Err(e) => log_err(mount_point, &format!("shell integration FAILED: {e}")),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unregister_sync_root(mount_point: &PathBuf) -> Result<(), String> {
|
||||
disconnect_callbacks()?;
|
||||
// Shell-Eintrag zuerst entfernen (schlaegt nie fehl).
|
||||
let _ = super::shell_integration::uninstall();
|
||||
|
||||
let _ = disconnect_callbacks();
|
||||
let path_wide = U16CString::from_str(mount_point.to_string_lossy().as_ref())
|
||||
.map_err(|e| e.to_string())?;
|
||||
unsafe {
|
||||
|
|
|
|||
Loading…
Reference in New Issue