ravnpad 1.3.14

A simple UTF-8 notepad
use std::io;
use std::path::Path;

use winreg::RegKey;
use winreg::enums::*;

const PROGID: &str = "RavnPad.txt";
const APP_NAME: &str = "RavnPad";

const DEFAULT_EXTS: &[&str] = &[".txt", ".text", ".log", ".md"];
const OPEN_WITH_EXTS: &[&str] = &[
    ".txt", ".text", ".log", ".md", ".rtf", ".csv", ".ini", ".cfg", ".conf", ".nfo", ".asc",
];

pub fn register() {
    let Ok(exe) = std::env::current_exe() else {
        return;
    };
    let exe = dunce_path(&exe);
    let _ = register_with(&exe);
}

fn dunce_path(path: &Path) -> std::path::PathBuf {
    path.canonicalize()
        .unwrap_or_else(|_| path.to_path_buf())
        .to_string_lossy()
        .strip_prefix(r"\\?\")
        .map(str::to_string)
        .map(std::path::PathBuf::from)
        .unwrap_or_else(|| path.to_path_buf())
}

fn register_with(exe: &Path) -> io::Result<()> {
    let exe_str = exe.to_string_lossy();
    let command = format!("\"{exe_str}\" \"%1\"");
    let icon = format!("{exe_str},0");
    let exe_name = exe
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("ravnpad.exe");

    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    let (classes, _) = hkcu.create_subkey("Software\\Classes")?;

    let (prog, _) = classes.create_subkey(PROGID)?;
    prog.set_value("", &format!("{APP_NAME}-dokument"))?;
    prog.set_value("FriendlyTypeName", &format!("{APP_NAME}-dokument"))?;
    let (default_icon, _) = prog.create_subkey("DefaultIcon")?;
    default_icon.set_value("", &icon)?;
    let (open_verb, _) = prog.create_subkey(r"shell\open")?;
    open_verb.set_value("", &"Åpne")?;
    open_verb.set_value("FriendlyAppName", &APP_NAME)?;
    let (open_cmd, _) = prog.create_subkey(r"shell\open\command")?;
    open_cmd.set_value("", &command)?;

    for ext in DEFAULT_EXTS {
        let (ext_key, _) = classes.create_subkey(ext)?;
        ext_key.set_value("", &PROGID)?;
        let (progids, _) = ext_key.create_subkey("OpenWithProgids")?;
        progids.set_value(PROGID, &"")?;
        let _ = clear_user_choice(ext);
    }

    for ext in OPEN_WITH_EXTS {
        let (ext_key, _) = classes.create_subkey(ext)?;
        let (progids, _) = ext_key.create_subkey("OpenWithProgids")?;
        progids.set_value(PROGID, &"")?;
        if let Ok((file_exts, _)) = hkcu.create_subkey(format!(
            r"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{ext}\OpenWithProgids"
        )) {
            let _ = file_exts.set_value(PROGID, &"");
        }
    }

    let (app, _) = classes.create_subkey(format!(r"Applications\{exe_name}"))?;
    app.set_value("FriendlyAppName", &APP_NAME)?;
    let (app_open, _) = app.create_subkey(r"shell\open\command")?;
    app_open.set_value("", &command)?;
    let (types, _) = app.create_subkey("SupportedTypes")?;
    for ext in OPEN_WITH_EXTS {
        types.set_value(*ext, &"")?;
    }

    let (app_path, _) = hkcu.create_subkey(format!(
        r"Software\Microsoft\Windows\CurrentVersion\App Paths\{exe_name}"
    ))?;
    app_path.set_value("", &exe_str.as_ref())?;
    if let Some(parent) = exe.parent() {
        app_path.set_value("Path", &parent.to_string_lossy().as_ref())?;
    }

    let (caps, _) = hkcu.create_subkey(r"Software\RavnPad\Capabilities")?;
    caps.set_value("ApplicationName", &APP_NAME)?;
    caps.set_value("ApplicationDescription", &"Enkel les/skriv-notisblokk")?;
    caps.set_value("ApplicationIcon", &icon)?;
    let (file_assoc, _) = caps.create_subkey("FileAssociations")?;
    for ext in DEFAULT_EXTS {
        file_assoc.set_value(*ext, &PROGID)?;
    }

    let (registered, _) = hkcu.create_subkey("Software\\RegisteredApplications")?;
    registered.set_value(APP_NAME, &r"Software\RavnPad\Capabilities")?;

    notify_assoc_changed();
    Ok(())
}

fn clear_user_choice(ext: &str) -> io::Result<()> {
    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    let key = hkcu.open_subkey_with_flags(
        format!(r"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{ext}"),
        KEY_READ | KEY_WRITE,
    )?;
    let _ = key.delete_subkey_all("UserChoice");
    let _ = key.delete_subkey_all("UserChoiceNew");
    Ok(())
}

fn notify_assoc_changed() {
    const SHCNE_ASSOCCHANGED: i32 = 0x0800_0000;
    const SHCNF_IDLIST: u32 = 0x0000;
    unsafe {
        SHChangeNotify(
            SHCNE_ASSOCCHANGED,
            SHCNF_IDLIST,
            std::ptr::null(),
            std::ptr::null(),
        );
    }
}

#[link(name = "shell32")]
unsafe extern "system" {
    fn SHChangeNotify(
        event: i32,
        flags: u32,
        item1: *const std::ffi::c_void,
        item2: *const std::ffi::c_void,
    );
}