fix(windows): grant microphone and camera permissions in WebView2
WebView2 silently denies getUserMedia() unless a PermissionRequested handler explicitly allows it. macOS was already covered by Info.plist; Windows had nothing. Adds a COM event handler via with_webview that auto-approves mic and camera requests so Element Call voice/video works in the desktop app. Also includes previously uncommitted changes: - tauri.conf.json: add media-src / mediastream: to CSP - Info.plist: macOS NSMicrophoneUsageDescription / NSCameraUsageDescription Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,10 @@ custom-protocol = [ "tauri/custom-protocol" ]
|
|||||||
tauri-plugin-global-shortcut = "2"
|
tauri-plugin-global-shortcut = "2"
|
||||||
tauri-plugin-updater = "2"
|
tauri-plugin-updater = "2"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
webview2-com = "0.38"
|
||||||
|
windows = { version = "0.61", features = [] }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "app_lib"
|
name = "app_lib"
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Request camera access for WebRTC calls.</string>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>Request microphone access for WebRTC calls.</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
+54
-7
@@ -8,26 +8,54 @@
|
|||||||
use tauri::{webview::{NewWindowResponse, WebviewWindowBuilder}, WebviewUrl};
|
use tauri::{webview::{NewWindowResponse, WebviewWindowBuilder}, WebviewUrl};
|
||||||
use tauri_plugin_opener::OpenerExt;
|
use tauri_plugin_opener::OpenerExt;
|
||||||
|
|
||||||
|
// Automatically grant camera and microphone permissions in WebView2 (Windows).
|
||||||
|
// macOS handles this via Info.plist; Windows requires an explicit PermissionRequested handler.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
mod win_permissions {
|
||||||
|
use webview2_com::Microsoft::Web::WebView2::Win32::{
|
||||||
|
ICoreWebView2, ICoreWebView2PermissionRequestedEventArgs,
|
||||||
|
ICoreWebView2PermissionRequestedEventHandler,
|
||||||
|
ICoreWebView2PermissionRequestedEventHandler_Impl,
|
||||||
|
COREWEBVIEW2_PERMISSION_KIND_CAMERA, COREWEBVIEW2_PERMISSION_KIND_MICROPHONE,
|
||||||
|
COREWEBVIEW2_PERMISSION_STATE_ALLOW,
|
||||||
|
};
|
||||||
|
use windows::core::implement;
|
||||||
|
|
||||||
|
#[implement(ICoreWebView2PermissionRequestedEventHandler)]
|
||||||
|
pub struct PermissionHandler;
|
||||||
|
|
||||||
|
impl ICoreWebView2PermissionRequestedEventHandler_Impl for PermissionHandler {
|
||||||
|
fn Invoke(
|
||||||
|
&self,
|
||||||
|
_sender: Option<&ICoreWebView2>,
|
||||||
|
args: Option<&ICoreWebView2PermissionRequestedEventArgs>,
|
||||||
|
) -> windows::core::Result<()> {
|
||||||
|
if let Some(args) = args {
|
||||||
|
let kind = unsafe { args.PermissionKind() }?;
|
||||||
|
if kind == COREWEBVIEW2_PERMISSION_KIND_MICROPHONE
|
||||||
|
|| kind == COREWEBVIEW2_PERMISSION_KIND_CAMERA
|
||||||
|
{
|
||||||
|
unsafe { args.SetState(COREWEBVIEW2_PERMISSION_STATE_ALLOW) }?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let port: u16 = 44548;
|
let port: u16 = 44548;
|
||||||
let context = tauri::generate_context!();
|
let context = tauri::generate_context!();
|
||||||
let builder = tauri::Builder::default();
|
let builder = tauri::Builder::default();
|
||||||
|
|
||||||
// #[cfg(target_os = "macos")]
|
|
||||||
// {
|
|
||||||
// builder = builder.menu(menu::menu());
|
|
||||||
// }
|
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.plugin(tauri_plugin_localhost::Builder::new(port).build())
|
.plugin(tauri_plugin_localhost::Builder::new(port).build())
|
||||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
// Dev: use devUrl from tauri.conf.json (http://localhost:8080) to support HMR
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let window_url = WebviewUrl::App(Default::default());
|
let window_url = WebviewUrl::App(Default::default());
|
||||||
|
|
||||||
// Release: tauri-plugin-localhost serves bundled frontend assets on this port
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
let window_url = {
|
let window_url = {
|
||||||
let url = format!("http://localhost:{}", port).parse().unwrap();
|
let url = format!("http://localhost:{}", port).parse().unwrap();
|
||||||
@@ -42,6 +70,25 @@ pub fn run() {
|
|||||||
let _ = app_handle.opener().open_url(url.as_str(), None::<&str>);
|
let _ = app_handle.opener().open_url(url.as_str(), None::<&str>);
|
||||||
NewWindowResponse::Deny
|
NewWindowResponse::Deny
|
||||||
})
|
})
|
||||||
|
.with_webview(|webview| {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2PermissionRequestedEventHandler;
|
||||||
|
use windows::core::EventRegistrationToken;
|
||||||
|
use win_permissions::PermissionHandler;
|
||||||
|
|
||||||
|
let controller = webview.controller();
|
||||||
|
if let Ok(core) = unsafe { controller.CoreWebView2() } {
|
||||||
|
let handler: ICoreWebView2PermissionRequestedEventHandler =
|
||||||
|
PermissionHandler.into();
|
||||||
|
// Register the handler; token is unused since we never unregister.
|
||||||
|
// If this fails to compile, try: unsafe { core.add_PermissionRequested(&handler) }
|
||||||
|
let mut token = EventRegistrationToken(0);
|
||||||
|
let _ = unsafe { core.add_PermissionRequested(&handler, &mut token) };
|
||||||
|
std::mem::forget(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.build()?;
|
.build()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src 'self' blob: data: filesystem: ws: wss: http: https: tauri:; script-src 'self' 'unsafe-eval' 'unsafe-inline' blob: data: filesystem: ws: wss: http: https: tauri:; style-src 'self' 'unsafe-inline' blob: data: filesystem: http: https:; img-src 'self' data: blob: filesystem: http: https:; connect-src 'self' blob: ipc: ws: wss: http: https: http://ipc.localhost"
|
"csp": "default-src 'self' blob: data: filesystem: ws: wss: http: https: tauri:; script-src 'self' 'unsafe-eval' 'unsafe-inline' blob: data: filesystem: ws: wss: http: https: tauri:; style-src 'self' 'unsafe-inline' blob: data: filesystem: http: https:; img-src 'self' data: blob: filesystem: http: https:; media-src 'self' blob: data: mediastream:; connect-src 'self' blob: ipc: ws: wss: http: https: http://ipc.localhost"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user