diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index eceadf8..83c1b03 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -42,6 +42,10 @@ custom-protocol = [ "tauri/custom-protocol" ] tauri-plugin-global-shortcut = "2" tauri-plugin-updater = "2" +[target.'cfg(target_os = "windows")'.dependencies] +webview2-com = "0.38" +windows = { version = "0.61", features = [] } + [lib] name = "app_lib" crate-type = ["staticlib", "cdylib", "rlib"] diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist new file mode 100644 index 0000000..39b69b1 --- /dev/null +++ b/src-tauri/Info.plist @@ -0,0 +1,10 @@ + + + + + NSCameraUsageDescription + Request camera access for WebRTC calls. + NSMicrophoneUsageDescription + Request microphone access for WebRTC calls. + + diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e6d5ba4..7310f69 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -8,26 +8,54 @@ use tauri::{webview::{NewWindowResponse, WebviewWindowBuilder}, WebviewUrl}; 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() { let port: u16 = 44548; let context = tauri::generate_context!(); let builder = tauri::Builder::default(); - // #[cfg(target_os = "macos")] - // { - // builder = builder.menu(menu::menu()); - // } - builder .plugin(tauri_plugin_localhost::Builder::new(port).build()) .plugin(tauri_plugin_window_state::Builder::default().build()) .plugin(tauri_plugin_opener::init()) .setup(move |app| { - // Dev: use devUrl from tauri.conf.json (http://localhost:8080) to support HMR #[cfg(debug_assertions)] let window_url = WebviewUrl::App(Default::default()); - // Release: tauri-plugin-localhost serves bundled frontend assets on this port #[cfg(not(debug_assertions))] let window_url = { 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>); 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()?; Ok(()) }) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 504e399..6b2e865 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -59,7 +59,7 @@ }, "app": { "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" } } } \ No newline at end of file