2024-07-23 20:33:10 +02:00
|
|
|
use std::cell::{OnceCell, Ref, RefCell};
|
2024-08-04 16:49:19 +02:00
|
|
|
use std::cmp::Ordering;
|
2024-07-23 19:59:37 +02:00
|
|
|
use std::fmt::{self, Debug, Formatter};
|
2024-07-23 20:33:10 +02:00
|
|
|
use std::future::Future;
|
2024-08-04 16:49:19 +02:00
|
|
|
use std::hash::{Hash, Hasher};
|
2024-07-23 20:33:10 +02:00
|
|
|
use std::iter::{self, Once};
|
|
|
|
|
use std::mem;
|
2024-07-23 19:59:37 +02:00
|
|
|
use std::num::{NonZeroU16, NonZeroU32};
|
2024-08-04 16:49:19 +02:00
|
|
|
use std::ops::{Deref, DerefMut};
|
2024-07-23 20:33:10 +02:00
|
|
|
use std::pin::Pin;
|
2024-08-04 16:49:19 +02:00
|
|
|
use std::rc::{Rc, Weak};
|
|
|
|
|
use std::sync::OnceLock;
|
2024-07-23 20:33:10 +02:00
|
|
|
use std::task::{ready, Context, Poll};
|
2023-08-26 18:56:44 +02:00
|
|
|
|
2024-07-23 20:33:10 +02:00
|
|
|
use dpi::LogicalSize;
|
|
|
|
|
use js_sys::{Object, Promise};
|
|
|
|
|
use tracing::error;
|
|
|
|
|
use wasm_bindgen::closure::Closure;
|
|
|
|
|
use wasm_bindgen::prelude::wasm_bindgen;
|
|
|
|
|
use wasm_bindgen::{JsCast, JsValue};
|
|
|
|
|
use wasm_bindgen_futures::JsFuture;
|
|
|
|
|
use web_sys::{
|
|
|
|
|
console, DomException, Navigator, OrientationLockType, OrientationType, PermissionState,
|
|
|
|
|
PermissionStatus, ScreenOrientation, Window,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use super::event_loop::runner::WeakShared;
|
|
|
|
|
use super::main_thread::MainThreadMarker;
|
|
|
|
|
use super::r#async::{Dispatcher, Notified, Notifier};
|
|
|
|
|
use super::web_sys::{Engine, EventListenerHandle};
|
2019-06-25 03:15:34 +02:00
|
|
|
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
2024-07-23 20:33:10 +02:00
|
|
|
use crate::platform::web::{
|
|
|
|
|
MonitorPermissionError, Orientation, OrientationData, OrientationLock, OrientationLockError,
|
|
|
|
|
};
|
2019-06-25 03:15:34 +02:00
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
#[derive(Clone, Eq)]
|
2024-08-04 16:49:19 +02:00
|
|
|
pub struct MonitorHandle {
|
|
|
|
|
/// [`None`] means [`web_sys::Screen`], which is always the same.
|
|
|
|
|
id: Option<u64>,
|
|
|
|
|
inner: Dispatcher<Inner>,
|
|
|
|
|
}
|
2019-06-25 03:15:34 +02:00
|
|
|
|
2022-03-18 14:09:39 +01:00
|
|
|
impl MonitorHandle {
|
2024-07-23 20:33:10 +02:00
|
|
|
fn new(main_thread: MainThreadMarker, inner: Inner) -> Self {
|
2024-08-04 16:49:19 +02:00
|
|
|
let id = if let Screen::Detailed { id, .. } = inner.screen { Some(id) } else { None };
|
|
|
|
|
Self { id, inner: Dispatcher::new(main_thread, inner).0 }
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
2020-01-03 14:52:27 -05:00
|
|
|
pub fn scale_factor(&self) -> f64 {
|
2024-07-23 19:59:37 +02:00
|
|
|
self.inner.queue(|inner| inner.scale_factor())
|
2019-06-25 03:15:34 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
pub fn position(&self) -> Option<PhysicalPosition<i32>> {
|
|
|
|
|
self.inner.queue(|inner| inner.position())
|
2019-06-25 03:15:34 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-11 00:54:54 +02:00
|
|
|
pub fn name(&self) -> Option<String> {
|
2024-07-23 19:59:37 +02:00
|
|
|
self.inner.queue(|inner| inner.name())
|
2019-06-25 03:15:34 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-21 00:01:43 +02:00
|
|
|
pub fn current_video_mode(&self) -> Option<VideoModeHandle> {
|
|
|
|
|
Some(VideoModeHandle(self.clone()))
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 20:33:10 +02:00
|
|
|
pub fn video_modes(&self) -> Once<VideoModeHandle> {
|
|
|
|
|
iter::once(VideoModeHandle(self.clone()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn orientation(&self) -> OrientationData {
|
2024-07-23 19:59:37 +02:00
|
|
|
self.inner.queue(|inner| inner.orientation())
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn request_lock(&self, orientation_lock: OrientationLock) -> OrientationLockFuture {
|
|
|
|
|
// Short-circuit without blocking.
|
2024-08-04 16:49:19 +02:00
|
|
|
if let Some(support) = has_previous_lock_support() {
|
2024-07-23 20:33:10 +02:00
|
|
|
if !support {
|
|
|
|
|
return OrientationLockFuture::Ready(Some(Err(OrientationLockError::Unsupported)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
self.inner.queue(|inner| {
|
|
|
|
|
if !inner.has_lock_support() {
|
2024-07-23 20:33:10 +02:00
|
|
|
return OrientationLockFuture::Ready(Some(Err(OrientationLockError::Unsupported)));
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
let future =
|
2024-07-23 19:59:37 +02:00
|
|
|
JsFuture::from(inner.orientation_raw().lock(orientation_lock.to_js()).unwrap());
|
2024-07-23 20:33:10 +02:00
|
|
|
let notifier = Notifier::new();
|
|
|
|
|
let notified = notifier.notified();
|
|
|
|
|
|
|
|
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
|
|
|
notifier.notify(future.await.map(|_| ()).map_err(OrientationLockError::from_js));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
OrientationLockFuture::Future(notified)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn unlock(&self) -> Result<(), OrientationLockError> {
|
|
|
|
|
// Short-circuit without blocking.
|
2024-08-04 16:49:19 +02:00
|
|
|
if let Some(support) = has_previous_lock_support() {
|
2024-07-23 20:33:10 +02:00
|
|
|
if !support {
|
|
|
|
|
return Err(OrientationLockError::Unsupported);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
self.inner.queue(|inner| {
|
|
|
|
|
if !inner.has_lock_support() {
|
2024-07-23 20:33:10 +02:00
|
|
|
return Err(OrientationLockError::Unsupported);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
inner.orientation_raw().unlock().map_err(OrientationLockError::from_js)
|
2024-07-23 20:33:10 +02:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_internal(&self) -> Option<bool> {
|
2024-07-23 19:59:37 +02:00
|
|
|
self.inner.queue(|inner| inner.is_internal())
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_detailed(&self) -> bool {
|
2024-07-23 19:59:37 +02:00
|
|
|
self.inner.queue(|inner| inner.is_detailed())
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn detailed(
|
|
|
|
|
&self,
|
|
|
|
|
main_thread: MainThreadMarker,
|
|
|
|
|
) -> Option<Ref<'_, ScreenDetailed>> {
|
2024-08-04 16:49:19 +02:00
|
|
|
let inner = self.inner.value(main_thread);
|
2024-07-23 20:33:10 +02:00
|
|
|
match &inner.screen {
|
|
|
|
|
Screen::Screen(_) => None,
|
2024-08-04 16:49:19 +02:00
|
|
|
Screen::Detailed { .. } => Some(Ref::map(inner, |inner| {
|
|
|
|
|
if let Screen::Detailed { screen, .. } = &inner.screen {
|
|
|
|
|
screen.deref()
|
2024-07-23 20:33:10 +02:00
|
|
|
} else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
}
|
|
|
|
|
})),
|
|
|
|
|
}
|
2019-06-25 03:15:34 +02:00
|
|
|
}
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
impl Debug for MonitorHandle {
|
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
|
|
let (name, position, scale_factor, orientation, is_internal, is_detailed) =
|
|
|
|
|
self.inner.queue(|this| {
|
|
|
|
|
(
|
|
|
|
|
this.name(),
|
|
|
|
|
this.position(),
|
|
|
|
|
this.scale_factor(),
|
|
|
|
|
this.orientation(),
|
|
|
|
|
this.is_internal(),
|
|
|
|
|
this.is_detailed(),
|
|
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
f.debug_struct("MonitorHandle")
|
|
|
|
|
.field("name", &name)
|
|
|
|
|
.field("position", &position)
|
|
|
|
|
.field("scale_factor", &scale_factor)
|
|
|
|
|
.field("orientation", &orientation)
|
|
|
|
|
.field("is_internal", &is_internal)
|
|
|
|
|
.field("is_detailed", &is_detailed)
|
|
|
|
|
.finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
impl Hash for MonitorHandle {
|
|
|
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
|
|
|
self.id.hash(state)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Ord for MonitorHandle {
|
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
|
self.id.cmp(&other.id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialEq for MonitorHandle {
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
self.id.eq(&other.id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialOrd for MonitorHandle {
|
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
|
|
|
Some(self.cmp(other))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 20:33:10 +02:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum OrientationLockFuture {
|
|
|
|
|
Future(Notified<Result<(), OrientationLockError>>),
|
|
|
|
|
Ready(Option<Result<(), OrientationLockError>>),
|
|
|
|
|
}
|
2019-07-11 00:54:54 +02:00
|
|
|
|
2024-07-23 20:33:10 +02:00
|
|
|
impl Future for OrientationLockFuture {
|
|
|
|
|
type Output = Result<(), OrientationLockError>;
|
|
|
|
|
|
|
|
|
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
|
|
|
match self.get_mut() {
|
|
|
|
|
Self::Future(notified) => Pin::new(notified).poll(cx).map(Option::unwrap),
|
|
|
|
|
Self::Ready(result) => {
|
|
|
|
|
Poll::Ready(result.take().expect("`OrientationLockFuture` polled after completion"))
|
|
|
|
|
},
|
|
|
|
|
}
|
2019-07-11 00:54:54 +02:00
|
|
|
}
|
2019-06-25 03:15:34 +02:00
|
|
|
}
|
2019-09-24 19:39:13 -04:00
|
|
|
|
2024-07-23 20:33:10 +02:00
|
|
|
impl OrientationLock {
|
|
|
|
|
fn to_js(self) -> OrientationLockType {
|
|
|
|
|
match self {
|
|
|
|
|
OrientationLock::Any => OrientationLockType::Any,
|
|
|
|
|
OrientationLock::Natural => OrientationLockType::Natural,
|
|
|
|
|
OrientationLock::Landscape { flipped: None } => OrientationLockType::Landscape,
|
|
|
|
|
OrientationLock::Landscape { flipped: Some(flipped) } => {
|
|
|
|
|
if flipped {
|
|
|
|
|
OrientationLockType::LandscapeSecondary
|
|
|
|
|
} else {
|
|
|
|
|
OrientationLockType::LandscapePrimary
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
OrientationLock::Portrait { flipped: None } => OrientationLockType::Portrait,
|
|
|
|
|
OrientationLock::Portrait { flipped: Some(flipped) } => {
|
|
|
|
|
if flipped {
|
|
|
|
|
OrientationLockType::PortraitSecondary
|
|
|
|
|
} else {
|
|
|
|
|
OrientationLockType::PortraitPrimary
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl OrientationLockError {
|
|
|
|
|
fn from_js(error: JsValue) -> Self {
|
|
|
|
|
debug_assert!(error.has_type::<DomException>());
|
|
|
|
|
let error: DomException = error.unchecked_into();
|
|
|
|
|
|
|
|
|
|
if let DomException::ABORT_ERR = error.code() {
|
|
|
|
|
OrientationLockError::Busy
|
|
|
|
|
} else {
|
|
|
|
|
OrientationLockError::Unsupported
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
#[derive(Clone, Eq, Hash, PartialEq)]
|
|
|
|
|
pub struct VideoModeHandle(MonitorHandle);
|
2019-09-24 19:39:13 -04:00
|
|
|
|
2023-12-26 22:12:33 +01:00
|
|
|
impl VideoModeHandle {
|
2020-01-04 01:33:07 -05:00
|
|
|
pub fn size(&self) -> PhysicalSize<u32> {
|
2024-07-23 19:59:37 +02:00
|
|
|
self.0.inner.queue(|inner| inner.size())
|
2019-09-24 19:39:13 -04:00
|
|
|
}
|
|
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
pub fn bit_depth(&self) -> Option<NonZeroU16> {
|
|
|
|
|
self.0.inner.queue(|inner| inner.bit_depth())
|
2019-09-24 19:39:13 -04:00
|
|
|
}
|
|
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
pub fn refresh_rate_millihertz(&self) -> Option<NonZeroU32> {
|
2024-07-21 00:40:57 +02:00
|
|
|
None
|
2019-09-24 19:39:13 -04:00
|
|
|
}
|
|
|
|
|
|
2022-09-21 10:04:28 +02:00
|
|
|
pub fn monitor(&self) -> MonitorHandle {
|
2024-07-23 20:33:10 +02:00
|
|
|
self.0.clone()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
impl Debug for VideoModeHandle {
|
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
|
|
let (size, bit_depth) = self.0.inner.queue(|this| (this.size(), this.bit_depth()));
|
|
|
|
|
|
|
|
|
|
f.debug_struct("MonitorHandle").field("size", &size).field("bit_depth", &bit_depth).finish()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 20:33:10 +02:00
|
|
|
struct Inner {
|
|
|
|
|
window: WindowExt,
|
|
|
|
|
engine: Option<Engine>,
|
|
|
|
|
screen: Screen,
|
|
|
|
|
orientation: OnceCell<ScreenOrientationExt>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Inner {
|
|
|
|
|
fn new(window: WindowExt, engine: Option<Engine>, screen: Screen) -> Self {
|
|
|
|
|
Self { window, engine, screen, orientation: OnceCell::new() }
|
|
|
|
|
}
|
2024-08-04 16:49:19 +02:00
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
fn scale_factor(&self) -> f64 {
|
|
|
|
|
match &self.screen {
|
|
|
|
|
Screen::Screen(_) => 0.,
|
|
|
|
|
Screen::Detailed { screen, .. } => screen.device_pixel_ratio(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn position(&self) -> Option<PhysicalPosition<i32>> {
|
|
|
|
|
if let Screen::Detailed { screen, .. } = &self.screen {
|
|
|
|
|
Some(PhysicalPosition::new(screen.left(), screen.top()))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn name(&self) -> Option<String> {
|
|
|
|
|
if let Screen::Detailed { screen, .. } = &self.screen {
|
|
|
|
|
Some(screen.label())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn orientation_raw(&self) -> &ScreenOrientationExt {
|
2024-08-04 16:49:19 +02:00
|
|
|
self.orientation.get_or_init(|| self.screen.orientation().unchecked_into())
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 19:59:37 +02:00
|
|
|
fn orientation(&self) -> OrientationData {
|
|
|
|
|
let orientation = self.orientation_raw();
|
|
|
|
|
|
|
|
|
|
let angle = orientation.angle().unwrap();
|
|
|
|
|
|
|
|
|
|
match orientation.type_().unwrap() {
|
|
|
|
|
OrientationType::LandscapePrimary => OrientationData {
|
|
|
|
|
orientation: Orientation::Landscape,
|
|
|
|
|
flipped: false,
|
2024-08-13 22:13:12 +02:00
|
|
|
natural: if angle == 0 { Orientation::Landscape } else { Orientation::Portrait },
|
2024-07-23 19:59:37 +02:00
|
|
|
},
|
|
|
|
|
OrientationType::LandscapeSecondary => OrientationData {
|
|
|
|
|
orientation: Orientation::Landscape,
|
|
|
|
|
flipped: true,
|
2024-08-13 22:13:12 +02:00
|
|
|
natural: if angle == 180 { Orientation::Landscape } else { Orientation::Portrait },
|
2024-07-23 19:59:37 +02:00
|
|
|
},
|
|
|
|
|
OrientationType::PortraitPrimary => OrientationData {
|
|
|
|
|
orientation: Orientation::Portrait,
|
|
|
|
|
flipped: false,
|
2024-08-13 22:13:12 +02:00
|
|
|
natural: if angle == 0 { Orientation::Portrait } else { Orientation::Landscape },
|
2024-07-23 19:59:37 +02:00
|
|
|
},
|
|
|
|
|
OrientationType::PortraitSecondary => OrientationData {
|
|
|
|
|
orientation: Orientation::Portrait,
|
|
|
|
|
flipped: true,
|
2024-08-13 22:13:12 +02:00
|
|
|
natural: if angle == 180 { Orientation::Portrait } else { Orientation::Landscape },
|
2024-07-23 19:59:37 +02:00
|
|
|
},
|
|
|
|
|
_ => {
|
|
|
|
|
unreachable!("found unrecognized orientation: {}", orientation.type_string())
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_internal(&self) -> Option<bool> {
|
|
|
|
|
if let Screen::Detailed { screen, .. } = &self.screen {
|
|
|
|
|
Some(screen.is_internal())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_detailed(&self) -> bool {
|
|
|
|
|
matches!(self.screen, Screen::Detailed { .. })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn size(&self) -> PhysicalSize<u32> {
|
|
|
|
|
let width = self.screen.width().unwrap();
|
|
|
|
|
let height = self.screen.height().unwrap();
|
|
|
|
|
|
|
|
|
|
if let Some(Engine::Chromium) = self.engine {
|
|
|
|
|
PhysicalSize::new(width, height).cast()
|
|
|
|
|
} else {
|
|
|
|
|
LogicalSize::new(width, height).to_physical(super::web_sys::scale_factor(&self.window))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn bit_depth(&self) -> Option<NonZeroU16> {
|
|
|
|
|
NonZeroU16::new(self.screen.color_depth().unwrap().try_into().unwrap())
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
fn has_lock_support(&self) -> bool {
|
2024-07-23 19:59:37 +02:00
|
|
|
*HAS_LOCK_SUPPORT.get_or_init(|| !self.orientation_raw().has_lock().is_undefined())
|
2024-08-04 16:49:19 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for Inner {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
if let Screen::Detailed { runner, id, screen } = &self.screen {
|
|
|
|
|
// If this is the last screen with its ID, clean it up in the `MonitorHandler`.
|
|
|
|
|
if Rc::strong_count(screen) == 1 {
|
|
|
|
|
if let Some(runner) = runner.upgrade() {
|
|
|
|
|
let mut state = runner.monitor().state.borrow_mut();
|
|
|
|
|
let State::Detailed(detailed) = state.deref_mut() else {
|
|
|
|
|
unreachable!("found a `ScreenDetailed` without being in `State::Detailed`")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
detailed.screens.retain(|(id_internal, _)| *id_internal != *id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Screen {
|
|
|
|
|
Screen(ScreenExt),
|
2024-08-04 16:49:19 +02:00
|
|
|
Detailed { runner: WeakShared, id: u64, screen: Rc<ScreenDetailed> },
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Deref for Screen {
|
|
|
|
|
type Target = ScreenExt;
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
match self {
|
|
|
|
|
Screen::Screen(screen) => screen,
|
2024-08-04 16:49:19 +02:00
|
|
|
Screen::Detailed { screen, .. } => screen,
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct MonitorHandler {
|
2024-08-04 16:49:19 +02:00
|
|
|
runner: WeakShared,
|
2024-07-23 20:33:10 +02:00
|
|
|
state: RefCell<State>,
|
|
|
|
|
main_thread: MainThreadMarker,
|
|
|
|
|
window: WindowExt,
|
|
|
|
|
engine: Option<Engine>,
|
|
|
|
|
screen: ScreenExt,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum State {
|
|
|
|
|
Unsupported,
|
|
|
|
|
Initialize(Notified<Result<(), MonitorPermissionError>>),
|
|
|
|
|
Permission { permission: PermissionStatusExt, _handle: EventListenerHandle<dyn Fn()> },
|
|
|
|
|
Upgrade(Notified<Result<(), MonitorPermissionError>>),
|
2024-08-04 16:49:19 +02:00
|
|
|
Detailed(Detailed),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Detailed {
|
|
|
|
|
details: ScreenDetails,
|
|
|
|
|
id_counter: u64,
|
|
|
|
|
screens: Vec<(u64, Weak<ScreenDetailed>)>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Detailed {
|
|
|
|
|
fn handle(
|
|
|
|
|
&mut self,
|
|
|
|
|
main_thread: MainThreadMarker,
|
|
|
|
|
runner: WeakShared,
|
|
|
|
|
window: WindowExt,
|
|
|
|
|
engine: Option<Engine>,
|
|
|
|
|
screen: ScreenDetailed,
|
|
|
|
|
) -> MonitorHandle {
|
|
|
|
|
// Before creating a new entry, see if we have an ID for this screen already.
|
|
|
|
|
let found_screen = self.screens.iter().find_map(|(id, internal_screen)| {
|
|
|
|
|
let internal_screen =
|
|
|
|
|
internal_screen.upgrade().expect("dropped `MonitorHandle` without cleaning up");
|
|
|
|
|
|
|
|
|
|
if *internal_screen == screen {
|
|
|
|
|
Some((*id, internal_screen))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
let (id, screen) = if let Some((id, screen)) = found_screen {
|
|
|
|
|
(id, screen)
|
|
|
|
|
} else {
|
|
|
|
|
let id = self.id_counter;
|
|
|
|
|
self.id_counter += 1;
|
|
|
|
|
let screen = Rc::new(screen);
|
|
|
|
|
|
|
|
|
|
self.screens.push((id, Rc::downgrade(&screen)));
|
|
|
|
|
|
|
|
|
|
(id, screen)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
MonitorHandle::new(
|
|
|
|
|
main_thread,
|
|
|
|
|
Inner::new(window, engine, Screen::Detailed { runner, id, screen }),
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MonitorHandler {
|
2024-08-04 16:49:19 +02:00
|
|
|
/// When the [`MonitorHandler`] is created, it first checks if permission has already been
|
|
|
|
|
/// granted by the user for this page, in which case it retrieves [`ScreenDetails`].
|
|
|
|
|
///
|
|
|
|
|
/// If not, it will listen to external changes in the permission and automatically elevate
|
|
|
|
|
/// [`MonitorHandler`].
|
2024-07-23 20:33:10 +02:00
|
|
|
pub fn new(
|
|
|
|
|
main_thread: MainThreadMarker,
|
|
|
|
|
window: Window,
|
|
|
|
|
navigator: &Navigator,
|
|
|
|
|
runner: WeakShared,
|
|
|
|
|
) -> Self {
|
|
|
|
|
let window: WindowExt = window.unchecked_into();
|
|
|
|
|
let engine = super::web_sys::engine(navigator);
|
|
|
|
|
let screen: ScreenExt = window.screen().unwrap().unchecked_into();
|
|
|
|
|
|
|
|
|
|
let state = if has_screen_details_support(&window) {
|
2024-08-04 16:49:19 +02:00
|
|
|
// First try and get permissions.
|
2024-07-23 20:33:10 +02:00
|
|
|
let permissions = navigator.permissions().expect(
|
|
|
|
|
"expected the Permissions API to be implemented if the Window Management API is \
|
|
|
|
|
as well",
|
|
|
|
|
);
|
|
|
|
|
let descriptor: PermissionDescriptor = Object::new().unchecked_into();
|
|
|
|
|
descriptor.set_name("window-management");
|
|
|
|
|
let future = JsFuture::from(permissions.query(&descriptor).unwrap());
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
let runner = runner.clone();
|
2024-07-23 20:33:10 +02:00
|
|
|
let window = window.clone();
|
|
|
|
|
let notifier = Notifier::new();
|
|
|
|
|
let notified = notifier.notified();
|
|
|
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
|
|
|
let permission: PermissionStatusExt = match future.await {
|
|
|
|
|
Ok(permission) => permission.unchecked_into(),
|
|
|
|
|
Err(error) => unreachable_error(
|
|
|
|
|
&error,
|
|
|
|
|
"retrieving permission for Window Management API failed even though its \
|
|
|
|
|
implemented",
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
let details = match permission.state() {
|
|
|
|
|
// If we have permission, go ahead and get `ScreenDetails`.
|
2024-07-23 20:33:10 +02:00
|
|
|
PermissionState::Granted => {
|
2024-08-04 16:49:19 +02:00
|
|
|
let details = match JsFuture::from(window.screen_details()).await {
|
|
|
|
|
Ok(details) => details.unchecked_into(),
|
2024-07-23 20:33:10 +02:00
|
|
|
Err(error) => unreachable_error(
|
|
|
|
|
&error,
|
|
|
|
|
"getting screen details failed even though permission was granted",
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
notifier.notify(Ok(()));
|
2024-08-04 16:49:19 +02:00
|
|
|
Some(details)
|
2024-07-23 20:33:10 +02:00
|
|
|
},
|
|
|
|
|
PermissionState::Denied => {
|
|
|
|
|
notifier.notify(Err(MonitorPermissionError::Denied));
|
|
|
|
|
None
|
|
|
|
|
},
|
|
|
|
|
PermissionState::Prompt => {
|
|
|
|
|
notifier.notify(Err(MonitorPermissionError::Prompt));
|
|
|
|
|
None
|
|
|
|
|
},
|
|
|
|
|
_ => {
|
|
|
|
|
error!(
|
|
|
|
|
"encountered unknown permission state: {}",
|
|
|
|
|
permission.state_string()
|
|
|
|
|
);
|
|
|
|
|
notifier.notify(Err(MonitorPermissionError::Denied));
|
|
|
|
|
None
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Notifying `Future`s is not dependant on the lifetime of the runner,
|
|
|
|
|
// because they can outlive it.
|
|
|
|
|
if let Some(runner) = runner.upgrade() {
|
2024-08-04 16:49:19 +02:00
|
|
|
if let Some(details) = details {
|
|
|
|
|
runner.monitor().upgrade(details);
|
2024-07-23 20:33:10 +02:00
|
|
|
} else {
|
|
|
|
|
// If permission is denied we listen for changes so we can catch external
|
|
|
|
|
// permission granting.
|
|
|
|
|
let handle =
|
|
|
|
|
Self::setup_listener(runner.weak(), window, permission.clone());
|
2024-08-04 16:49:19 +02:00
|
|
|
*runner.monitor().state.borrow_mut() =
|
|
|
|
|
State::Permission { permission, _handle: handle };
|
2024-07-23 20:33:10 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
runner.start_delayed();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
State::Initialize(notified)
|
|
|
|
|
} else {
|
|
|
|
|
State::Unsupported
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
Self { runner, state: RefCell::new(state), main_thread, window, engine, screen }
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
/// Listens to external permission changes and elevates [`MonitorHandle`] automatically.
|
2024-07-23 20:33:10 +02:00
|
|
|
fn setup_listener(
|
|
|
|
|
runner: WeakShared,
|
|
|
|
|
window: WindowExt,
|
|
|
|
|
permission: PermissionStatus,
|
|
|
|
|
) -> EventListenerHandle<dyn Fn()> {
|
|
|
|
|
EventListenerHandle::new(
|
|
|
|
|
permission.clone(),
|
|
|
|
|
"change",
|
|
|
|
|
Closure::new(move || {
|
|
|
|
|
if let PermissionState::Granted = permission.state() {
|
|
|
|
|
let future = JsFuture::from(window.screen_details());
|
|
|
|
|
|
|
|
|
|
let runner = runner.clone();
|
|
|
|
|
wasm_bindgen_futures::spawn_local(async move {
|
2024-08-04 16:49:19 +02:00
|
|
|
let details = match future.await {
|
|
|
|
|
Ok(details) => details.unchecked_into(),
|
2024-07-23 20:33:10 +02:00
|
|
|
Err(error) => unreachable_error(
|
|
|
|
|
&error,
|
|
|
|
|
"getting screen details failed even though permission was granted",
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some(runner) = runner.upgrade() {
|
|
|
|
|
// We drop the event listener handle here, which
|
2024-08-04 16:49:19 +02:00
|
|
|
// doesn't drop it during its execution, because
|
2024-07-23 20:33:10 +02:00
|
|
|
// we are in a `spawn_local()` context.
|
2024-08-04 16:49:19 +02:00
|
|
|
runner.monitor().upgrade(details);
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
/// Elevate [`MonitorHandler`] to [`ScreenDetails`].
|
|
|
|
|
fn upgrade(&self, details: ScreenDetails) {
|
|
|
|
|
*self.state.borrow_mut() =
|
|
|
|
|
State::Detailed(Detailed { details, id_counter: 0, screens: Vec::new() });
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 20:33:10 +02:00
|
|
|
pub fn is_extended(&self) -> Option<bool> {
|
|
|
|
|
self.screen.is_extended()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_initializing(&self) -> bool {
|
|
|
|
|
matches!(self.state.borrow().deref(), State::Initialize(_))
|
2019-09-24 19:39:13 -04:00
|
|
|
}
|
2024-07-23 20:33:10 +02:00
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
fn handle(&self, detailed: &mut Detailed, screen: ScreenDetailed) -> MonitorHandle {
|
|
|
|
|
detailed.handle(
|
|
|
|
|
self.main_thread,
|
|
|
|
|
self.runner.clone(),
|
|
|
|
|
self.window.clone(),
|
|
|
|
|
self.engine,
|
|
|
|
|
screen,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 20:33:10 +02:00
|
|
|
pub fn current_monitor(&self) -> MonitorHandle {
|
2024-08-04 16:49:19 +02:00
|
|
|
if let State::Detailed(detailed) = self.state.borrow_mut().deref_mut() {
|
|
|
|
|
self.handle(detailed, detailed.details.current_screen())
|
2024-07-23 20:33:10 +02:00
|
|
|
} else {
|
|
|
|
|
MonitorHandle::new(
|
|
|
|
|
self.main_thread,
|
|
|
|
|
Inner::new(self.window.clone(), self.engine, Screen::Screen(self.screen.clone())),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note: We have to return a `Vec` here because the iterator is otherwise not `Send` + `Sync`.
|
|
|
|
|
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
|
2024-08-04 16:49:19 +02:00
|
|
|
let mut state = self.state.borrow_mut();
|
|
|
|
|
if let State::Detailed(detailed) = state.deref_mut() {
|
|
|
|
|
detailed
|
|
|
|
|
.details
|
2024-07-23 20:33:10 +02:00
|
|
|
.screens()
|
|
|
|
|
.into_iter()
|
2024-08-04 16:49:19 +02:00
|
|
|
.map(move |screen| self.handle(detailed, screen))
|
2024-07-23 20:33:10 +02:00
|
|
|
.collect()
|
|
|
|
|
} else {
|
2024-08-04 16:49:19 +02:00
|
|
|
drop(state);
|
2024-07-23 20:33:10 +02:00
|
|
|
vec![self.current_monitor()]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
2024-08-04 16:49:19 +02:00
|
|
|
if let State::Detailed(detailed) = self.state.borrow_mut().deref_mut() {
|
|
|
|
|
detailed
|
|
|
|
|
.details
|
|
|
|
|
.screens()
|
|
|
|
|
.into_iter()
|
|
|
|
|
.find_map(|screen| screen.is_primary().then(|| self.handle(detailed, screen)))
|
2024-07-23 20:33:10 +02:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
pub(crate) fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture {
|
2024-07-23 20:33:10 +02:00
|
|
|
let state = self.state.borrow();
|
2024-08-04 16:49:19 +02:00
|
|
|
let permission = match state.deref() {
|
2024-07-23 20:33:10 +02:00
|
|
|
State::Unsupported => {
|
|
|
|
|
return MonitorPermissionFuture::Ready(Some(Err(
|
|
|
|
|
MonitorPermissionError::Unsupported,
|
|
|
|
|
)))
|
|
|
|
|
},
|
2024-08-04 16:49:19 +02:00
|
|
|
// If we are currently initializing, wait for initialization to finish before we do our
|
|
|
|
|
// thing.
|
2024-07-23 20:33:10 +02:00
|
|
|
State::Initialize(notified) => {
|
|
|
|
|
return MonitorPermissionFuture::Initialize {
|
2024-08-04 16:49:19 +02:00
|
|
|
runner: Dispatcher::new(
|
|
|
|
|
self.main_thread,
|
|
|
|
|
(self.runner.clone(), self.window.clone()),
|
|
|
|
|
)
|
|
|
|
|
.0,
|
2024-07-23 20:33:10 +02:00
|
|
|
notified: notified.clone(),
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-08-04 16:49:19 +02:00
|
|
|
// If we finished initialization we at least possess `PermissionStatus`.
|
|
|
|
|
State::Permission { permission, .. } => permission,
|
|
|
|
|
// A request is already in progress. Use that!
|
|
|
|
|
State::Upgrade(notified) => return MonitorPermissionFuture::Upgrade(notified.clone()),
|
|
|
|
|
State::Detailed { .. } => return MonitorPermissionFuture::Ready(Some(Ok(()))),
|
|
|
|
|
};
|
2024-07-23 20:33:10 +02:00
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
match permission.state() {
|
|
|
|
|
PermissionState::Granted | PermissionState::Prompt => (),
|
|
|
|
|
PermissionState::Denied => {
|
|
|
|
|
return MonitorPermissionFuture::Ready(Some(Err(MonitorPermissionError::Denied)))
|
|
|
|
|
},
|
|
|
|
|
_ => {
|
|
|
|
|
error!("encountered unknown permission state: {}", permission.state_string());
|
2024-07-23 20:33:10 +02:00
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
return MonitorPermissionFuture::Ready(Some(Err(MonitorPermissionError::Denied)));
|
|
|
|
|
},
|
|
|
|
|
}
|
2024-07-23 20:33:10 +02:00
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
drop(state);
|
2024-07-23 20:33:10 +02:00
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
// We are ready to explicitly ask the user for permission, lets go!
|
2024-07-23 20:33:10 +02:00
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
let notifier = Notifier::new();
|
|
|
|
|
let notified = notifier.notified();
|
|
|
|
|
*self.state.borrow_mut() = State::Upgrade(notified.clone());
|
2024-07-23 20:33:10 +02:00
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
MonitorPermissionFuture::upgrade_internal(self.runner.clone(), &self.window, notifier);
|
2024-07-23 20:33:10 +02:00
|
|
|
|
|
|
|
|
MonitorPermissionFuture::Upgrade(notified)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn has_detailed_monitor_permission_async(&self) -> HasMonitorPermissionFuture {
|
|
|
|
|
match self.state.borrow().deref() {
|
|
|
|
|
State::Unsupported | State::Permission { .. } | State::Upgrade(_) => {
|
|
|
|
|
HasMonitorPermissionFuture::Ready(Some(false))
|
|
|
|
|
},
|
|
|
|
|
State::Initialize(notified) => HasMonitorPermissionFuture::Future(notified.clone()),
|
2024-08-04 16:49:19 +02:00
|
|
|
State::Detailed { .. } => HasMonitorPermissionFuture::Ready(Some(true)),
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn has_detailed_monitor_permission(&self) -> bool {
|
|
|
|
|
match self.state.borrow().deref() {
|
|
|
|
|
State::Unsupported | State::Permission { .. } | State::Upgrade(_) => false,
|
|
|
|
|
State::Initialize(_) => {
|
|
|
|
|
unreachable!("called `has_detailed_monitor_permission()` while initializing")
|
|
|
|
|
},
|
2024-08-04 16:49:19 +02:00
|
|
|
State::Detailed { .. } => true,
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub(crate) enum MonitorPermissionFuture {
|
|
|
|
|
Initialize {
|
|
|
|
|
runner: Dispatcher<(WeakShared, WindowExt)>,
|
|
|
|
|
notified: Notified<Result<(), MonitorPermissionError>>,
|
|
|
|
|
},
|
|
|
|
|
Upgrade(Notified<Result<(), MonitorPermissionError>>),
|
|
|
|
|
Ready(Option<Result<(), MonitorPermissionError>>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MonitorPermissionFuture {
|
|
|
|
|
fn upgrade(&mut self) {
|
|
|
|
|
let notifier = Notifier::new();
|
|
|
|
|
let notified = notifier.notified();
|
|
|
|
|
let Self::Initialize { runner, .. } = mem::replace(self, Self::Upgrade(notified.clone()))
|
|
|
|
|
else {
|
|
|
|
|
unreachable!()
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
runner.dispatch(|(runner, window)| {
|
|
|
|
|
if let Some(runner) = runner.upgrade() {
|
|
|
|
|
*runner.monitor().state.borrow_mut() = State::Upgrade(notified);
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
Self::upgrade_internal(runner.clone(), window, notifier);
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-07-23 20:33:10 +02:00
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
fn upgrade_internal(
|
|
|
|
|
runner: WeakShared,
|
|
|
|
|
window: &WindowExt,
|
|
|
|
|
notifier: Notifier<Result<(), MonitorPermissionError>>,
|
|
|
|
|
) {
|
|
|
|
|
let future = JsFuture::from(window.screen_details());
|
|
|
|
|
|
|
|
|
|
wasm_bindgen_futures::spawn_local(async move {
|
|
|
|
|
match future.await {
|
|
|
|
|
Ok(details) => {
|
|
|
|
|
// Notifying `Future`s is not dependant on the lifetime of the runner, because
|
|
|
|
|
// they can outlive it.
|
|
|
|
|
notifier.notify(Ok(()));
|
|
|
|
|
|
|
|
|
|
if let Some(runner) = runner.upgrade() {
|
|
|
|
|
runner.monitor().upgrade(details.unchecked_into());
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Err(error) => unreachable_error(
|
|
|
|
|
&error,
|
|
|
|
|
"getting screen details failed even though permission was granted",
|
|
|
|
|
),
|
|
|
|
|
}
|
2024-07-23 20:33:10 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Future for MonitorPermissionFuture {
|
|
|
|
|
type Output = Result<(), MonitorPermissionError>;
|
|
|
|
|
|
|
|
|
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
|
|
|
let this = self.get_mut();
|
|
|
|
|
|
|
|
|
|
match this {
|
|
|
|
|
Self::Initialize { notified, .. } => {
|
|
|
|
|
if let Err(error) = ready!(Pin::new(notified).poll(cx).map(Option::unwrap)) {
|
|
|
|
|
match error {
|
|
|
|
|
MonitorPermissionError::Denied | MonitorPermissionError::Unsupported => {
|
|
|
|
|
Poll::Ready(Err(error))
|
|
|
|
|
},
|
|
|
|
|
MonitorPermissionError::Prompt => {
|
|
|
|
|
this.upgrade();
|
|
|
|
|
Poll::Pending
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Poll::Ready(Ok(()))
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
Self::Upgrade(notified) => Pin::new(notified).poll(cx).map(Option::unwrap),
|
|
|
|
|
Self::Ready(result) => Poll::Ready(
|
|
|
|
|
result.take().expect("`MonitorPermissionFuture` polled after completion"),
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum HasMonitorPermissionFuture {
|
|
|
|
|
Future(Notified<Result<(), MonitorPermissionError>>),
|
|
|
|
|
Ready(Option<bool>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Future for HasMonitorPermissionFuture {
|
|
|
|
|
type Output = bool;
|
|
|
|
|
|
|
|
|
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
|
|
|
match self.get_mut() {
|
|
|
|
|
Self::Future(notified) => {
|
|
|
|
|
Pin::new(notified).poll(cx).map(Option::unwrap).map(|result| result.is_ok())
|
|
|
|
|
},
|
|
|
|
|
Self::Ready(result) => Poll::Ready(
|
|
|
|
|
result.take().expect("`MonitorPermissionFuture` polled after completion"),
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[track_caller]
|
|
|
|
|
fn unreachable_error(error: &JsValue, message: &str) -> ! {
|
|
|
|
|
if let Some(error) = error.dyn_ref::<DomException>() {
|
|
|
|
|
unreachable!("{message}. {}: {}", error.name(), error.message());
|
|
|
|
|
} else {
|
|
|
|
|
console::error_1(error);
|
|
|
|
|
unreachable!("{message}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
static HAS_LOCK_SUPPORT: OnceLock<bool> = OnceLock::new();
|
|
|
|
|
|
|
|
|
|
fn has_previous_lock_support() -> Option<bool> {
|
|
|
|
|
HAS_LOCK_SUPPORT.get().cloned()
|
2024-07-23 20:33:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn has_screen_details_support(window: &Window) -> bool {
|
|
|
|
|
thread_local! {
|
|
|
|
|
static HAS_SCREEN_DETAILS: OnceCell<bool> = const { OnceCell::new() };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HAS_SCREEN_DETAILS.with(|support| {
|
|
|
|
|
*support.get_or_init(|| {
|
|
|
|
|
let window: &WindowExt = window.unchecked_ref();
|
|
|
|
|
!window.has_screen_details().is_undefined()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen]
|
|
|
|
|
extern "C" {
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
#[wasm_bindgen(extends = Window)]
|
|
|
|
|
pub(crate) type WindowExt;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter, js_name = getScreenDetails)]
|
|
|
|
|
fn has_screen_details(this: &WindowExt) -> JsValue;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, js_name = getScreenDetails)]
|
|
|
|
|
fn screen_details(this: &WindowExt) -> Promise;
|
|
|
|
|
|
|
|
|
|
type ScreenDetails;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter, js_name = currentScreen)]
|
|
|
|
|
fn current_screen(this: &ScreenDetails) -> ScreenDetailed;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter)]
|
|
|
|
|
fn screens(this: &ScreenDetails) -> Vec<ScreenDetailed>;
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
#[derive(Clone, PartialEq)]
|
2024-07-23 20:33:10 +02:00
|
|
|
#[wasm_bindgen(extends = web_sys::Screen)]
|
|
|
|
|
pub(crate) type ScreenExt;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter, js_name = isExtended)]
|
|
|
|
|
fn is_extended(this: &ScreenExt) -> Option<bool>;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(extends = ScreenOrientation)]
|
|
|
|
|
type ScreenOrientationExt;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter, js_name = type)]
|
|
|
|
|
fn type_string(this: &ScreenOrientationExt) -> String;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter, js_name = lock)]
|
|
|
|
|
fn has_lock(this: &ScreenOrientationExt) -> JsValue;
|
|
|
|
|
|
2024-08-04 16:49:19 +02:00
|
|
|
#[derive(PartialEq)]
|
2024-07-23 20:33:10 +02:00
|
|
|
#[wasm_bindgen(extends = ScreenExt)]
|
|
|
|
|
pub(crate) type ScreenDetailed;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter, js_name = devicePixelRatio)]
|
|
|
|
|
fn device_pixel_ratio(this: &ScreenDetailed) -> f64;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter, js_name = isInternal)]
|
|
|
|
|
fn is_internal(this: &ScreenDetailed) -> bool;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter, js_name = isPrimary)]
|
|
|
|
|
fn is_primary(this: &ScreenDetailed) -> bool;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter)]
|
|
|
|
|
fn label(this: &ScreenDetailed) -> String;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter)]
|
|
|
|
|
fn left(this: &ScreenDetailed) -> i32;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter)]
|
|
|
|
|
fn top(this: &ScreenDetailed) -> i32;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(extends = Object)]
|
|
|
|
|
type PermissionDescriptor;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, setter, js_name = name)]
|
|
|
|
|
fn set_name(this: &PermissionDescriptor, name: &str);
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(extends = PermissionStatus)]
|
|
|
|
|
type PermissionStatusExt;
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(method, getter, js_name = state)]
|
|
|
|
|
fn state_string(this: &PermissionStatusExt) -> String;
|
2019-09-24 19:39:13 -04:00
|
|
|
}
|