fix(windows): merge taskbar icon (AUMID) + focus window on notification click
Taskbar showed two icons because the AppUserModelID was (a) set too late — after the main window was built, so its taskbar button grouped under a mismatched implicit AUMID — and (b) valued 'LotusGuild.LotusChat', which differs from the bundle identifier 'org.lotusguild.lotus-chat' that Tauri's NSIS installer stamps on its shortcuts. Fix both: set the AUMID at the top of run() before the window is built (new set_process_aumid, split out of ensure_app_user_model_id), and align the constant to the bundle identifier so the running window and a pinned installer shortcut group together. Notification click didn't raise the window: add a focus_main_window command (reuses show_main) for the web/service-worker path, and call show_main directly in the rich-toast Activated body-click handler. Note: existing users who pinned the old mis-grouped icon may need to re-pin once. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -466,7 +466,20 @@ fn flash_window(window: tauri::Window) -> Result<(), String> {
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Bring the main window to the foreground. Invoked from the web side when a
|
||||
/// notification is clicked — the service-worker/page path can't raise the native
|
||||
/// OS window on its own (a WebView2 `client.focus()` only focuses the document).
|
||||
#[tauri::command]
|
||||
fn focus_main_window(app: tauri::AppHandle) {
|
||||
show_main(&app);
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
// Advertise the process AUMID BEFORE the main window is built (in `.setup`,
|
||||
// below) so the window's taskbar button groups under the same identity as the
|
||||
// pinned installer shortcut. Must precede the WebviewWindowBuilder.
|
||||
native::aumid::set_process_aumid();
|
||||
|
||||
let port: u16 = 44548;
|
||||
let context = tauri::generate_context!();
|
||||
|
||||
@@ -492,6 +505,7 @@ pub fn run() {
|
||||
set_tray_unread,
|
||||
get_tray_dnd,
|
||||
flash_window,
|
||||
focus_main_window,
|
||||
send_notification,
|
||||
check_for_update,
|
||||
install_update,
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
//! (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`
|
||||
//! 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).
|
||||
//!
|
||||
@@ -19,12 +22,35 @@ 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";
|
||||
///
|
||||
/// 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, HSTRING, PCWSTR};
|
||||
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.
|
||||
@@ -34,20 +60,14 @@ pub fn ensure_app_user_model_id(_app: &AppHandle) {
|
||||
StructuredStorage::PROPVARIANT, CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED,
|
||||
};
|
||||
use windows::Win32::UI::Shell::{
|
||||
PropertiesSystem::IPropertyStore, IShellLinkW, SetCurrentProcessExplicitAppUserModelID,
|
||||
ShellLink,
|
||||
PropertiesSystem::IPropertyStore, IShellLinkW, ShellLink,
|
||||
};
|
||||
|
||||
// 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.
|
||||
// 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,
|
||||
|
||||
@@ -186,7 +186,13 @@ fn show_windows_toast(
|
||||
.to_string();
|
||||
super::emit_to_web(&app_activated, "lotus-notification-reply", &payload);
|
||||
} else {
|
||||
// Plain body click: forward the launch path so the web routes to it.
|
||||
// Plain body click: raise the window to the foreground (the
|
||||
// "foreground" activationType is unreliable for an unpackaged app,
|
||||
// so do it explicitly), then forward the launch path so the web
|
||||
// routes to the room. `show_main` is the shared tray/deep-link
|
||||
// helper. Not done for the reply branch — an inline quick-reply
|
||||
// shouldn't yank the window forward.
|
||||
crate::show_main(&app_activated);
|
||||
let payload = serde_json::json!({
|
||||
"path": path_owned.as_deref(),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user