aura-gui 0.2.2

UI nativa acelerada por GPU para escritorio, Android e iOS, en un solo crate: estilo tipo Tailwind, componentes, sombras, animaciones y agentes de IA. Reposo a cero. Licencia AGPL-3.0 o comercial.
//! Windows: llamadas directas a la API del sistema, sin PowerShell ni `cmd` de por medio (no hay que escapar nada).

use std::ffi::c_void;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::process::CommandExt;
use std::path::Path;
use std::process::Command;
use std::ptr::{null, null_mut};

pub use super::wincred::{secret_get, secret_remove, secret_set};

#[link(name = "bcrypt")]
unsafe extern "system" {
    fn BCryptGenRandom(algorithm: *mut c_void, buffer: *mut u8, len: u32, flags: u32) -> i32;
}

#[link(name = "shell32")]
unsafe extern "system" {
    fn ShellExecuteW(
        window: *mut c_void,
        verb: *const u16,
        file: *const u16,
        args: *const u16,
        dir: *const u16,
        show: i32,
    ) -> isize;
    fn SHFileOperationW(operation: *mut FileOperation) -> i32;
}

/// `SHFILEOPSTRUCTW`: el SDK la empaqueta a 1 byte en 32 bits.
#[repr(C)]
#[cfg_attr(not(target_pointer_width = "64"), repr(packed(1)))]
struct FileOperation {
    window: *mut c_void,
    function: u32,
    from: *const u16,
    to: *const u16,
    flags: u16,
    aborted: i32,
    mappings: *mut c_void,
    title: *const u16,
}

const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x2;
const SW_SHOWNORMAL: i32 = 1;
const FO_DELETE: u32 = 3;
/// Papelera (`ALLOWUNDO`) sin diálogos de confirmación, progreso ni errores.
const FOF_TRASH_QUIETLY: u16 = 0x40 | 0x10 | 0x4 | 0x400;
const BELOW_NORMAL_PRIORITY_CLASS: u32 = 0x4000;
const CREATE_NO_WINDOW: u32 = 0x0800_0000;

pub(super) fn wide(text: impl AsRef<std::ffi::OsStr>) -> Vec<u16> {
    text.as_ref().encode_wide().chain(std::iter::once(0)).collect()
}

pub fn fill_random(buffer: &mut [u8]) -> Result<(), String> {
    let status = unsafe {
        BCryptGenRandom(null_mut(), buffer.as_mut_ptr(), buffer.len() as u32, BCRYPT_USE_SYSTEM_PREFERRED_RNG)
    };
    if status == 0 { Ok(()) } else { Err(format!("BCryptGenRandom falló (NTSTATUS {status:#x})")) }
}

/// `ShellExecuteW` recibe la dirección como un único argumento: no pasa por `cmd` ni PowerShell, así que `&`, `%` o
/// la longitud no la parten.
pub fn open_url(url: &str, _background: bool) -> Result<(), String> {
    let (verb, file) = (wide("open"), wide(url));
    let result = unsafe { ShellExecuteW(null_mut(), verb.as_ptr(), file.as_ptr(), null(), null(), SW_SHOWNORMAL) };
    // 32 o menos es un código de error (el resto es un HINSTANCE heredado).
    if result > 32 { Ok(()) } else { Err(format!("ShellExecuteW devolvió {result}")) }
}

/// `explorer /select,"ruta"`. Explorer sale con 1 aunque funcione, así que no se espera su resultado.
pub fn reveal(path: &Path) -> Result<(), String> {
    let mut command = Command::new("explorer");
    command.raw_arg(format!("/select,\"{}\"", path.display()));
    command.spawn().map(drop).map_err(|error| format!("no se pudo abrir el Explorador: {error}"))
}

/// Papelera de reciclaje con la API del shell. `from` termina en doble NUL, como pide la función.
pub fn trash(path: &Path) -> Result<(), String> {
    let mut from = wide(path);
    from.push(0);
    let mut operation = FileOperation {
        window: null_mut(),
        function: FO_DELETE,
        from: from.as_ptr(),
        to: null(),
        flags: FOF_TRASH_QUIETLY,
        aborted: 0,
        mappings: null_mut(),
        title: null(),
    };
    match unsafe { SHFileOperationW(&mut operation) } {
        0 if { operation.aborted == 0 } => Ok(()),
        0 => Err("operación cancelada".into()),
        code => Err(format!("SHFileOperationW devolvió {code:#x}")),
    }
}

pub fn low_priority_command(program: &str) -> Command {
    let mut command = Command::new(program);
    command.creation_flags(BELOW_NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW);
    command
}