feat(windows): implement explorer shell integration, auto drive allocation, auto-open, and system tray icon

This commit is contained in:
2026-09-08 10:04:24 +02:00
parent 1c8a860184
commit 38df3d3845
6 changed files with 561 additions and 23 deletions
Generated
+248 -11
View File
@@ -104,7 +104,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -115,7 +115,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -187,6 +187,12 @@ dependencies = [
"hex-conservative",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.1"
@@ -202,6 +208,12 @@ dependencies = [
"digest",
]
[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -300,18 +312,82 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "cocoa"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
dependencies = [
"bitflags 1.3.2",
"block",
"cocoa-foundation",
"core-foundation",
"core-graphics",
"foreign-types",
"libc",
"objc",
]
[[package]]
name = "cocoa-foundation"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
dependencies = [
"bitflags 1.3.2",
"block",
"core-foundation",
"core-graphics-types",
"libc",
"objc",
]
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core-graphics"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-graphics-types",
"foreign-types",
"libc",
]
[[package]]
name = "core-graphics-types"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"libc",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -421,7 +497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -448,6 +524,33 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
"foreign-types-shared",
]
[[package]]
name = "foreign-types-macros"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.5",
]
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -947,6 +1050,15 @@ dependencies = [
"twox-hash",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]]
name = "matchers"
version = "0.2.0"
@@ -986,7 +1098,7 @@ checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
dependencies = [
"libc",
"wasi",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -995,7 +1107,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -1007,6 +1119,35 @@ dependencies = [
"autocfg",
]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -1025,6 +1166,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "padlock"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c10569378a1dacd9f30dbe7ae49e054d2c45dc2f8ee49899903e09c3924e8b6f"
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -1167,7 +1314,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
"bitflags 2.13.1",
]
[[package]]
@@ -1195,7 +1342,7 @@ checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196"
dependencies = [
"libc",
"rtoolbox",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -1205,7 +1352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1efe12a1469752d0e6ff5ebec0b6ef4924cc5c4c71046b0ec730040535819d"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -1214,7 +1361,7 @@ version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@@ -1255,6 +1402,7 @@ dependencies = [
"tokio",
"tracing",
"tracing-subscriber",
"tray-item",
"zeroize",
]
@@ -1362,7 +1510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -1484,7 +1632,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -1573,6 +1721,22 @@ dependencies = [
"tracing-log",
]
[[package]]
name = "tray-item"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59d4bd406170690dc30eabb3badc67a085beaf9b2c3b1923afcc9c26a2191353"
dependencies = [
"cocoa",
"core-graphics",
"libc",
"objc",
"objc-foundation",
"objc_id",
"padlock",
"windows-sys 0.52.0",
]
[[package]]
name = "twox-hash"
version = "2.1.4"
@@ -1779,6 +1943,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -1788,6 +1961,70 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "writeable"
version = "0.6.4"
+1
View File
@@ -36,6 +36,7 @@ dyn-clone = "1.0"
lz4_flex = "0.11"
bip39 = { version = "2.2", features = ["zeroize"] }
hex = "0.4"
tray-item = "0.10"
[profile.release]
opt-level = 3
+1
View File
@@ -5,3 +5,4 @@ pub mod storage;
pub mod ui;
pub mod verify;
pub mod vfs;
pub mod windows;
+53 -4
View File
@@ -41,9 +41,9 @@ enum Commands {
#[arg(short, long)]
path: PathBuf,
/// Laufwerksbuchstabe (z. B. 'S' oder 'S:')
/// Laufwerksbuchstabe (z. B. 'S' oder 'S:', optional; wählt standardmäßig automatisch das nächste freie Laufwerk)
#[arg(short, long)]
drive: String,
drive: Option<String>,
/// Optionaler TCP-Port für den lokalen WebDAV-Server (Standard: 8443)
#[arg(long)]
@@ -52,6 +52,14 @@ enum Commands {
/// Optionaler 24-Wort Notfall-Wiederherstellungsschlüssel (umgeht Passwortabfrage)
#[arg(long)]
recovery_key: Option<String>,
/// Öffnet das Netzlaufwerk nach dem Mounten nicht automatisch im Windows Explorer
#[arg(long, default_value_t = false)]
no_open: bool,
/// Deaktiviert das Windows System-Tray Icon während des Mounts
#[arg(long, default_value_t = false)]
no_tray: bool,
},
/// Trennt ein eingebundenes Netzlaufwerk manuell
@@ -115,6 +123,12 @@ enum Commands {
#[arg(long, default_value_t = true)]
full: bool,
},
/// Registriert .sanctum Containerdateien im Windows Explorer (Doppelklick & Kontextmenü, keine Adminrechte)
Register,
/// Entfernt die .sanctum Explorer-Integration aus der Benutzer-Registry
Unregister,
}
fn parse_drive_letter(s: &str) -> Result<char> {
@@ -465,8 +479,21 @@ async fn run() -> Result<()> {
drive,
port,
recovery_key,
no_open,
no_tray,
} => {
let drive_char = parse_drive_letter(&drive)?;
let drive_char = match drive {
Some(ref d) => parse_drive_letter(d)?,
None => {
let auto_drive = sanctum::windows::find_next_available_drive()?;
println!(
" {} Kein Laufwerksbuchstabe angegeben. Wähle automatisch freien Buchstaben {}:",
ui::cyan(""),
auto_drive
);
auto_drive
}
};
let auth = if let Some(key) = recovery_key {
ContainerAuth::RecoveryKey(key)
} else {
@@ -479,7 +506,9 @@ async fn run() -> Result<()> {
ContainerAuth::Password(password)
};
mount_container(&path, drive_char, port, auth).await?;
let open_explorer = !no_open;
let enable_tray = !no_tray;
mount_container(&path, drive_char, port, auth, open_explorer, enable_tray).await?;
}
Commands::Unmount { drive } => {
let drive_char = parse_drive_letter(&drive)?;
@@ -509,6 +538,26 @@ async fn run() -> Result<()> {
Commands::Verify { path, full } => {
handle_verify(&path, full)?;
}
Commands::Register => {
println!("┌─────────────────────────────────────────────────────────────┐");
println!("│ Sanctum — Windows Explorer Integration einrichten │");
println!("└─────────────────────────────────────────────────────────────┘");
println!();
sanctum::windows::register_explorer_integration()?;
println!(" {} .sanctum-Dateien wurden erfolgreich im Windows Explorer verknüpft!", ui::green(""));
println!(" • Doppelklick: Öffnet und bindet den Container direkt als Laufwerk ein");
println!(" • Rechtsklick: Bietet Optionen für Integritätsprüfung (FSCK) und Header-Backup");
println!();
}
Commands::Unregister => {
println!("┌─────────────────────────────────────────────────────────────┐");
println!("│ Sanctum — Windows Explorer Integration entfernen │");
println!("└─────────────────────────────────────────────────────────────┘");
println!();
sanctum::windows::unregister_explorer_integration()?;
println!(" {} .sanctum Dateiverknüpfungen wurden aus der Registry entfernt.", ui::green(""));
println!();
}
}
Ok(())
+57 -8
View File
@@ -81,6 +81,8 @@ pub async fn mount_container(
drive_letter: char,
requested_port: Option<u16>,
auth: ContainerAuth,
open_explorer: bool,
enable_tray: bool,
) -> Result<()> {
let drive_str = format_drive(drive_letter);
@@ -207,6 +209,45 @@ pub async fn mount_container(
return Err(e);
}
// Optional automatisch im Windows Explorer öffnen
if open_explorer {
let _ = crate::windows::open_in_explorer(drive_letter);
}
// System-Tray Initialisierung
let (tray_shutdown_tx, mut tray_shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
#[cfg(windows)]
let _tray = if enable_tray {
let icon_source = crate::windows::get_default_system_icon()
.unwrap_or(tray_item::IconSource::Resource(""));
let title = format!("Sanctum ({drive_str})");
match tray_item::TrayItem::new(&title, icon_source) {
Ok(mut tray) => {
let container_name = container_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let _ = tray.add_label(&format!("Sanctum: {drive_str} ({container_name})"));
let dl = drive_letter;
let _ = tray.add_menu_item("Im Explorer öffnen", move || {
let _ = crate::windows::open_in_explorer(dl);
});
let s_tx = tray_shutdown_tx.clone();
let _ = tray.add_menu_item("Trennen & Beenden", move || {
let _ = s_tx.blocking_send(());
});
Some(tray)
}
Err(e) => {
debug!("System-Tray Icon konnte nicht erstellt werden: {e}");
None
}
}
} else {
None
};
println!();
println!("┌─────────────────────────────────────────────────────────────┐");
println!("│ ✔ Sanctum Container erfolgreich gemountet │");
@@ -215,17 +256,25 @@ pub async fn mount_container(
println!(" • Container: {}", container_path.display());
println!(" • Netzlaufwerk: {} (im Windows Explorer bereit)", ui::cyan(&drive_str));
println!(" • WebDAV-URL: http://127.0.0.1:{}/", bound_port);
if enable_tray {
println!(" • System-Tray: Icon aktiv (Rechtsklick für Explorer/Trennen)");
}
println!();
println!(" [{}] Drücke [Ctrl+C] zum sicheren Trennen und Schließen.", ui::yellow("Tipp"));
println!(" [{}] Drücke [Ctrl+C] oder nutze das Tray-Icon zum Beenden.", ui::yellow("Tipp"));
println!();
// Warten auf Strg+C
tokio::signal::ctrl_c()
.await
.context("Fehler beim Registrieren des Ctrl+C Signalhandlers")?;
println!();
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]"));
// Warten auf Strg+C ODER Signal aus dem System-Tray
tokio::select! {
res = tokio::signal::ctrl_c() => {
let _ = res;
println!();
println!(" {} Beendigungssignal (Ctrl+C) empfangen.", ui::yellow("[!]"));
}
_ = tray_shutdown_rx.recv() => {
println!();
println!(" {} Beendigungssignal aus System-Tray empfangen.", ui::yellow("[!]"));
}
}
print!(" {} Trenne Windows-Netzlaufwerk {} ... ", ui::dim("[-]"), drive_str);
let _ = std::io::Write::flush(&mut std::io::stdout());
+201
View File
@@ -0,0 +1,201 @@
use std::process::Command;
use anyhow::{bail, Context, Result};
/// Ermittelt den nächsten verfügbaren Windows-Laufwerksbuchstaben (von 'Z' rückwärts bis 'D').
pub fn find_next_available_drive() -> Result<char> {
#[cfg(windows)]
{
extern "system" {
fn GetLogicalDrives() -> u32;
}
let mask = unsafe { GetLogicalDrives() };
if mask == 0 {
bail!("Fehler beim Abfragen der logischen Laufwerke via Win32 API");
}
// Suche von 'Z' abwärts bis 'D' (A, B für Diskettenlaufwerke, C für System reservieren)
for ch in ('D'..='Z').rev() {
let bit_index = (ch as u8) - b'A';
if (mask & (1 << bit_index)) == 0 {
return Ok(ch);
}
}
bail!("Kein freier Windows-Laufwerksbuchstabe (D: bis Z:) verfügbar!");
}
#[cfg(not(windows))]
{
Ok('S')
}
}
/// Öffnet das eingebundene Netzlaufwerk direkt im Windows Explorer.
pub fn open_in_explorer(drive_char: char) -> Result<()> {
let drive_path = format!("{}:\\", drive_char.to_ascii_uppercase());
Command::new("explorer.exe")
.arg(&drive_path)
.spawn()
.with_context(|| format!("Konnte Windows Explorer für '{}' nicht öffnen", drive_path))?;
Ok(())
}
/// Benachrichtigt die Windows-Shell (Explorer) über geänderte Dateiverknüpfungen (SHCNE_ASSOCCHANGED).
pub fn notify_shell_associations_changed() {
#[cfg(windows)]
{
extern "system" {
fn SHChangeNotify(
w_event_id: i32,
u_flags: u32,
dw_item1: *const std::ffi::c_void,
dw_item2: *const std::ffi::c_void,
);
}
const SHCNE_ASSOCCHANGED: i32 = 0x0800_0000;
const SHCNF_IDLIST: u32 = 0x0000;
unsafe {
SHChangeNotify(
SHCNE_ASSOCCHANGED,
SHCNF_IDLIST,
std::ptr::null(),
std::ptr::null(),
);
}
}
}
/// Registriert `.sanctum`-Containerdateien im Windows Explorer für den aktuellen Benutzer (HKCU, 100% Userland, keine Adminrechte).
pub fn register_explorer_integration() -> Result<()> {
let current_exe = std::env::current_exe()
.context("Konnte den Pfad zur aktuellen sanctum.exe nicht ermitteln")?;
let exe_str = current_exe.display().to_string();
let reg_commands = [
// 1. .sanctum Erweiterung mit ProgID verknüpfen
(
r"HKCU\Software\Classes\.sanctum",
"",
"Sanctum.Container",
),
// 2. ProgID Metadaten & Beschreibung
(
r"HKCU\Software\Classes\Sanctum.Container",
"",
"Sanctum Verschlüsselter Container",
),
// 3. Icon
(
r"HKCU\Software\Classes\Sanctum.Container\DefaultIcon",
"",
&format!("\"{exe_str}\",0"),
),
// 4. Standard-Doppelklick-Aktion: Mount
(
r"HKCU\Software\Classes\Sanctum.Container\shell\open",
"",
"Als Laufwerk einbinden",
),
(
r"HKCU\Software\Classes\Sanctum.Container\shell\open\command",
"",
&format!("\"{exe_str}\" mount --path \"%1\""),
),
// 5. Kontextmenü-Aktion: Integritätsprüfung
(
r"HKCU\Software\Classes\Sanctum.Container\shell\verify",
"",
"Integrität prüfen (FSCK)",
),
(
r"HKCU\Software\Classes\Sanctum.Container\shell\verify\command",
"",
&format!("\"{exe_str}\" verify --path \"%1\""),
),
// 6. Kontextmenü-Aktion: Header sichern
(
r"HKCU\Software\Classes\Sanctum.Container\shell\backup_header",
"",
"Header sichern",
),
(
r"HKCU\Software\Classes\Sanctum.Container\shell\backup_header\command",
"",
&format!("\"{exe_str}\" backup-header --path \"%1\""),
),
];
for (key, val_name, val_data) in reg_commands {
let mut cmd = Command::new("reg");
cmd.arg("add").arg(key);
if val_name.is_empty() {
cmd.arg("/ve");
} else {
cmd.arg("/v").arg(val_name);
}
cmd.arg("/d").arg(val_data).arg("/f");
let output = cmd.output().with_context(|| format!("Fehler beim Ausführen von 'reg add {key}'"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
bail!("Registry-Fehler beim Anlegen von {key}: {stderr}");
}
}
notify_shell_associations_changed();
Ok(())
}
/// Entfernt die Windows-Explorer-Verknüpfungen aus der Benutzer-Registry (HKCU).
pub fn unregister_explorer_integration() -> Result<()> {
let keys_to_delete = [
r"HKCU\Software\Classes\.sanctum",
r"HKCU\Software\Classes\Sanctum.Container",
];
for key in keys_to_delete {
let _ = Command::new("reg")
.args(["delete", key, "/f"])
.output();
}
notify_shell_associations_changed();
Ok(())
}
/// Lädt das Windows-Sicherheitsschild-Icon (IDI_SHIELD) oder Anwendungs-Icon für den System-Tray.
#[cfg(windows)]
pub fn get_default_system_icon() -> Option<tray_item::IconSource> {
extern "system" {
fn LoadIconW(instance: isize, icon_name: *const u16) -> isize;
}
// IDI_SHIELD = 32518, IDI_APPLICATION = 32512
let icon = unsafe { LoadIconW(0, 32518 as *const u16) };
if icon != 0 {
Some(tray_item::IconSource::RawIcon(icon))
} else {
let icon_app = unsafe { LoadIconW(0, 32512 as *const u16) };
if icon_app != 0 {
Some(tray_item::IconSource::RawIcon(icon_app))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_next_available_drive() {
let drive = find_next_available_drive().expect("Find next drive");
assert!(drive.is_ascii_alphabetic());
assert!(drive >= 'D' && drive <= 'Z');
}
}