tauri-plugin-deeprespond-im 1.0.0-alpha

深应智能 IM 的 Tauri 插件:本地存储、安全存储、窗口间的锁与消息、系统事件、通知、角标、文件
//! Windows 的系统事件:插件自己的隐藏顶层窗口(专用线程、自己的消息循环)接收
//! WM_POWERBROADCAST(休眠、唤醒)、WM_WTSSESSION_CHANGE(锁屏、解锁)和 WM_QUERYENDSESSION、WM_ENDSESSION(关机、注销)。
//!
//! 不挂在引擎窗口上:引擎窗口会重建,而且它属于主线程——WM_ENDSESSION 返回后进程随时可能被结束,
//! 必须在处理它时等引擎收尾完,而引擎的收尾(engine_exit_done)要经主线程,在主线程里等会互相卡住。
//! 隐藏的顶层窗口照样收到这些广播消息(只接收消息的 HWND_MESSAGE 窗口收不到)。

use std::ffi::c_void;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::sync::{Arc, OnceLock};
use std::time::Duration;

use ::windows::core::{w, HSTRING, PWSTR};
use ::windows::Win32::Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS, HINSTANCE, HWND, LPARAM, LRESULT, WPARAM};
use ::windows::Win32::NetworkManagement::IpHelper::{
    GetAdaptersAddresses, GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST, IP_ADAPTER_ADDRESSES_LH,
};
use ::windows::Win32::NetworkManagement::Ndis::IfOperStatusUp;
use ::windows::Win32::Networking::WinSock::{AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR_IN, SOCKADDR_IN6};
use ::windows::Win32::System::LibraryLoader::GetModuleHandleW;
use ::windows::Win32::System::Registry::{RegGetValueW, HKEY_LOCAL_MACHINE, RRF_RT_REG_DWORD, RRF_RT_REG_SZ};
use ::windows::Win32::System::RemoteDesktop::{WTSRegisterSessionNotification, NOTIFY_FOR_THIS_SESSION};
use ::windows::Win32::System::Shutdown::{ShutdownBlockReasonCreate, ShutdownBlockReasonDestroy};
use ::windows::Win32::System::SystemInformation::{GetComputerNameExW, ComputerNameDnsHostname};
use ::windows::Win32::UI::WindowsAndMessaging::{
    CreateWindowExW, DefWindowProcW, DispatchMessageW, GetMessageW, RegisterClassExW, TranslateMessage, MSG, PBT_APMRESUMEAUTOMATIC,
    PBT_APMRESUMESUSPEND, PBT_APMSUSPEND, WINDOW_EX_STYLE, WM_ENDSESSION, WM_POWERBROADCAST, WM_QUERYENDSESSION, WM_WTSSESSION_CHANGE,
    WNDCLASSEXW, WS_OVERLAPPED, WTS_SESSION_LOCK, WTS_SESSION_UNLOCK,
};
use tauri::{AppHandle, Runtime};

use crate::Shared;

/// WM_ENDSESSION 中最多等这么久:引擎收尾最多 2 秒,进程在这之前退出时这个等待随之结束。
const END_SESSION_WAIT: Duration = Duration::from_millis(2500);

enum PowerEvent {
    Suspend,
    Resume,
    Lock(bool),
    EndSession,
}

type Handler = Box<dyn Fn(PowerEvent) + Send + Sync>;
/// 窗口过程不能带泛型,经这个静态的处理函数交给插件。
static HANDLER: OnceLock<Handler> = OnceLock::new();

pub(super) fn start<R: Runtime>(app: &AppHandle<R>, shared: &Arc<Shared>) {
    let (handle, state) = (app.clone(), shared.clone());
    let handler: Handler = Box::new(move |event| match event {
        PowerEvent::Suspend => super::on_sleep(&state, "WM_POWERBROADCAST"),
        PowerEvent::Resume => super::on_resume(&state, None, "WM_POWERBROADCAST"),
        PowerEvent::Lock(locked) => super::on_lock(&handle, &state, locked, "WTS"),
        PowerEvent::EndSession => super::on_shutdown(&handle, &state, "WM_ENDSESSION"),
    });
    if HANDLER.set(handler).is_err() {
        return;
    }
    let state = shared.clone();
    let spawned = std::thread::Builder::new().name("drim-system-events".into()).spawn(move || {
        // SAFETY:窗口在本线程创建、本线程处理消息,直到进程退出
        if let Err(err) = unsafe { run_window() } {
            state.logs.plugin("warn", &format!("系统事件窗口创建失败,休眠、锁屏、关机只靠通用办法:{err}"));
        }
    });
    if let Err(err) = spawned {
        shared.logs.plugin("warn", &format!("系统事件线程创建失败:{err}"));
    }
}

unsafe fn run_window() -> ::windows::core::Result<()> {
    unsafe {
        let module = GetModuleHandleW(None)?;
        let instance: HINSTANCE = module.into();
        let class = w!("DeeprespondImSystemEvents");
        let wc = WNDCLASSEXW {
            cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
            lpfnWndProc: Some(window_proc),
            hInstance: instance,
            lpszClassName: class,
            ..Default::default()
        };
        if RegisterClassExW(&wc) == 0 {
            return Err(::windows::core::Error::from_thread());
        }
        // 不显示的顶层窗口:没有任务栏按钮,但收得到电源、会话和关机的广播
        let hwnd = CreateWindowExW(WINDOW_EX_STYLE::default(), class, w!("deeprespond-im"), WS_OVERLAPPED, 0, 0, 0, 0, None, None, Some(instance), None)?;
        if let Err(err) = WTSRegisterSessionNotification(hwnd, NOTIFY_FOR_THIS_SESSION) {
            log::warn!("WTSRegisterSessionNotification 失败,收不到锁屏通知:{err}");
        }
        let mut msg = MSG::default();
        // GetMessageW 出错时返回 -1,只在大于 0 时继续
        while GetMessageW(&mut msg, None, 0, 0).0 > 0 {
            let _ = TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }
    Ok(())
}

fn emit(event: PowerEvent) {
    if let Some(handler) = HANDLER.get() {
        handler(event);
    }
}

unsafe extern "system" fn window_proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    match msg {
        WM_POWERBROADCAST => {
            match wparam.0 as u32 {
                PBT_APMSUSPEND => emit(PowerEvent::Suspend),
                // 自动唤醒一定会收到;用户操作唤醒的另有 RESUMESUSPEND,重复的由 on_resume 去重
                PBT_APMRESUMEAUTOMATIC | PBT_APMRESUMESUSPEND => emit(PowerEvent::Resume),
                _ => {}
            }
            LRESULT(1)
        }
        WM_WTSSESSION_CHANGE => {
            match wparam.0 as u32 {
                WTS_SESSION_LOCK => emit(PowerEvent::Lock(true)),
                WTS_SESSION_UNLOCK => emit(PowerEvent::Lock(false)),
                _ => {}
            }
            LRESULT(0)
        }
        WM_QUERYENDSESSION => {
            // 不阻止关机,只说明原因:收尾期间系统的关机界面显示它
            let _ = unsafe { ShutdownBlockReasonCreate(hwnd, &HSTRING::from("正在保存数据并断开连接")) };
            LRESULT(1)
        }
        WM_ENDSESSION => {
            if wparam.0 != 0 {
                // 会话确实要结束了:开始收尾,并在这里等它完成(返回后进程随时可能被结束)
                emit(PowerEvent::EndSession);
                std::thread::sleep(END_SESSION_WAIT);
            }
            let _ = unsafe { ShutdownBlockReasonDestroy(hwnd) };
            LRESULT(0)
        }
        _ => unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) },
    }
}

/// 已连接网卡的地址(GetAdaptersAddresses,只取状态为“已连接”的网卡)。
pub(super) fn interface_addrs() -> Vec<(String, IpAddr)> {
    let mut out = Vec::new();
    let flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
    let mut size: u32 = 16 * 1024;
    let mut buf: Vec<u64>;
    let mut tries = 0;
    loop {
        // 按 8 字节对齐分配:结构中有 64 位的字段
        buf = vec![0u64; (size as usize).div_ceil(8)];
        // SAFETY:缓冲区大小由 size 给出,函数只写入其中
        let ret = unsafe { GetAdaptersAddresses(AF_UNSPEC.0 as u32, flags, None, Some(buf.as_mut_ptr() as *mut IP_ADAPTER_ADDRESSES_LH), &mut size) };
        if ret == ERROR_BUFFER_OVERFLOW.0 && tries < 3 {
            tries += 1;
            continue;
        }
        if ret != ERROR_SUCCESS.0 {
            return out;
        }
        break;
    }
    // SAFETY:GetAdaptersAddresses 成功后,链表及其中的地址都在 buf 内
    unsafe {
        let mut adapter = buf.as_ptr() as *const IP_ADAPTER_ADDRESSES_LH;
        while !adapter.is_null() {
            let a = &*adapter;
            adapter = a.Next;
            if a.OperStatus != IfOperStatusUp {
                continue;
            }
            let name = a.AdapterName.to_string().unwrap_or_default();
            let mut unicast = a.FirstUnicastAddress;
            while !unicast.is_null() {
                let u = &*unicast;
                unicast = u.Next;
                let sa = u.Address.lpSockaddr;
                if sa.is_null() {
                    continue;
                }
                let addr = if (*sa).sa_family == AF_INET {
                    let sin = &*(sa as *const SOCKADDR_IN);
                    IpAddr::V4(Ipv4Addr::from(u32::from_be(sin.sin_addr.S_un.S_addr)))
                } else if (*sa).sa_family == AF_INET6 {
                    let sin6 = &*(sa as *const SOCKADDR_IN6);
                    IpAddr::V6(Ipv6Addr::from(sin6.sin6_addr.u.Byte))
                } else {
                    continue;
                };
                out.push((name.clone(), addr));
            }
        }
    }
    out
}

/// 默认路由的变化体现在网卡状态和地址上,不另外读取。
pub(super) fn default_routes() -> Vec<String> {
    Vec::new()
}

const CURRENT_VERSION: &str = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion";

/// “Windows 10.0.22631”:注册表中的版本号(GetVersionEx 按清单返回兼容版本,不可靠)。
pub(super) fn os_version() -> String {
    let major = reg_dword(CURRENT_VERSION, "CurrentMajorVersionNumber");
    let minor = reg_dword(CURRENT_VERSION, "CurrentMinorVersionNumber");
    let build = reg_string(CURRENT_VERSION, "CurrentBuildNumber");
    match (major, minor, build) {
        (Some(major), Some(minor), Some(build)) => format!("Windows {major}.{minor}.{build}"),
        (_, _, Some(build)) => format!("Windows {build}"),
        _ => "Windows".into(),
    }
}

fn reg_dword(subkey: &str, name: &str) -> Option<u32> {
    let mut value: u32 = 0;
    let mut size = std::mem::size_of::<u32>() as u32;
    // SAFETY:value 和 size 在调用期间有效
    let ret = unsafe {
        RegGetValueW(HKEY_LOCAL_MACHINE, &HSTRING::from(subkey), &HSTRING::from(name), RRF_RT_REG_DWORD, None, Some(&mut value as *mut u32 as *mut c_void), Some(&mut size))
    };
    (ret == ERROR_SUCCESS).then_some(value)
}

fn reg_string(subkey: &str, name: &str) -> Option<String> {
    let (key, value) = (HSTRING::from(subkey), HSTRING::from(name));
    let mut size: u32 = 0;
    // SAFETY:先取长度,再按长度分配缓冲区
    unsafe {
        if RegGetValueW(HKEY_LOCAL_MACHINE, &key, &value, RRF_RT_REG_SZ, None, None, Some(&mut size)) != ERROR_SUCCESS {
            return None;
        }
        let mut buf = vec![0u16; (size as usize).div_ceil(2) + 1];
        let mut size = (buf.len() * 2) as u32;
        if RegGetValueW(HKEY_LOCAL_MACHINE, &key, &value, RRF_RT_REG_SZ, None, Some(buf.as_mut_ptr() as *mut c_void), Some(&mut size)) != ERROR_SUCCESS {
            return None;
        }
        let len = buf.iter().position(|c| *c == 0).unwrap_or(buf.len());
        Some(String::from_utf16_lossy(&buf[..len]))
    }
}

/// 计算机名(保留大小写的 DNS 主机名,与“设置”中显示的相同)。
pub(super) fn computer_name() -> String {
    let mut size: u32 = 0;
    // SAFETY:先取长度(这次调用按约定失败),再按长度分配缓冲区
    unsafe {
        let _ = GetComputerNameExW(ComputerNameDnsHostname, None, &mut size);
        if size == 0 {
            return String::new();
        }
        let mut buf = vec![0u16; size as usize + 1];
        let mut len = buf.len() as u32;
        if GetComputerNameExW(ComputerNameDnsHostname, Some(PWSTR(buf.as_mut_ptr())), &mut len).is_err() {
            return String::new();
        }
        String::from_utf16_lossy(&buf[..len as usize])
    }
}