Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/fix-unsuppress-media-request-for-mac.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wry": patch
---

On macOS 14.0, unsuppressing the media permission request.
11 changes: 11 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 21 additions & 7 deletions examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ 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",
Expand All @@ -35,7 +40,7 @@ fn main() -> wry::Result<()> {
};

let _webview = builder
.with_url("http://tauri.app")
.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 } => {
Expand All @@ -53,6 +58,15 @@ fn main() -> wry::Result<()> {
})
.build()?;

// TODO: remove me
#[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;

Expand Down
59 changes: 57 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -503,6 +505,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<Rect>,

/// TODO:
/// https://github.com/tauri-apps/wry/issues/1195
/// for macOS 13+ only
#[cfg(target_os = "macos")]
pub display_capture_decision_handler:
Option<Box<dyn Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision>>,
#[cfg(not(target_os = "macos"))]
display_capture_decision_handler:
Option<Box<dyn Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision>>,
}

impl Default for WebViewAttributes {
Expand Down Expand Up @@ -541,6 +553,7 @@ impl Default for WebViewAttributes {
position: dpi::LogicalPosition::new(0, 0).into(),
size: dpi::LogicalSize::new(200, 200).into(),
}),
display_capture_decision_handler: None,
}
}
}
Expand Down Expand Up @@ -1051,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 {
Expand Down Expand Up @@ -1269,6 +1282,36 @@ 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<Box<dyn Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision>>,
}

#[cfg(target_os = "macos")]
pub trait WebViewBuilderExtMacOS {
/// TODO: document
fn with_display_capture_decision_handler<F>(self, handler: F) -> Self
where
F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static;
}

#[cfg(target_os = "macos")]
impl WebViewBuilderExtMacOS for WebViewBuilder<'_> {
fn with_display_capture_decision_handler<F>(mut self, handler: F) -> Self
where
F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static,
{
self.platform_specific.display_capture_decision_handler = Some(Box::new(handler));
self
}
}

#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
Expand Down Expand Up @@ -1659,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<F>(&self, handler: F)
where
F: Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision + 'static;
}

#[cfg(target_os = "macos")]
Expand All @@ -1685,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<F>(&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.
Expand Down Expand Up @@ -1750,6 +1804,7 @@ pub enum PageLoadEvent {
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "ios",
))]
#[derive(Default)]
pub(crate) struct PlatformSpecificWebViewAttributes;
Expand Down
115 changes: 115 additions & 0 deletions src/wkwebview/display_capture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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<isize> 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<isize> 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(
webview: id,
handler: Option<Box<dyn Fn(WKMediaCaptureType) -> 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));
(*ui_delegate).set_ivar(
"display_capture_decision_handler",
handler as *mut _ as *mut c_void,
);
}
}
}
}

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<dyn for<'s> Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision>;
drop(Box::from_raw(function));
}
}
}

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<dyn for<'s> Fn(WKMediaCaptureType) -> WKDisplayCapturePermissionDecision>;

let decision = (*function)(WKMediaCaptureType::from(capture_type));
(*decision_handler).call((decision,));
} else {
(*decision_handler).call((WKDisplayCapturePermissionDecision::Deny,));
}
}
}
Loading