From 336b0e69d2da37574f7e5786d55fcf2705a33e3c Mon Sep 17 00:00:00 2001 From: Jason Tsai Date: Wed, 20 Mar 2024 12:11:53 +0800 Subject: [PATCH 1/8] fix(mac): unsuppressing media permission request only for macOS 14.0 --- .../fix-unsuppress-media-request-for-mac.md | 5 +++ src/wkwebview/mod.rs | 35 ++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 .changes/fix-unsuppress-media-request-for-mac.md diff --git a/.changes/fix-unsuppress-media-request-for-mac.md b/.changes/fix-unsuppress-media-request-for-mac.md new file mode 100644 index 000000000..b94d98b5e --- /dev/null +++ b/.changes/fix-unsuppress-media-request-for-mac.md @@ -0,0 +1,5 @@ +--- +"wry": patch +--- + +On macOS 14.0, unsuppressing the media permission request. diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 01a0d3477..515a655a9 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -16,7 +16,9 @@ mod util; use cocoa::appkit::{NSView, NSViewHeightSizable, NSViewMinYMargin, NSViewWidthSizable}; use cocoa::{ base::{id, nil, NO, YES}, - foundation::{NSDictionary, NSFastEnumeration, NSInteger}, + foundation::{ + NSDictionary, NSFastEnumeration, NSInteger, NSOperatingSystemVersion, NSProcessInfo, + }, }; use dpi::{LogicalPosition, LogicalSize}; use once_cell::sync::Lazy; @@ -929,10 +931,19 @@ impl InnerWebView { run_file_upload_panel as extern "C" fn(&Object, Sel, id, id, id, id), ); - ctl.add_method( - sel!(webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:), - request_media_capture_permission as extern "C" fn(&Object, Sel, id, id, id, id, id), - ); + #[cfg(target_os = "macos")] + match operating_system_version() { + (14, 0, _) => { + // Do not suppress media request on 14.0: + // https://github.com/tauri-apps/wry/issues/1101 + } + _ => { + ctl.add_method( + sel!(webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:), + request_media_capture_permission as extern "C" fn(&Object, Sel, id, id, id, id, id), + ); + } + } ctl.register() } @@ -1336,8 +1347,22 @@ pub fn url_from_webview(webview: id) -> Result { .map_err(Into::into) } +pub fn operating_system_version() -> (u64, u64, u64) { + unsafe { + let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; + let version: NSOperatingSystemVersion = msg_send![process_info, operatingSystemVersion]; + ( + version.majorVersion, + version.minorVersion, + version.patchVersion, + ) + } +} + +/// **Warning:** Panic occurs when caller application's `CFBundleDisplayName` contains non-ASCII characters. pub fn platform_webview_version() -> Result { unsafe { + // bundleWithIdentifier: panic when CFBundleDisplayName contains non-ASCII characters. let bundle: id = msg_send![class!(NSBundle), bundleWithIdentifier: NSString::new("com.apple.WebKit")]; let dict: id = msg_send![bundle, infoDictionary]; From d23ebfa1383cf87803eb5b0d647bf5f0a2ecce5e Mon Sep 17 00:00:00 2001 From: fabianlars Date: Wed, 20 Mar 2024 18:05:51 +0100 Subject: [PATCH 2/8] wip --- src/wkwebview/mod.rs | 89 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 515a655a9..39850f251 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -918,12 +918,69 @@ impl InnerWebView { decision_handler: id, ) { unsafe { + println!("request_media_capture_permission"); + let ty: NSInteger = msg_send![_type, integerValue]; + dbg!(ty); + + let decision_handler = decision_handler as *mut block::Block<(NSInteger,), c_void>; + //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc + (*decision_handler).call((0,)); // should be 1 for mic/cam + } + } + + extern "C" fn request_user_media_authorization( + _this: &Object, + _: Sel, + _webview: id, + _devices: id, + _url: id, + _main_frame_url: id, + decision_handler: id, + ) { + unsafe { + println!("request_user_media_authorization"); + + let decision_handler = decision_handler as *mut block::Block<(BOOL,), c_void>; + //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc + (*decision_handler).call((BOOL::from(true),)); + } + } + + extern "C" fn request_display_capture_permission( + _this: &Object, + _: Sel, + _webview: id, + _origin: id, + _frame: id, + _type: id, + decision_handler: id, + ) { + unsafe { + println!("request_display_capture_permission"); + let decision_handler = decision_handler as *mut block::Block<(NSInteger,), c_void>; //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc (*decision_handler).call((1,)); } } + extern "C" fn query_permission_for_name( + _this: &Object, + _: Sel, + _webview: id, + _name: id, + _origin: id, + handler: id, + ) { + unsafe { + println!("query_permission_for_name"); + + let handler = handler as *mut block::Block<(NSInteger,), c_void>; + //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc + (*handler).call((1,)); // should be 1 for mic/cam + } + } + let ui_delegate = match ClassDecl::new("WebViewUIDelegate", class!(NSObject)) { Some(mut ctl) => { ctl.add_method( @@ -932,16 +989,44 @@ impl InnerWebView { ); #[cfg(target_os = "macos")] + // TODO: remove this later + #[allow(clippy::match_single_binding)] match operating_system_version() { - (14, 0, _) => { + // TODO: Test on older versions + /* (14, 0, _) => { // Do not suppress media request on 14.0: // https://github.com/tauri-apps/wry/issues/1101 - } + } */ _ => { + dbg!("register"); + + // This probably isn't working but we somehow need an equivalent to: + // @protocol WKUIDelegatePrivate + let p = objc::runtime::Protocol::get("WKUIDelegatePrivate"); + ctl.add_protocol(p.unwrap()); + ctl.add_method( sel!(webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:), request_media_capture_permission as extern "C" fn(&Object, Sel, id, id, id, id, id), ); + + // I tried webView _webView and webView_ and none of them worked but this is probably not the root cause + // Also, they seem to do _something_ because i have to comment out all of them (and requestMediaCapturePermission above) for getDisplayMedia to work. + // If i leave requestMediaCapturePermission commented out but add those 3 methods below it does seem to do something because + // https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/ stops printing NotAllowed. + ctl + .add_method(sel!(_webView:requestUserMediaAuthorizationForDevices:url:mainFrameURL:decisionHandler:), + request_user_media_authorization as extern "C" fn(&Object, Sel, id, id, id, id, id)); + + ctl.add_method( + sel!(_webView:requestDisplayCapturePermissionForSecurityOrigin:initiatedByFrame:withSystemAudio:decisionHandler:), + request_display_capture_permission as extern "C" fn(&Object, Sel, id, id, id, id, id), + ); + + ctl.add_method( + sel!(_webView:queryPermissionName:forOrigin:completionHandler:), + query_permission_for_name as extern "C" fn(&Object, Sel, id, id, id, id), + ); } } From c2757702ff2b50565679469977db56b7eeec73d5 Mon Sep 17 00:00:00 2001 From: Jason Tsai Date: Sat, 23 Mar 2024 17:23:00 +0800 Subject: [PATCH 3/8] wip: possible fix --- examples/simple.rs | 2 +- src/wkwebview/mod.rs | 118 ++++++++++++++++++++----------------- src/wkwebview/objc_type.rs | 88 +++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 56 deletions(-) create mode 100644 src/wkwebview/objc_type.rs diff --git a/examples/simple.rs b/examples/simple.rs index 5528e41b9..b215f69ab 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -35,7 +35,7 @@ fn main() -> wry::Result<()> { }; let _webview = builder - .with_url("http://tauri.app") + .with_url("https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/") .with_drag_drop_handler(|e| { match e { wry::DragDropEvent::Enter { paths, position } => { diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 39850f251..9c133de4a 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +// TODO: remove this once the issue is fixed +mod objc_type; + mod download; #[cfg(target_os = "macos")] mod drag_drop; @@ -16,9 +19,7 @@ mod util; use cocoa::appkit::{NSView, NSViewHeightSizable, NSViewMinYMargin, NSViewWidthSizable}; use cocoa::{ base::{id, nil, NO, YES}, - foundation::{ - NSDictionary, NSFastEnumeration, NSInteger, NSOperatingSystemVersion, NSProcessInfo, - }, + foundation::{NSDictionary, NSFastEnumeration, NSInteger, NSOperatingSystemVersion}, }; use dpi::{LogicalPosition, LogicalSize}; use once_cell::sync::Lazy; @@ -64,6 +65,7 @@ use crate::{ set_download_delegate, }, navigation::{add_navigation_mathods, drop_navigation_methods, set_navigation_methods}, + objc_type::{WKDisplayCapturePermissionDecision, WKMediaCaptureType, WKPermissionDecision}, }, Error, PageLoadEvent, Rect, RequestAsyncResponder, Result, WebContext, WebViewAttributes, RGBA, }; @@ -908,79 +910,85 @@ impl InnerWebView { } } + // getUserMedia permission request handler extern "C" fn request_media_capture_permission( _this: &Object, _: Sel, _webview: id, _origin: id, _frame: id, - _type: id, + capture_type: isize, decision_handler: id, ) { unsafe { println!("request_media_capture_permission"); - let ty: NSInteger = msg_send![_type, integerValue]; - dbg!(ty); - let decision_handler = decision_handler as *mut block::Block<(NSInteger,), c_void>; - //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc - (*decision_handler).call((0,)); // should be 1 for mic/cam - } - } - - extern "C" fn request_user_media_authorization( - _this: &Object, - _: Sel, - _webview: id, - _devices: id, - _url: id, - _main_frame_url: id, - decision_handler: id, - ) { - unsafe { - println!("request_user_media_authorization"); + dbg!(WKMediaCaptureType::from(capture_type)); - let decision_handler = decision_handler as *mut block::Block<(BOOL,), c_void>; - //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc - (*decision_handler).call((BOOL::from(true),)); + // https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc + let decision_handler = + decision_handler as *mut block::Block<(WKPermissionDecision,), c_void>; + (*decision_handler).call((WKPermissionDecision::Prompt,)); // should be 1 for mic/cam } } + // extern "C" fn request_user_media_authorization( + // _this: &Object, + // _: Sel, + // _webview: id, + // _devices: id, + // _url: id, + // _main_frame_url: id, + // decision_handler: id, + // ) { + // unsafe { + // println!("request_user_media_authorization"); + + // let decision_handler = decision_handler as *mut block::Block<(BOOL,), c_void>; + // //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc + // (*decision_handler).call((BOOL::from(true),)); + // } + // } + + // getDisplayMedia permission request handler extern "C" fn request_display_capture_permission( _this: &Object, _: Sel, _webview: id, _origin: id, _frame: id, - _type: id, + capture_type: isize, decision_handler: id, ) { unsafe { println!("request_display_capture_permission"); - let decision_handler = decision_handler as *mut block::Block<(NSInteger,), c_void>; - //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc - (*decision_handler).call((1,)); - } - } - - extern "C" fn query_permission_for_name( - _this: &Object, - _: Sel, - _webview: id, - _name: id, - _origin: id, - handler: id, - ) { - unsafe { - println!("query_permission_for_name"); + dbg!(WKMediaCaptureType::from(capture_type)); - let handler = handler as *mut block::Block<(NSInteger,), c_void>; + let decision_handler = + decision_handler as *mut block::Block<(WKDisplayCapturePermissionDecision,), c_void>; //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc - (*handler).call((1,)); // should be 1 for mic/cam + (*decision_handler).call((WKDisplayCapturePermissionDecision::ScreenPrompt,)); } } + // extern "C" fn query_permission_for_name( + // _this: &Object, + // _: Sel, + // _webview: id, + // _name: id, + // _origin: id, + // handler: id, + // ) { + // unsafe { + // println!("query_permission_for_name"); + + // let handler = handler as *mut block::Block<(NSInteger,), c_void>; + // //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc + // (*handler).call((1,)); // should be 1 for mic/cam + // } + // } + let ui_delegate = match ClassDecl::new("WebViewUIDelegate", class!(NSObject)) { Some(mut ctl) => { ctl.add_method( @@ -1007,26 +1015,26 @@ impl InnerWebView { ctl.add_method( sel!(webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:), - request_media_capture_permission as extern "C" fn(&Object, Sel, id, id, id, id, id), + request_media_capture_permission as extern "C" fn(&Object, Sel, id, id, id, isize, id), ); // I tried webView _webView and webView_ and none of them worked but this is probably not the root cause // Also, they seem to do _something_ because i have to comment out all of them (and requestMediaCapturePermission above) for getDisplayMedia to work. // If i leave requestMediaCapturePermission commented out but add those 3 methods below it does seem to do something because // https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/ stops printing NotAllowed. - ctl - .add_method(sel!(_webView:requestUserMediaAuthorizationForDevices:url:mainFrameURL:decisionHandler:), - request_user_media_authorization as extern "C" fn(&Object, Sel, id, id, id, id, id)); + // ctl + // .add_method(sel!(_webView:requestUserMediaAuthorizationForDevices:url:mainFrameURL:decisionHandler:), + // request_user_media_authorization as extern "C" fn(&Object, Sel, id, id, id, id, id)); ctl.add_method( - sel!(_webView:requestDisplayCapturePermissionForSecurityOrigin:initiatedByFrame:withSystemAudio:decisionHandler:), - request_display_capture_permission as extern "C" fn(&Object, Sel, id, id, id, id, id), + sel!(_webView:requestDisplayCapturePermissionForOrigin:initiatedByFrame:withSystemAudio:decisionHandler:), + request_display_capture_permission as extern "C" fn(&Object, Sel, id, id, id, isize, id), ); - ctl.add_method( - sel!(_webView:queryPermissionName:forOrigin:completionHandler:), - query_permission_for_name as extern "C" fn(&Object, Sel, id, id, id, id), - ); + // ctl.add_method( + // sel!(webView:queryPermissionName:forOrigin:completionHandler:), + // query_permission_for_name as extern "C" fn(&Object, Sel, id, id, id, id), + // ); } } diff --git a/src/wkwebview/objc_type.rs b/src/wkwebview/objc_type.rs new file mode 100644 index 000000000..3da859573 --- /dev/null +++ b/src/wkwebview/objc_type.rs @@ -0,0 +1,88 @@ +// TODO: This file is for convinience testing the media permission and should be removed + +#[repr(isize)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WKMediaCaptureType { + Camera = 0, + Microphone, + CameraAndMicrophone, +} + +impl From for WKMediaCaptureType { + fn from(value: isize) -> Self { + match value { + 0 => WKMediaCaptureType::Camera, + 1 => WKMediaCaptureType::Microphone, + 2 => WKMediaCaptureType::CameraAndMicrophone, + _ => panic!("Invalid WKMediaCaptureType value"), + } + } +} + +impl From for isize { + fn from(value: WKMediaCaptureType) -> Self { + match value { + WKMediaCaptureType::Camera => 0, + WKMediaCaptureType::Microphone => 1, + WKMediaCaptureType::CameraAndMicrophone => 2, + } + } +} + +#[repr(isize)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WKPermissionDecision { + Prompt = 0, + Grant, + Deny, +} + +impl From for WKPermissionDecision { + fn from(value: isize) -> Self { + match value { + 0 => WKPermissionDecision::Prompt, + 1 => WKPermissionDecision::Grant, + 2 => WKPermissionDecision::Deny, + _ => panic!("Invalid WKPermissionDecision value"), + } + } +} + +impl From for isize { + fn from(value: WKPermissionDecision) -> Self { + match value { + WKPermissionDecision::Prompt => 0, + WKPermissionDecision::Grant => 1, + WKPermissionDecision::Deny => 2, + } + } +} + +#[repr(isize)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WKDisplayCapturePermissionDecision { + Deny = 0, + ScreenPrompt, + WindowPrompt, +} + +impl From for WKDisplayCapturePermissionDecision { + fn from(value: isize) -> Self { + match value { + 0 => WKDisplayCapturePermissionDecision::Deny, + 1 => WKDisplayCapturePermissionDecision::ScreenPrompt, + 2 => WKDisplayCapturePermissionDecision::WindowPrompt, + _ => panic!("Invalid WKDisplayCapturePermissionDecision value"), + } + } +} + +impl From for isize { + fn from(value: WKDisplayCapturePermissionDecision) -> Self { + match value { + WKDisplayCapturePermissionDecision::Deny => 0, + WKDisplayCapturePermissionDecision::ScreenPrompt => 1, + WKDisplayCapturePermissionDecision::WindowPrompt => 2, + } + } +} From f615a23eae8a7c471c43207807db0d312e5cd7aa Mon Sep 17 00:00:00 2001 From: Jason Tsai Date: Tue, 26 Mar 2024 18:24:19 +0800 Subject: [PATCH 4/8] poc: expose display capture decision handler --- examples/simple.rs | 5 ++ src/lib.rs | 67 +++++++++++++++++++ src/wkwebview/mod.rs | 128 +++++++++++-------------------------- src/wkwebview/objc_type.rs | 88 ------------------------- 4 files changed, 111 insertions(+), 177 deletions(-) delete mode 100644 src/wkwebview/objc_type.rs diff --git a/examples/simple.rs b/examples/simple.rs index b215f69ab..31092c9e9 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -8,6 +8,7 @@ use tao::{ window::WindowBuilder, }; use wry::WebViewBuilder; +use wry::WebViewBuilderExtMacOS; fn main() -> wry::Result<()> { let event_loop = EventLoop::new(); @@ -36,6 +37,10 @@ fn main() -> wry::Result<()> { let _webview = builder .with_url("https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/") + .with_display_capture_decision_handler(|capture_type| { + dbg!(capture_type); + wry::WKDisplayCapturePermissionDecision::ScreenPrompt + }) .with_drag_drop_handler(|e| { match e { wry::DragDropEvent::Enter { paths, position } => { diff --git a/src/lib.rs b/src/lib.rs index 54e740bb9..2da201952 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -503,6 +503,16 @@ pub struct WebViewAttributes { /// This is only effective if the webview was created by [`WebView::new_as_child`] or [`WebViewBuilder::new_as_child`] /// or on Linux, if was created by [`WebViewExtUnix::new_gtk`] or [`WebViewBuilderExtUnix::new_gtk`] with [`gtk::Fixed`]. pub bounds: Option, + + /// TODO: + /// https://github.com/tauri-apps/wry/issues/1195 + /// for macOS 13+ only + #[cfg(target_os = "macos")] + pub display_capture_decision_handler: + Option WKDisplayCapturePermissionDecision>>, + #[cfg(not(target_os = "macos"))] + display_capture_decision_handler: + Option WKDisplayCapturePermissionDecision>>, } impl Default for WebViewAttributes { @@ -541,6 +551,7 @@ impl Default for WebViewAttributes { position: dpi::LogicalPosition::new(0, 0).into(), size: dpi::LogicalSize::new(200, 200).into(), }), + display_capture_decision_handler: None, } } } @@ -1269,6 +1280,24 @@ impl WebViewBuilderExtAndroid for WebViewBuilder<'_> { } } +#[cfg(target_os = "macos")] +pub trait WebViewBuilderExtMacOS { + /// TODO: document + fn with_display_capture_decision_handler(self, handler: F) -> Self + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static; +} + +impl WebViewBuilderExtMacOS for WebViewBuilder<'_> { + fn with_display_capture_decision_handler(mut self, handler: F) -> Self + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static, + { + self.attrs.display_capture_decision_handler = Some(Box::new(handler)); + self + } +} + #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -1766,3 +1795,41 @@ mod tests { } } } + +#[repr(isize)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WKMediaCaptureType { + Camera = 0, + Microphone, + CameraAndMicrophone, +} + +impl From for WKMediaCaptureType { + fn from(value: isize) -> Self { + match value { + 0 => WKMediaCaptureType::Camera, + 1 => WKMediaCaptureType::Microphone, + 2 => WKMediaCaptureType::CameraAndMicrophone, + _ => panic!("Invalid WKMediaCaptureType value"), + } + } +} + +#[repr(isize)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WKDisplayCapturePermissionDecision { + Deny = 0, + ScreenPrompt, + WindowPrompt, +} + +impl From for WKDisplayCapturePermissionDecision { + fn from(value: isize) -> Self { + match value { + 0 => WKDisplayCapturePermissionDecision::Deny, + 1 => WKDisplayCapturePermissionDecision::ScreenPrompt, + 2 => WKDisplayCapturePermissionDecision::WindowPrompt, + _ => panic!("Invalid WKDisplayCapturePermissionDecision value"), + } + } +} diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 9c133de4a..011b90763 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -2,9 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -// TODO: remove this once the issue is fixed -mod objc_type; - mod download; #[cfg(target_os = "macos")] mod drag_drop; @@ -65,9 +62,9 @@ use crate::{ set_download_delegate, }, navigation::{add_navigation_mathods, drop_navigation_methods, set_navigation_methods}, - objc_type::{WKDisplayCapturePermissionDecision, WKMediaCaptureType, WKPermissionDecision}, }, - Error, PageLoadEvent, Rect, RequestAsyncResponder, Result, WebContext, WebViewAttributes, RGBA, + Error, PageLoadEvent, Rect, RequestAsyncResponder, Result, WKDisplayCapturePermissionDecision, + WKMediaCaptureType, WebContext, WebViewAttributes, RGBA, }; use http::{ @@ -917,42 +914,19 @@ impl InnerWebView { _webview: id, _origin: id, _frame: id, - capture_type: isize, + _capture_type: isize, decision_handler: id, ) { unsafe { - println!("request_media_capture_permission"); - - dbg!(WKMediaCaptureType::from(capture_type)); - // https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc - let decision_handler = - decision_handler as *mut block::Block<(WKPermissionDecision,), c_void>; - (*decision_handler).call((WKPermissionDecision::Prompt,)); // should be 1 for mic/cam + let decision_handler = decision_handler as *mut block::Block<(u64,), c_void>; + (*decision_handler).call((1,)); // should be 1 for mic/cam } } - // extern "C" fn request_user_media_authorization( - // _this: &Object, - // _: Sel, - // _webview: id, - // _devices: id, - // _url: id, - // _main_frame_url: id, - // decision_handler: id, - // ) { - // unsafe { - // println!("request_user_media_authorization"); - - // let decision_handler = decision_handler as *mut block::Block<(BOOL,), c_void>; - // //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc - // (*decision_handler).call((BOOL::from(true),)); - // } - // } - // getDisplayMedia permission request handler extern "C" fn request_display_capture_permission( - _this: &Object, + this: &Object, _: Sel, _webview: id, _origin: id, @@ -963,32 +937,23 @@ impl InnerWebView { unsafe { println!("request_display_capture_permission"); - dbg!(WKMediaCaptureType::from(capture_type)); - let decision_handler = decision_handler as *mut block::Block<(WKDisplayCapturePermissionDecision,), c_void>; - //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc - (*decision_handler).call((WKDisplayCapturePermissionDecision::ScreenPrompt,)); + + let function = this.get_ivar::<*mut c_void>("display_capture_decision_handler"); + if !function.is_null() { + let function = &mut *(*function + as *mut Box< + dyn for<'s> Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision, + >); + let decision = (function)(WKMediaCaptureType::from(capture_type)); + (*decision_handler).call((decision,)); + } else { + (*decision_handler).call((WKDisplayCapturePermissionDecision::Deny,)); + } } } - // extern "C" fn query_permission_for_name( - // _this: &Object, - // _: Sel, - // _webview: id, - // _name: id, - // _origin: id, - // handler: id, - // ) { - // unsafe { - // println!("query_permission_for_name"); - - // let handler = handler as *mut block::Block<(NSInteger,), c_void>; - // //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc - // (*handler).call((1,)); // should be 1 for mic/cam - // } - // } - let ui_delegate = match ClassDecl::new("WebViewUIDelegate", class!(NSObject)) { Some(mut ctl) => { ctl.add_method( @@ -996,46 +961,19 @@ impl InnerWebView { run_file_upload_panel as extern "C" fn(&Object, Sel, id, id, id, id), ); - #[cfg(target_os = "macos")] - // TODO: remove this later - #[allow(clippy::match_single_binding)] - match operating_system_version() { - // TODO: Test on older versions - /* (14, 0, _) => { - // Do not suppress media request on 14.0: - // https://github.com/tauri-apps/wry/issues/1101 - } */ - _ => { - dbg!("register"); - - // This probably isn't working but we somehow need an equivalent to: - // @protocol WKUIDelegatePrivate - let p = objc::runtime::Protocol::get("WKUIDelegatePrivate"); - ctl.add_protocol(p.unwrap()); - - ctl.add_method( - sel!(webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:), - request_media_capture_permission as extern "C" fn(&Object, Sel, id, id, id, isize, id), - ); - - // I tried webView _webView and webView_ and none of them worked but this is probably not the root cause - // Also, they seem to do _something_ because i have to comment out all of them (and requestMediaCapturePermission above) for getDisplayMedia to work. - // If i leave requestMediaCapturePermission commented out but add those 3 methods below it does seem to do something because - // https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/ stops printing NotAllowed. - // ctl - // .add_method(sel!(_webView:requestUserMediaAuthorizationForDevices:url:mainFrameURL:decisionHandler:), - // request_user_media_authorization as extern "C" fn(&Object, Sel, id, id, id, id, id)); + ctl.add_method( + sel!(webView:requestMediaCapturePermissionForOrigin:initiatedByFrame:type:decisionHandler:), + request_media_capture_permission as extern "C" fn(&Object, Sel, id, id, id, isize, id), + ); - ctl.add_method( + // https://github.com/tauri-apps/wry/issues/1195 + #[cfg(target_os = "macos")] + if operating_system_version().0 >= 13 { + ctl.add_ivar::<*mut c_void>("display_capture_decision_handler"); + ctl.add_method( sel!(_webView:requestDisplayCapturePermissionForOrigin:initiatedByFrame:withSystemAudio:decisionHandler:), request_display_capture_permission as extern "C" fn(&Object, Sel, id, id, id, isize, id), ); - - // ctl.add_method( - // sel!(webView:queryPermissionName:forOrigin:completionHandler:), - // query_permission_for_name as extern "C" fn(&Object, Sel, id, id, id, id), - // ); - } } ctl.register() @@ -1043,6 +981,18 @@ impl InnerWebView { None => class!(WebViewUIDelegate), }; let ui_delegate: id = msg_send![ui_delegate, new]; + + #[cfg(target_os = "macos")] + if operating_system_version().0 >= 13 { + if let Some(handler) = attributes.display_capture_decision_handler { + let handler = Box::into_raw(Box::new(handler)); + (*ui_delegate).set_ivar( + "display_capture_decision_handler", + handler as *mut _ as *mut c_void, + ); + } + } + let _: () = msg_send![webview, setUIDelegate: ui_delegate]; // File drop handling diff --git a/src/wkwebview/objc_type.rs b/src/wkwebview/objc_type.rs deleted file mode 100644 index 3da859573..000000000 --- a/src/wkwebview/objc_type.rs +++ /dev/null @@ -1,88 +0,0 @@ -// TODO: This file is for convinience testing the media permission and should be removed - -#[repr(isize)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum WKMediaCaptureType { - Camera = 0, - Microphone, - CameraAndMicrophone, -} - -impl From for WKMediaCaptureType { - fn from(value: isize) -> Self { - match value { - 0 => WKMediaCaptureType::Camera, - 1 => WKMediaCaptureType::Microphone, - 2 => WKMediaCaptureType::CameraAndMicrophone, - _ => panic!("Invalid WKMediaCaptureType value"), - } - } -} - -impl From for isize { - fn from(value: WKMediaCaptureType) -> Self { - match value { - WKMediaCaptureType::Camera => 0, - WKMediaCaptureType::Microphone => 1, - WKMediaCaptureType::CameraAndMicrophone => 2, - } - } -} - -#[repr(isize)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum WKPermissionDecision { - Prompt = 0, - Grant, - Deny, -} - -impl From for WKPermissionDecision { - fn from(value: isize) -> Self { - match value { - 0 => WKPermissionDecision::Prompt, - 1 => WKPermissionDecision::Grant, - 2 => WKPermissionDecision::Deny, - _ => panic!("Invalid WKPermissionDecision value"), - } - } -} - -impl From for isize { - fn from(value: WKPermissionDecision) -> Self { - match value { - WKPermissionDecision::Prompt => 0, - WKPermissionDecision::Grant => 1, - WKPermissionDecision::Deny => 2, - } - } -} - -#[repr(isize)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum WKDisplayCapturePermissionDecision { - Deny = 0, - ScreenPrompt, - WindowPrompt, -} - -impl From for WKDisplayCapturePermissionDecision { - fn from(value: isize) -> Self { - match value { - 0 => WKDisplayCapturePermissionDecision::Deny, - 1 => WKDisplayCapturePermissionDecision::ScreenPrompt, - 2 => WKDisplayCapturePermissionDecision::WindowPrompt, - _ => panic!("Invalid WKDisplayCapturePermissionDecision value"), - } - } -} - -impl From for isize { - fn from(value: WKDisplayCapturePermissionDecision) -> Self { - match value { - WKDisplayCapturePermissionDecision::Deny => 0, - WKDisplayCapturePermissionDecision::ScreenPrompt => 1, - WKDisplayCapturePermissionDecision::WindowPrompt => 2, - } - } -} From b8d97fbcf0d08fe7dc60b70d6c749595c2e60fe1 Mon Sep 17 00:00:00 2001 From: Jason Tsai Date: Tue, 26 Mar 2024 22:52:53 +0800 Subject: [PATCH 5/8] move to a module --- .idea/.gitignore | 11 ++++ examples/simple.rs | 3 +- src/lib.rs | 59 ++++++------------ src/wkwebview/display_capture.rs | 100 +++++++++++++++++++++++++++++++ src/wkwebview/mod.rs | 60 ++++--------------- 5 files changed, 141 insertions(+), 92 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 src/wkwebview/display_capture.rs diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..2fba4c20e --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,11 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +/modules.xml +/vcs.xml +/wry.iml diff --git a/examples/simple.rs b/examples/simple.rs index 31092c9e9..30cd82400 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -38,8 +38,9 @@ fn main() -> wry::Result<()> { let _webview = builder .with_url("https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/") .with_display_capture_decision_handler(|capture_type| { + // TODO: remove this dbg!(capture_type); - wry::WKDisplayCapturePermissionDecision::ScreenPrompt + wry::WKDisplayCapturePermissionDecision::WindowPrompt }) .with_drag_drop_handler(|e| { match e { diff --git a/src/lib.rs b/src/lib.rs index 2da201952..0f471b753 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -234,6 +234,8 @@ pub(crate) mod wkwebview; use wkwebview::*; #[cfg(any(target_os = "macos", target_os = "ios"))] pub use wkwebview::{PrintMargin, PrintOptions}; +#[cfg(target_os = "macos")] +pub use wkwebview::{WKDisplayCapturePermissionDecision, WKMediaCaptureType}; #[cfg(target_os = "windows")] pub(crate) mod webview2; @@ -1062,13 +1064,13 @@ impl<'a> WebViewBuilder<'a> { } } -#[cfg(any(target_os = "macos", target_os = "ios",))] +#[cfg(target_os = "ios")] #[derive(Clone)] pub(crate) struct PlatformSpecificWebViewAttributes { data_store_identifier: Option<[u8; 16]>, } -#[cfg(any(target_os = "macos", target_os = "ios",))] +#[cfg(target_os = "ios")] impl Default for PlatformSpecificWebViewAttributes { fn default() -> Self { Self { @@ -1280,6 +1282,17 @@ impl WebViewBuilderExtAndroid for WebViewBuilder<'_> { } } +#[cfg(target_os = "macos")] +#[derive(Default)] +pub(crate) struct PlatformSpecificWebViewAttributes { + data_store_identifier: Option<[u8; 16]>, + /// A closure that make the permission decision for display capture (e.g. getDisplayMedia()) requests. + /// + /// Only available on macOS 13+. + pub display_capture_decision_handler: + Option WKDisplayCapturePermissionDecision>>, +} + #[cfg(target_os = "macos")] pub trait WebViewBuilderExtMacOS { /// TODO: document @@ -1288,12 +1301,13 @@ pub trait WebViewBuilderExtMacOS { F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static; } +#[cfg(target_os = "macos")] impl WebViewBuilderExtMacOS for WebViewBuilder<'_> { fn with_display_capture_decision_handler(mut self, handler: F) -> Self where F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static, { - self.attrs.display_capture_decision_handler = Some(Box::new(handler)); + self.platform_specific.display_capture_decision_handler = Some(Box::new(handler)); self } } @@ -1779,6 +1793,7 @@ pub enum PageLoadEvent { target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", + target_os = "ios", ))] #[derive(Default)] pub(crate) struct PlatformSpecificWebViewAttributes; @@ -1795,41 +1810,3 @@ mod tests { } } } - -#[repr(isize)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum WKMediaCaptureType { - Camera = 0, - Microphone, - CameraAndMicrophone, -} - -impl From for WKMediaCaptureType { - fn from(value: isize) -> Self { - match value { - 0 => WKMediaCaptureType::Camera, - 1 => WKMediaCaptureType::Microphone, - 2 => WKMediaCaptureType::CameraAndMicrophone, - _ => panic!("Invalid WKMediaCaptureType value"), - } - } -} - -#[repr(isize)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum WKDisplayCapturePermissionDecision { - Deny = 0, - ScreenPrompt, - WindowPrompt, -} - -impl From for WKDisplayCapturePermissionDecision { - fn from(value: isize) -> Self { - match value { - 0 => WKDisplayCapturePermissionDecision::Deny, - 1 => WKDisplayCapturePermissionDecision::ScreenPrompt, - 2 => WKDisplayCapturePermissionDecision::WindowPrompt, - _ => panic!("Invalid WKDisplayCapturePermissionDecision value"), - } - } -} diff --git a/src/wkwebview/display_capture.rs b/src/wkwebview/display_capture.rs new file mode 100644 index 000000000..babb97864 --- /dev/null +++ b/src/wkwebview/display_capture.rs @@ -0,0 +1,100 @@ +use crate::operating_system_version; +use cocoa::base::id; +use objc::{ + declare::ClassDecl, + runtime::{Object, Sel}, +}; +use std::ffi::c_void; + +#[repr(isize)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WKMediaCaptureType { + Camera = 0, + Microphone, + CameraAndMicrophone, +} + +impl From for WKMediaCaptureType { + fn from(value: isize) -> Self { + match value { + 0 => WKMediaCaptureType::Camera, + 1 => WKMediaCaptureType::Microphone, + 2 => WKMediaCaptureType::CameraAndMicrophone, + _ => panic!("Invalid WKMediaCaptureType value"), + } + } +} + +#[repr(isize)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum WKDisplayCapturePermissionDecision { + Deny = 0, + ScreenPrompt, + WindowPrompt, +} + +impl From for WKDisplayCapturePermissionDecision { + fn from(value: isize) -> Self { + match value { + 0 => WKDisplayCapturePermissionDecision::Deny, + 1 => WKDisplayCapturePermissionDecision::ScreenPrompt, + 2 => WKDisplayCapturePermissionDecision::WindowPrompt, + _ => panic!("Invalid WKDisplayCapturePermissionDecision value"), + } + } +} + +pub(crate) fn declare_decision_handler(ctl: &mut ClassDecl) { + #[cfg(target_os = "macos")] + if operating_system_version().0 >= 13 { + unsafe { + ctl.add_ivar::<*mut c_void>("display_capture_decision_handler"); + ctl.add_method(sel!(_webView:requestDisplayCapturePermissionForOrigin:initiatedByFrame:withSystemAudio:decisionHandler:), + request_display_capture_permission as extern "C" fn(&Object, Sel, id, id, id, isize, id),); + } + } +} + +pub(crate) fn set_decision_handler( + delegate: id, + handler: Option WKDisplayCapturePermissionDecision + 'static>>, +) { + #[cfg(target_os = "macos")] + if operating_system_version().0 >= 13 { + unsafe { + if let Some(handler) = handler { + let handler = Box::into_raw(Box::new(handler)); + (*delegate).set_ivar( + "display_capture_decision_handler", + handler as *mut _ as *mut c_void, + ); + } + } + } +} + +extern "C" fn request_display_capture_permission( + this: &Object, + _: Sel, + _webview: id, + _origin: id, + _frame: id, + capture_type: isize, + decision_handler: id, +) { + unsafe { + let decision_handler = + decision_handler as *mut block::Block<(WKDisplayCapturePermissionDecision,), c_void>; + + let function = this.get_ivar::<*mut c_void>("display_capture_decision_handler"); + if !function.is_null() { + let function = *function + as *mut Box Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision>; + + let decision = (*function)(WKMediaCaptureType::from(capture_type)); + (*decision_handler).call((decision,)); + } else { + (*decision_handler).call((WKDisplayCapturePermissionDecision::Deny,)); + } + } +} diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 011b90763..3edc3264f 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +#[cfg(target_os = "macos")] +mod display_capture; mod download; #[cfg(target_os = "macos")] mod drag_drop; @@ -55,6 +57,9 @@ use crate::{ }, }; +#[cfg(target_os = "macos")] +pub use display_capture::{WKDisplayCapturePermissionDecision, WKMediaCaptureType}; + use crate::{ wkwebview::{ download::{ @@ -63,8 +68,7 @@ use crate::{ }, navigation::{add_navigation_mathods, drop_navigation_methods, set_navigation_methods}, }, - Error, PageLoadEvent, Rect, RequestAsyncResponder, Result, WKDisplayCapturePermissionDecision, - WKMediaCaptureType, WebContext, WebViewAttributes, RGBA, + Error, PageLoadEvent, Rect, RequestAsyncResponder, Result, WebContext, WebViewAttributes, RGBA, }; use http::{ @@ -924,36 +928,6 @@ impl InnerWebView { } } - // getDisplayMedia permission request handler - extern "C" fn request_display_capture_permission( - this: &Object, - _: Sel, - _webview: id, - _origin: id, - _frame: id, - capture_type: isize, - decision_handler: id, - ) { - unsafe { - println!("request_display_capture_permission"); - - let decision_handler = - decision_handler as *mut block::Block<(WKDisplayCapturePermissionDecision,), c_void>; - - let function = this.get_ivar::<*mut c_void>("display_capture_decision_handler"); - if !function.is_null() { - let function = &mut *(*function - as *mut Box< - dyn for<'s> Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision, - >); - let decision = (function)(WKMediaCaptureType::from(capture_type)); - (*decision_handler).call((decision,)); - } else { - (*decision_handler).call((WKDisplayCapturePermissionDecision::Deny,)); - } - } - } - let ui_delegate = match ClassDecl::new("WebViewUIDelegate", class!(NSObject)) { Some(mut ctl) => { ctl.add_method( @@ -966,34 +940,20 @@ impl InnerWebView { request_media_capture_permission as extern "C" fn(&Object, Sel, id, id, id, isize, id), ); + // Workaround for getDisplayMedia() // https://github.com/tauri-apps/wry/issues/1195 #[cfg(target_os = "macos")] - if operating_system_version().0 >= 13 { - ctl.add_ivar::<*mut c_void>("display_capture_decision_handler"); - ctl.add_method( - sel!(_webView:requestDisplayCapturePermissionForOrigin:initiatedByFrame:withSystemAudio:decisionHandler:), - request_display_capture_permission as extern "C" fn(&Object, Sel, id, id, id, isize, id), - ); - } + display_capture::declare_decision_handler(&mut ctl); ctl.register() } None => class!(WebViewUIDelegate), }; let ui_delegate: id = msg_send![ui_delegate, new]; + let _: () = msg_send![webview, setUIDelegate: ui_delegate]; #[cfg(target_os = "macos")] - if operating_system_version().0 >= 13 { - if let Some(handler) = attributes.display_capture_decision_handler { - let handler = Box::into_raw(Box::new(handler)); - (*ui_delegate).set_ivar( - "display_capture_decision_handler", - handler as *mut _ as *mut c_void, - ); - } - } - - let _: () = msg_send![webview, setUIDelegate: ui_delegate]; + display_capture::set_decision_handler(ui_delegate, pl_attrs.display_capture_decision_handler); // File drop handling #[cfg(target_os = "macos")] From 24ece0092a02fa98ebf025838b66c76ae0369fa5 Mon Sep 17 00:00:00 2001 From: Jason Tsai Date: Wed, 27 Mar 2024 10:26:16 +0800 Subject: [PATCH 6/8] organize example --- examples/simple.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 30cd82400..eef6078e6 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -8,20 +8,24 @@ use tao::{ window::WindowBuilder, }; use wry::WebViewBuilder; -use wry::WebViewBuilderExtMacOS; fn main() -> wry::Result<()> { let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - #[cfg(any( - target_os = "windows", - target_os = "macos", - target_os = "ios", - target_os = "android" - ))] + #[cfg(any(target_os = "windows", target_os = "ios", target_os = "android"))] let builder = WebViewBuilder::new(&window); + // TODO: remove this + #[cfg(target_os = "macos")] + let builder = { + use wry::WebViewBuilderExtMacOS; + WebViewBuilder::new(&window).with_display_capture_decision_handler(|capture_type| { + dbg!(capture_type); + wry::WKDisplayCapturePermissionDecision::WindowPrompt + }) + }; + #[cfg(not(any( target_os = "windows", target_os = "macos", @@ -37,11 +41,6 @@ fn main() -> wry::Result<()> { let _webview = builder .with_url("https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/") - .with_display_capture_decision_handler(|capture_type| { - // TODO: remove this - dbg!(capture_type); - wry::WKDisplayCapturePermissionDecision::WindowPrompt - }) .with_drag_drop_handler(|e| { match e { wry::DragDropEvent::Enter { paths, position } => { From f0aec84a0c2749a5da6ead18a3bacdfd52016937 Mon Sep 17 00:00:00 2001 From: Jason Tsai Date: Mon, 8 Apr 2024 11:01:44 +0800 Subject: [PATCH 7/8] add decision handler setter --- examples/simple.rs | 9 +++++++-- src/lib.rs | 11 +++++++++++ src/wkwebview/display_capture.rs | 19 +++++++++++++++++-- src/wkwebview/mod.rs | 13 ++++++++++++- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index eef6078e6..c20e4551b 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -7,7 +7,7 @@ use tao::{ event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; -use wry::WebViewBuilder; +use wry::{WebViewBuilder, WebViewExtMacOS}; fn main() -> wry::Result<()> { let event_loop = EventLoop::new(); @@ -40,7 +40,7 @@ fn main() -> wry::Result<()> { }; let _webview = builder - .with_url("https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/") + .with_url("https://webrtc.github.io/samples/src/content/getusermedia/getdisplaymedia/") // TODO: revert .with_drag_drop_handler(|e| { match e { wry::DragDropEvent::Enter { paths, position } => { @@ -58,6 +58,11 @@ fn main() -> wry::Result<()> { }) .build()?; + // TODO: remove me + _webview.set_display_capture_decision_handler(|_capture_type| { + wry::WKDisplayCapturePermissionDecision::ScreenPrompt + }); + event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/src/lib.rs b/src/lib.rs index 0f471b753..848b1d69c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1702,6 +1702,10 @@ pub trait WebViewExtMacOS { fn reparent(&self, window: cocoa::base::id) -> Result<()>; // Prints with extra options fn print_with_options(&self, options: &PrintOptions) -> Result<()>; + /// Set display capture decision handler to decide if incoming display capture request is allowed and its target. + fn set_display_capture_decision_handler(&self, handler: F) + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static; } #[cfg(target_os = "macos")] @@ -1728,6 +1732,13 @@ impl WebViewExtMacOS for WebView { fn print_with_options(&self, options: &PrintOptions) -> Result<()> { self.webview.print_with_options(options) } + + fn set_display_capture_decision_handler(&self, handler: F) + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static, + { + self.webview.set_display_capture_decision_handler(handler); + } } /// Additional methods on `WebView` that are specific to iOS. diff --git a/src/wkwebview/display_capture.rs b/src/wkwebview/display_capture.rs index babb97864..6696a1b2a 100644 --- a/src/wkwebview/display_capture.rs +++ b/src/wkwebview/display_capture.rs @@ -56,15 +56,18 @@ pub(crate) fn declare_decision_handler(ctl: &mut ClassDecl) { } pub(crate) fn set_decision_handler( - delegate: id, + webview: id, handler: Option WKDisplayCapturePermissionDecision + 'static>>, ) { #[cfg(target_os = "macos")] if operating_system_version().0 >= 13 { unsafe { if let Some(handler) = handler { + drop_decision_hanlder(webview); + + let ui_delegate: id = msg_send![webview, UIDelegate]; let handler = Box::into_raw(Box::new(handler)); - (*delegate).set_ivar( + (*ui_delegate).set_ivar( "display_capture_decision_handler", handler as *mut _ as *mut c_void, ); @@ -73,6 +76,18 @@ pub(crate) fn set_decision_handler( } } +pub(crate) fn drop_decision_hanlder(webview: *mut Object) { + unsafe { + let ui_delegate: id = msg_send![webview, UIDelegate]; + let function = (*ui_delegate).get_ivar::<*mut c_void>("display_capture_decision_handler"); + if !function.is_null() { + let function = *function + as *mut Box Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision>; + drop(Box::from_raw(function)); + } + } +} + extern "C" fn request_display_capture_permission( this: &Object, _: Sel, diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 3edc3264f..0d48258aa 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -953,7 +953,7 @@ impl InnerWebView { let _: () = msg_send![webview, setUIDelegate: ui_delegate]; #[cfg(target_os = "macos")] - display_capture::set_decision_handler(ui_delegate, pl_attrs.display_capture_decision_handler); + display_capture::set_decision_handler(webview, pl_attrs.display_capture_decision_handler); // File drop handling #[cfg(target_os = "macos")] @@ -1330,6 +1330,14 @@ r#"Object.defineProperty(window, 'ipc', { Ok(()) } + + #[cfg(target_os = "macos")] + pub fn set_display_capture_decision_handler(&self, handler: F) + where + F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static, + { + display_capture::set_decision_handler(self.webview, Some(Box::new(handler))); + } } pub fn url_from_webview(webview: id) -> Result { @@ -1416,6 +1424,9 @@ impl Drop for InnerWebView { } } + #[cfg(target_os = "macos")] + display_capture::drop_decision_hanlder(self.webview); + // Remove webview from window's NSView before dropping. let () = msg_send![self.webview, removeFromSuperview]; let _: Id<_> = Id::from_retained_ptr(self.webview); From 8eae2bcaf3b3ce1845414592e7f78e1b8f0416f3 Mon Sep 17 00:00:00 2001 From: Jason Tsai Date: Mon, 8 Apr 2024 14:46:06 +0800 Subject: [PATCH 8/8] fix simple example --- examples/simple.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index c20e4551b..3315867fc 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -7,7 +7,7 @@ use tao::{ event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; -use wry::{WebViewBuilder, WebViewExtMacOS}; +use wry::WebViewBuilder; fn main() -> wry::Result<()> { let event_loop = EventLoop::new(); @@ -59,9 +59,13 @@ fn main() -> wry::Result<()> { .build()?; // TODO: remove me - _webview.set_display_capture_decision_handler(|_capture_type| { - wry::WKDisplayCapturePermissionDecision::ScreenPrompt - }); + #[cfg(target_os = "macos")] + { + use wry::WebViewExtMacOS; + _webview.set_display_capture_decision_handler(|_capture_type| { + wry::WKDisplayCapturePermissionDecision::ScreenPrompt + }); + } event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait;