//! P5-43 — System Media Transport Controls (SMTC) call surface (Windows). //! //! Surfaces the active voice/video call to the Windows media overlay (the //! volume flyout / SMTC card) so the user can mute or hang up from the OS //! media controls. We create the SMTC via the WinRT interop factory //! (`ISystemMediaTransportControlsInterop::GetForWindow`), keep the object in //! managed state, and let the web client drive its state through //! `set_smtc_call_state` as the call-embed atom / mic state changes. //! //! Button mapping: **Play/Pause → mute toggle**, **Stop → end call**. Presses //! are forwarded to the web client as a `smtc-action` DOM CustomEvent (see //! `super::emit_to_web`) with `action` in `"mute" | "end"`; the web hook //! (`useTauriSmtc`) translates them into `CallControl.toggleMicrophone()` / //! `CallEmbed.hangup()`. //! //! RUNTIME NOTE: SMTC is designed for real media apps. For a non-media app the //! card may not actually appear unless the process owns an active audio session //! recognised by the system. This module prioritises a clean compile and //! correct WinRT API usage; visibility of the overlay at runtime is uncertain //! and may depend on the embedded Element Call iframe holding an audio session. //! //! Other platforms are a no-op (SMTC is Windows-only); the command keeps an //! identical cross-platform signature so the web side stays unconditional. use tauri::AppHandle; /// Payload for the `smtc-action` DOM event forwarded to the web client. #[cfg(target_os = "windows")] #[derive(serde::Serialize)] struct Ev { action: String, } /// Holds the SMTC object (and its `ButtonPressed` registration token) in Tauri /// managed state so `set_smtc_call_state` can update it at runtime. Mirrors the /// `TrayUnreadState` managed-state pattern in lib.rs. #[cfg(target_os = "windows")] struct SmtcState { controls: std::sync::Mutex>, // Kept alive so the ButtonPressed handler stays registered for the app's // lifetime; never unregistered. (windows 0.61 event registrations return a // plain i64 token — the EventRegistrationToken newtype is gone.) _token: i64, } /// Called once from `native::setup()`. Creates and configures the SMTC on /// Windows; no-op elsewhere. SMTC init failures are logged and swallowed so a /// missing/unsupported overlay never blocks app startup. pub fn setup(app: &AppHandle) -> tauri::Result<()> { #[cfg(target_os = "windows")] { if let Err(err) = init_smtc(app) { eprintln!("smtc: failed to initialize System Media Transport Controls: {err:?}"); } } #[cfg(not(target_os = "windows"))] { let _ = app; } Ok(()) } #[cfg(target_os = "windows")] fn init_smtc(app: &AppHandle) -> Result<(), Box> { use tauri::Manager; use windows::core::{factory, HSTRING}; use windows::Foundation::TypedEventHandler; use windows::Media::{ MediaPlaybackStatus, MediaPlaybackType, SystemMediaTransportControls, SystemMediaTransportControlsButton, SystemMediaTransportControlsButtonPressedEventArgs, }; use windows::Win32::Foundation::HWND; use windows::Win32::System::WinRT::ISystemMediaTransportControlsInterop; let window = app .get_webview_window("main") .ok_or("smtc: main window not found")?; // Match the HWND conversion used by set_badge_count in lib.rs. let hwnd = HWND(window.hwnd()?.0 as _); // SMTC has no WinRT constructor; it's obtained per-window via the interop // factory. `factory::()` fetches the activation factory for the // runtime class `C` cast to the classic COM interop interface `I`. let interop = factory::()?; let controls: SystemMediaTransportControls = unsafe { interop.GetForWindow(hwnd)? }; controls.SetIsEnabled(true)?; controls.SetIsPlayEnabled(true)?; controls.SetIsPauseEnabled(true)?; controls.SetIsStopEnabled(true)?; // Configure the card metadata once ("In call"); the web side only toggles // playback status afterwards. let updater = controls.DisplayUpdater()?; updater.SetType(MediaPlaybackType::Music)?; let music = updater.MusicProperties()?; music.SetTitle(&HSTRING::from("In call"))?; updater.Update()?; // Idle until a call becomes active (set_smtc_call_state flips this). controls.SetPlaybackStatus(MediaPlaybackStatus::Closed)?; // ButtonPressed → forward a normalized action to the web client. let app_for_handler = app.clone(); let handler = TypedEventHandler::< SystemMediaTransportControls, SystemMediaTransportControlsButtonPressedEventArgs, >::new(move |_sender, args| { if let Some(args) = args.as_ref() { let button = args.Button()?; let action = if button == SystemMediaTransportControlsButton::Play || button == SystemMediaTransportControlsButton::Pause { Some("mute") } else if button == SystemMediaTransportControlsButton::Stop { Some("end") } else { None }; if let Some(action) = action { let payload = serde_json::to_string(&Ev { action: action.to_string(), }) .unwrap_or_default(); super::emit_to_web(&app_for_handler, "smtc-action", &payload); } } Ok(()) }); let token = controls.ButtonPressed(&handler)?; app.manage(SmtcState { controls: std::sync::Mutex::new(Some(controls)), _token: token, }); Ok(()) } /// Reflect the call state onto the SMTC. When `active`, enable the controls and /// set playback status to Playing (unmuted) / Paused (muted); when inactive, /// mark the card Closed and disable it. Windows-only; no-op elsewhere. #[tauri::command] pub fn set_smtc_call_state(app: AppHandle, active: bool, muted: bool) { #[cfg(target_os = "windows")] { use tauri::Manager; if let Some(state) = app.try_state::() { if let Ok(guard) = state.controls.lock() { if let Some(controls) = guard.as_ref() { let _ = apply_call_state(controls, active, muted); } } } } #[cfg(not(target_os = "windows"))] { // No-op off Windows; bind args so the signature is identical everywhere // and no unused warnings fire. let _ = (&app, active, muted); } } #[cfg(target_os = "windows")] fn apply_call_state( controls: &windows::Media::SystemMediaTransportControls, active: bool, muted: bool, ) -> windows::core::Result<()> { use windows::Media::MediaPlaybackStatus; if active { controls.SetIsEnabled(true)?; let status = if muted { MediaPlaybackStatus::Paused } else { MediaPlaybackStatus::Playing }; controls.SetPlaybackStatus(status)?; } else { controls.SetPlaybackStatus(MediaPlaybackStatus::Closed)?; controls.SetIsEnabled(false)?; } Ok(()) }