2026-07-02 22:32:08 -04:00
|
|
|
//! 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.
|
|
|
|
|
//!
|
|
|
|
|
//! On startup we (1) advertise the AUMID for this process and (2) install/refresh
|
|
|
|
|
//! a Start-Menu `.lnk` (same name → overwrites the installer's, no duplicate)
|
|
|
|
|
//! carrying the AUMID. Reuses 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`.
|
|
|
|
|
pub const APP_USER_MODEL_ID: &str = "LotusGuild.LotusChat";
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
|
pub fn ensure_app_user_model_id(_app: &AppHandle) {
|
|
|
|
|
use std::os::windows::ffi::OsStrExt;
|
2026-07-03 10:54:14 -04:00
|
|
|
use windows::core::{Interface, HSTRING, 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;
|
2026-07-02 22:32:08 -04:00
|
|
|
use windows::Win32::System::Com::{
|
|
|
|
|
CoCreateInstance, CoInitializeEx, CoUninitialize, IPersistFile,
|
|
|
|
|
StructuredStorage::PROPVARIANT, CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED,
|
|
|
|
|
};
|
|
|
|
|
use windows::Win32::UI::Shell::{
|
2026-07-03 10:54:14 -04:00
|
|
|
PropertiesSystem::IPropertyStore, IShellLinkW, SetCurrentProcessExplicitAppUserModelID,
|
|
|
|
|
ShellLink,
|
2026-07-02 22:32:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 1. Advertise the AUMID for this process (must happen before any toast fires).
|
|
|
|
|
if let Err(e) =
|
|
|
|
|
unsafe { SetCurrentProcessExplicitAppUserModelID(&HSTRING::from(APP_USER_MODEL_ID)) }
|
|
|
|
|
{
|
|
|
|
|
eprintln!("aumid: SetCurrentProcessExplicitAppUserModelID failed: {e}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 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<u16> = exe
|
|
|
|
|
.as_os_str()
|
|
|
|
|
.encode_wide()
|
|
|
|
|
.chain(std::iter::once(0))
|
|
|
|
|
.collect();
|
|
|
|
|
let lnk_wide: Vec<u16> = 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);
|
2026-07-03 10:54:14 -04:00
|
|
|
store.SetValue(&PKEY_AppUserModel_ID, &value)?;
|
2026-07-02 22:32:08 -04:00
|
|
|
store.Commit()?;
|
|
|
|
|
|
|
|
|
|
// Persist the .lnk to the Start-Menu Programs folder.
|
|
|
|
|
let persist: IPersistFile = link.cast()?;
|
2026-07-03 10:54:14 -04:00
|
|
|
persist.Save(PCWSTR(lnk_wide.as_ptr()), true)?;
|
2026-07-02 22:32:08 -04:00
|
|
|
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) {}
|