feat(native): Tier A desktop features (P5-46/36/44/43/49) + window chrome (P5-47)
Adds a `native/` module system (each feature = its own module exposing `#[tauri::command]`s + optional `setup`; `emit_to_web` pushes DOM CustomEvents to the web like `forward_deeplink`). Wired into generate_handler! + native::setup; windows-crate feature union added to Cargo.toml. - power.rs (P5-46): SetThreadExecutionState held on the main thread while a call is active; released on end. Cross-platform (no-op off Windows). - jumplist.rs (P5-36): ICustomDestinationList "Recent Rooms" of IShellLink tasks launching the exe with a matrix: arg (existing deep-link handler opens the room). - thumbbar.rs (P5-44): ITaskbarList3 ThumbBar Mute/Deafen/End (GDI HICONs) + a window subclass catching THBN_CLICKED → emit thumbbar-action. - smtc.rs (P5-43): WinRT SystemMediaTransportControls via GetForWindow; ButtonPressed → smtc-action; call-state command. (Experimental for a non-media app.) - network.rs (P5-49): INetworkListManager poll thread → emit network-changed. - chrome.rs (P5-47): cross-platform window-control commands + set_custom_chrome (set_decorations) for the opt-in TDS titlebar. NOT compile-verified locally (no Rust/Windows toolchain on the dev box) — this is for the CI Windows compile pass (GitHub test.yml / Gitea windows runner). Expect a possible fixup round (windows-crate feature/namespace paths, e.g. subclass APIs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
//! 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<JumpItem>) -> 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, HSTRING, PCWSTR, PROPVARIANT},
|
||||
Win32::{
|
||||
System::Com::{
|
||||
CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_INPROC_SERVER,
|
||||
COINIT_APARTMENTTHREADED,
|
||||
},
|
||||
UI::Shell::{
|
||||
DestinationList, EnumerableObjectCollection, ICustomDestinationList,
|
||||
IObjectArray, IObjectCollection, IShellLinkW, ShellLink,
|
||||
PropertiesSystem::{IPropertyStore, PKEY_Title},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// 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<u16> = 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<u16> =
|
||||
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()?;
|
||||
let title = PROPVARIANT::from(&HSTRING::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(())
|
||||
}
|
||||
Reference in New Issue
Block a user