use windows::Win32::System::Registry::*;
use windows::core::HSTRING;
pub fn register_shell_integration(exe_path: &str) -> Result<(), String> {
let exe = HSTRING::from(exe_path);
register_app_open_with(&exe)?;
register_context_menu(&exe, ".zip", "ZIP")?;
register_context_menu(&exe, ".rar", "RAR")?;
register_context_menu(&exe, ".7z", "7Z")?;
register_context_menu(&exe, ".tar", "TAR")?;
register_context_menu(&exe, ".tar.gz", "TAR.GZ")?;
register_context_menu(&exe, ".tgz", "TGZ")?;
register_context_menu(&exe, ".tar.bz2", "TAR.BZ2")?;
register_context_menu(&exe, ".tbz2", "TBZ2")?;
register_context_menu(&exe, ".tar.xz", "TAR.XZ")?;
register_context_menu(&exe, ".txz", "TXZ")?;
register_directory_context_menu(&exe)?;
register_file_association(&exe, ".zip", "WinRAR.Zip")?;
register_file_association(&exe, ".rar", "WinRAR.Rar")?;
register_file_association(&exe, ".7z", "WinRAR.7z")?;
register_file_association(&exe, ".tar", "WinRAR.Tar")?;
register_file_association(&exe, ".tar.gz", "WinRAR.TarGz")?;
register_file_association(&exe, ".tar.bz2", "WinRAR.TarBz2")?;
register_file_association(&exe, ".tar.xz", "WinRAR.TarXz")?;
notify_shell_change();
Ok(())
}
pub fn unregister_shell_integration() -> Result<(), String> {
let extensions = [".zip", ".rar", ".7z", ".tar", ".tar.gz", ".tar.bz2", ".tar.xz"];
let progids = ["WinRAR.Zip", "WinRAR.Rar", "WinRAR.7z", "WinRAR.Tar", "WinRAR.TarGz", "WinRAR.TarBz2", "WinRAR.TarXz"];
for ext in &extensions {
delete_key_tree(HKEY_CLASSES_ROOT, &ext.to_lowercase());
let menu_key = format!("{}\\shell\\WinRAR", ext.to_lowercase());
delete_key_tree(HKEY_CLASSES_ROOT, &menu_key);
}
for progid in &progids {
delete_key_tree(HKEY_CLASSES_ROOT, progid);
}
delete_key_tree(HKEY_CLASSES_ROOT, "Directory\\shell\\WinRAR.Compress");
delete_key_tree(HKEY_CLASSES_ROOT, "Directory\\Background\\shell\\WinRAR.Compress");
delete_key_tree(HKEY_CLASSES_ROOT, "Applications\\win-rar.exe");
notify_shell_change();
Ok(())
}
pub fn is_shell_integrated() -> bool {
let exe_path = match std::env::current_exe() {
Ok(p) => p.to_string_lossy().to_string(),
Err(_) => return false,
};
let key_name = "WinRAR.Zip\\shell\\open\\command";
let key = open_key_read(HKEY_CLASSES_ROOT, key_name);
if key.is_invalid() {
return false;
}
let value = query_value(&key, "");
let _ = unsafe { RegCloseKey(key) };
value.contains(&exe_path)
}
fn register_context_menu(exe: &HSTRING, ext: &str, _label: &str) -> Result<(), String> {
let key_path = format!("{}\\shell\\WinRAR", ext.to_lowercase());
let key = create_key(HKEY_CLASSES_ROOT, &key_path);
set_value(&key, "", "用 WinRAR 打开");
set_value(&key, "Icon", &exe.to_string_lossy());
let _ = unsafe { RegCloseKey(key) };
let cmd_path = format!("{}\\command", key_path);
let cmd_key = create_key(HKEY_CLASSES_ROOT, &cmd_path);
set_value(&cmd_key, "", &format!("\"{}\" \"%1\"", exe.to_string_lossy()));
let _ = unsafe { RegCloseKey(cmd_key) };
let extract_path = format!("{}\\shell\\WinRAR.ExtractHere", ext.to_lowercase());
let extract_key = create_key(HKEY_CLASSES_ROOT, &extract_path);
set_value(&extract_key, "", "解压到当前目录");
set_value(&extract_key, "Icon", &exe.to_string_lossy());
let _ = unsafe { RegCloseKey(extract_key) };
let extract_cmd_path = format!("{}\\command", extract_path);
let extract_cmd_key = create_key(HKEY_CLASSES_ROOT, &extract_cmd_path);
set_value(&extract_cmd_key, "", &format!("\"{}\" --extract-here \"%1\"", exe.to_string_lossy()));
let _ = unsafe { RegCloseKey(extract_cmd_key) };
Ok(())
}
fn register_directory_context_menu(exe: &HSTRING) -> Result<(), String> {
let key_path = "Directory\\shell\\WinRAR.Compress";
let key = create_key(HKEY_CLASSES_ROOT, key_path);
set_value(&key, "", "添加到压缩包...");
set_value(&key, "Icon", &exe.to_string_lossy());
let _ = unsafe { RegCloseKey(key) };
let cmd_path = format!("{}\\command", key_path);
let cmd_key = create_key(HKEY_CLASSES_ROOT, &cmd_path);
set_value(&cmd_key, "", &format!("\"{}\" --compress \"%1\"", exe.to_string_lossy()));
let _ = unsafe { RegCloseKey(cmd_key) };
let bg_path = "Directory\\Background\\shell\\WinRAR.Compress";
let bg_key = create_key(HKEY_CLASSES_ROOT, bg_path);
set_value(&bg_key, "", "添加到压缩包...");
set_value(&bg_key, "Icon", &exe.to_string_lossy());
let _ = unsafe { RegCloseKey(bg_key) };
let bg_cmd_path = format!("{}\\command", bg_path);
let bg_cmd_key = create_key(HKEY_CLASSES_ROOT, &bg_cmd_path);
set_value(&bg_cmd_key, "", &format!("\"{}\" --compress \"%V\"", exe.to_string_lossy()));
let _ = unsafe { RegCloseKey(bg_cmd_key) };
Ok(())
}
fn register_file_association(exe: &HSTRING, ext: &str, progid: &str) -> Result<(), String> {
let ext_key = create_key(HKEY_CLASSES_ROOT, &ext.to_lowercase());
set_value(&ext_key, "", progid);
let _ = unsafe { RegCloseKey(ext_key) };
let prog_key = create_key(HKEY_CLASSES_ROOT, progid);
set_value(&prog_key, "", "WinRAR 压缩文件");
let _ = unsafe { RegCloseKey(prog_key) };
let icon_path = format!("{}\\DefaultIcon", progid);
let icon_key = create_key(HKEY_CLASSES_ROOT, &icon_path);
set_value(&icon_key, "", &format!("{},0", exe.to_string_lossy()));
let _ = unsafe { RegCloseKey(icon_key) };
let cmd_path = format!("{}\\shell\\open\\command", progid);
let cmd_key = create_key(HKEY_CLASSES_ROOT, &cmd_path);
set_value(&cmd_key, "", &format!("\"{}\" \"%1\"", exe.to_string_lossy()));
let _ = unsafe { RegCloseKey(cmd_key) };
let shell_path = format!("{}\\shell", progid);
let shell_key = create_key(HKEY_CLASSES_ROOT, &shell_path);
set_value(&shell_key, "", "open");
let _ = unsafe { RegCloseKey(shell_key) };
Ok(())
}
fn register_app_open_with(exe: &HSTRING) -> Result<(), String> {
let key_path = "Applications\\win-rar.exe\\shell\\open\\command";
let key = create_key(HKEY_CLASSES_ROOT, key_path);
set_value(&key, "", &format!("\"{}\" \"%1\"", exe.to_string_lossy()));
let _ = unsafe { RegCloseKey(key) };
let supported_key = "Applications\\win-rar.exe\\SupportedTypes";
let sup_key = create_key(HKEY_CLASSES_ROOT, supported_key);
for ext in &[".zip", ".rar", ".7z", ".tar", ".tar.gz", ".tar.bz2", ".tar.xz"] {
set_value(&sup_key, ext, "");
}
let _ = unsafe { RegCloseKey(sup_key) };
Ok(())
}
fn notify_shell_change() {
use windows::Win32::UI::Shell::*;
unsafe {
let _ = SHChangeNotify(
SHCNE_ASSOCCHANGED,
SHCNF_IDLIST,
None,
None,
);
}
}
fn create_key(hkey: HKEY, path: &str) -> HKEY {
let hstr = HSTRING::from(path);
let mut key = HKEY::default();
unsafe {
let _ = RegCreateKeyW(hkey, &hstr, &mut key);
}
key
}
fn open_key_read(hkey: HKEY, path: &str) -> HKEY {
let hstr = HSTRING::from(path);
let mut key = HKEY::default();
unsafe {
let _ = RegOpenKeyExW(hkey, &hstr, 0, KEY_READ, &mut key);
}
key
}
fn set_value(key: &HKEY, name: &str, value: &str) {
let name_hstr = HSTRING::from(name);
let value_hstr = HSTRING::from(value);
let value_bytes = value_hstr.as_wide();
unsafe {
let _ = RegSetValueExW(
*key,
&name_hstr,
0,
REG_SZ,
Some(value_bytes.align_to::<u8>().1),
);
}
}
fn query_value(key: &HKEY, name: &str) -> String {
let name_hstr = HSTRING::from(name);
let mut buf_size: u32 = 512;
let mut buf = vec![0u16; 256];
unsafe {
let _ = RegQueryValueExW(
*key,
&name_hstr,
None,
None,
Some(buf.as_mut_ptr() as *mut u8),
Some(&mut buf_size),
);
}
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
String::from_utf16_lossy(&buf[..len])
}
fn delete_key_tree(hkey: HKEY, path: &str) {
let hstr = HSTRING::from(path);
unsafe {
let _ = RegDeleteTreeW(hkey, &hstr);
}
}
extern "system" {
fn RegCloseKey(hkey: HKEY) -> i32;
}