win-rar 0.1.0

A Windows archive manager supporting ZIP, 7z, RAR, TAR with encryption, shell integration, and drag-and-drop
// Copyright (c) 北京锋通科技有限公司 — 郭玉峰、吴琼
// SPDX-License-Identifier: MIT

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);

    // 1. 注册应用程序到 OpenWithList
    register_app_open_with(&exe)?;

    // 2. 注册压缩文件右键菜单
    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")?;

    // 3. 注册目录/文件夹右键菜单(压缩)
    register_directory_context_menu(&exe)?;

    // 4. 注册文件关联(双击打开)
    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")?;

    // 5. 通知 shell 刷新
    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(())
}

/// 注册到 OpenWithList(右键"打开方式")
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(())
}

/// 通知 shell 刷新图标和关联
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),
        );
    }
    // 找到第一个 null 终止符
    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);
    }
}

// 声明 RegCloseKey
extern "system" {
    fn RegCloseKey(hkey: HKEY) -> i32;
}