//! P5-41 / P5-35 — Register an AppUserModelID (AUMID) so the WinRT rich toasts in //! `toast.rs` actually work on Windows. //! //! `ToastNotificationManager::CreateToastNotifierWithId` (and the ambient //! `CreateToastNotifier`) require the process to run under an AUMID that maps to a //! Start-Menu shortcut carrying `System.AppUserModel.ID`. An unpackaged Win32 app //! (our NSIS build) has none by default, so `Show()` errored and the rich toast //! (reply box + click-to-open-room) silently fell back to the plain plugin toast. //! //! Two pieces: (1) `set_process_aumid` advertises the AUMID for this process — //! called at the very top of `run()` so it precedes the main window's taskbar //! button (which otherwise groups under a mismatched implicit AUMID and shows a //! second taskbar icon); (2) `ensure_app_user_model_id` installs/refreshes a //! Start-Menu `.lnk` (same name → overwrites the installer's, no duplicate) //! carrying the AUMID, reusing the `IShellLinkW` + `IPropertyStore` + `PROPVARIANT` //! pattern proven in `jumplist.rs`. Best-effort: any failure is logged and //! swallowed (the toast just keeps falling back, as before — never crash boot). //! //! Non-Windows: a no-op. use tauri::AppHandle; /// The AUMID this process advertises and that the Start-Menu shortcut carries. /// `toast.rs` binds the toast notifier to it via `CreateToastNotifierWithId`. /// /// This MUST equal the AUMID Tauri's NSIS installer stamps on its Desktop / /// Start-Menu shortcuts (`System.AppUserModel.ID` = the bundle `identifier`), /// otherwise the running window and a pinned shortcut group under different /// identities and Windows shows two taskbar icons. pub const APP_USER_MODEL_ID: &str = "org.lotusguild.lotus-chat"; /// Advertise this process's AUMID. MUST run before the main window is built so /// the window's taskbar button groups under the same AUMID as the pinned /// installer shortcut. Plain shell32 export — no COM init required, so this is /// deliberately separate from the `.lnk` install (which needs its own STA COM). #[cfg(target_os = "windows")] pub fn set_process_aumid() { use windows::core::HSTRING; use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID; if let Err(e) = unsafe { SetCurrentProcessExplicitAppUserModelID(&HSTRING::from(APP_USER_MODEL_ID)) } { eprintln!("aumid: SetCurrentProcessExplicitAppUserModelID failed: {e}"); } } #[cfg(not(target_os = "windows"))] pub fn set_process_aumid() {} #[cfg(target_os = "windows")] pub fn ensure_app_user_model_id(_app: &AppHandle) { use std::os::windows::ffi::OsStrExt; use windows::core::{Interface, PCWSTR}; // PKEY_AppUserModel_ID lives in EnhancedStorage (same module as jumplist's // PKEY_Title), NOT PropertiesSystem — use the ready-made constant rather than // hand-rolling the PROPERTYKEY. use windows::Win32::Storage::EnhancedStorage::PKEY_AppUserModel_ID; use windows::Win32::System::Com::{ CoCreateInstance, CoInitializeEx, CoUninitialize, IPersistFile, StructuredStorage::PROPVARIANT, CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED, }; use windows::Win32::UI::Shell::{ PropertiesSystem::IPropertyStore, IShellLinkW, ShellLink, }; // The process AUMID is advertised earlier (see `set_process_aumid`, called at // the top of `run()` before the window is built). Here we only install/refresh // the Start-Menu shortcut carrying the AUMID so Action Center attributes toasts // to "Lotus Chat". Path via %APPDATA% (avoids the SHGetKnownFolderPath free-mem // dance); dir already exists for installed apps. let appdata = match std::env::var_os("APPDATA") { Some(v) => v, None => return, }; let mut lnk = std::path::PathBuf::from(appdata); lnk.push(r"Microsoft\Windows\Start Menu\Programs"); let _ = std::fs::create_dir_all(&lnk); lnk.push("Lotus Chat.lnk"); let exe = match std::env::current_exe() { Ok(p) => p, Err(_) => return, }; let exe_wide: Vec = exe .as_os_str() .encode_wide() .chain(std::iter::once(0)) .collect(); let lnk_wide: Vec = lnk .as_os_str() .encode_wide() .chain(std::iter::once(0)) .collect(); // STA apartment for the shell link objects, mirroring jumplist.rs. All COM // interfaces are dropped before CoUninitialize. let hr = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }; let result = (|| -> windows::core::Result<()> { unsafe { let link: IShellLinkW = CoCreateInstance(&ShellLink, None, CLSCTX_INPROC_SERVER)?; link.SetPath(PCWSTR(exe_wide.as_ptr()))?; link.SetIconLocation(PCWSTR(exe_wide.as_ptr()), 0)?; // Stamp the AUMID onto the link's property store (VT_LPWSTR, exactly // like PKEY_Title in jumplist.rs). let store: IPropertyStore = link.cast()?; let value = PROPVARIANT::from(APP_USER_MODEL_ID); store.SetValue(&PKEY_AppUserModel_ID, &value)?; store.Commit()?; // Persist the .lnk to the Start-Menu Programs folder. let persist: IPersistFile = link.cast()?; persist.Save(PCWSTR(lnk_wide.as_ptr()), true)?; Ok(()) } })(); if hr.is_ok() { unsafe { CoUninitialize() }; } if let Err(e) = result { eprintln!("aumid: failed to install Start-Menu shortcut: {e}"); } } #[cfg(not(target_os = "windows"))] pub fn ensure_app_user_model_id(_app: &AppHandle) {}