//! P5-36 — Windows taskbar Jump List ("Recent Rooms"). //! //! Publishes a custom Jump List category so users can right-click the taskbar //! (or Start) icon and jump straight into a recently-active room. The web client //! calls `set_jump_list([{ title, uri }])` from `useTauriJumpList` whenever its //! recent-room list changes; each `uri` is a `matrix:` deep link. //! //! Windows: builds an `ICustomDestinationList` with an `IObjectCollection` of //! `IShellLinkW` task links. Each link relaunches the current executable with the //! room's `matrix:` URI as its single argument — the existing deep-link handler //! in lib.rs (`forward_deeplink` → `lotus-deeplink`) then routes it to the room. //! The link's visible label is set via `IPropertyStore` + `PKEY_Title` //! (System.Title) using a `PROPVARIANT`. //! //! COM here runs on the command's (thread-pool) thread, so we initialize an STA //! apartment with `CoInitializeEx` and balance it with `CoUninitialize` only when //! we were the ones that initialized it (mirrors the COM usage in //! `set_badge_count`). All COM interfaces are scoped so they release before the //! apartment is torn down. //! //! Other platforms are a no-op (macOS has no direct equivalent; Linux desktop //! files differ) — the command stays cross-platform so the web side is //! unconditional. use tauri::AppHandle; /// One Jump List entry supplied by the web client. `uri` is a `matrix:` deep /// link accepted by the deep-link handler in lib.rs. #[derive(serde::Deserialize)] pub struct JumpItem { pub title: String, pub uri: String, } #[tauri::command] pub fn set_jump_list(app: AppHandle, items: Vec) -> Result<(), String> { #[cfg(target_os = "windows")] { // `app` is unused on Windows (COM runs on the calling thread); bind it so // the signature stays identical cross-platform and no warning fires. let _ = &app; use std::os::windows::ffi::OsStrExt; use windows::{ core::{w, Interface, PCWSTR}, Win32::{ Storage::EnhancedStorage::PKEY_Title, System::Com::{ CoCreateInstance, CoInitializeEx, CoUninitialize, StructuredStorage::PROPVARIANT, CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED, }, UI::Shell::{ // IObjectArray/IObjectCollection live in Shell::Common // (feature Win32_UI_Shell_Common), NOT Shell or System::Com. Common::{IObjectArray, IObjectCollection}, DestinationList, EnumerableObjectCollection, ICustomDestinationList, IShellLinkW, PropertiesSystem::IPropertyStore, ShellLink, }, }, }; // Wide, NUL-terminated path to the running executable; reused for every // link's target and icon. Computed before touching COM so a failure here // doesn't leak an initialized apartment. let exe = std::env::current_exe().map_err(|e| e.to_string())?; let exe_wide: Vec = exe .as_os_str() .encode_wide() .chain(std::iter::once(0)) .collect(); // STA is required for the shell Jump List objects. S_OK means we // initialized (and must uninitialize); S_FALSE means it was already // initialized on this thread (still balance it); RPC_E_CHANGED_MODE (an // error) means don't touch it. Note: `unsafe` does not reach into the // closure below, so its body carries its own `unsafe` block; the COM // interfaces it creates are all released (dropped) before we uninitialize. let hr = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }; let result = (|| -> windows::core::Result<()> { unsafe { let list: ICustomDestinationList = CoCreateInstance(&DestinationList, None, CLSCTX_INPROC_SERVER)?; if items.is_empty() { // Nothing to show — clear any list we previously published. list.DeleteList(PCWSTR::null())?; return Ok(()); } // BeginList hands back the items the user manually removed; we // don't re-add anything, so we can ignore it. `min_slots` is the // max entries the shell will display. let mut min_slots: u32 = 0; let _removed: IObjectArray = list.BeginList(&mut min_slots)?; let collection: IObjectCollection = CoCreateInstance(&EnumerableObjectCollection, None, CLSCTX_INPROC_SERVER)?; for item in &items { let link: IShellLinkW = CoCreateInstance(&ShellLink, None, CLSCTX_INPROC_SERVER)?; // Relaunch this exe with the matrix: URI as its only argument. link.SetPath(PCWSTR(exe_wide.as_ptr()))?; let arg_wide: Vec = item.uri.encode_utf16().chain(std::iter::once(0)).collect(); link.SetArguments(PCWSTR(arg_wide.as_ptr()))?; // Use the app's own icon for the entry. link.SetIconLocation(PCWSTR(exe_wide.as_ptr()), 0)?; // The visible label comes from System.Title on the link's // property store (a bare IShellLink has no display name). let store: IPropertyStore = link.cast()?; // From<&str> builds a VT_LPWSTR PROPVARIANT (what System.Title expects). let title = PROPVARIANT::from(item.title.as_str()); store.SetValue(&PKEY_Title, &title)?; store.Commit()?; collection.AddObject(&link)?; } let array: IObjectArray = collection.cast()?; list.AppendCategory(w!("Recent Rooms"), &array)?; list.CommitList()?; Ok(()) } })(); // All interfaces above are dropped (released) by the time we get here, so // it's safe to tear the apartment down. if hr.is_ok() { unsafe { CoUninitialize() }; } result.map_err(|e| e.to_string())?; } #[cfg(not(target_os = "windows"))] { // No-op on non-Windows platforms (see module docs). Bind the args so the // signature stays identical cross-platform and no unused warnings fire. let _ = (&app, &items); } Ok(()) }