Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web: Implement MonitorHandle #3801

Merged
merged 2 commits into from
Jul 23, 2024
Merged
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
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -305,14 +305,21 @@ web_sys = { package = "web-sys", version = "0.3.64", features = [
'MessagePort',
'Navigator',
'Node',
'OrientationType',
'OrientationLockType',
'PageTransitionEvent',
'Permissions',
'PermissionState',
'PermissionStatus',
'PointerEvent',
'PremultiplyAlpha',
'ResizeObserver',
'ResizeObserverBoxOptions',
'ResizeObserverEntry',
'ResizeObserverOptions',
'ResizeObserverSize',
'Screen',
'ScreenOrientation',
'VisibilityState',
'Window',
'WheelEvent',
1 change: 1 addition & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ disallowed-methods = [
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
{ path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
102 changes: 80 additions & 22 deletions examples/window.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ use std::error::Error;
use std::fmt::Debug;
#[cfg(not(any(android_platform, ios_platform)))]
use std::num::NonZeroU32;
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::Arc;
use std::{fmt, mem};

@@ -25,6 +26,8 @@ use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMac
use winit::platform::startup_notify::{
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
};
#[cfg(web_platform)]
use winit::platform::web::{ActiveEventLoopExtWeb, CustomCursorExtWeb, WindowAttributesExtWeb};
use winit::window::{
Cursor, CursorGrabMode, CustomCursor, CustomCursorSource, Fullscreen, Icon, ResizeDirection,
Theme, Window, WindowId,
@@ -43,26 +46,34 @@ fn main() -> Result<(), Box<dyn Error>> {
tracing::init();

let event_loop = EventLoop::new()?;
let _event_loop_proxy = event_loop.create_proxy();
let (sender, receiver) = mpsc::channel();

// Wire the user event from another thread.
#[cfg(not(web_platform))]
std::thread::spawn(move || {
// Wake up the `event_loop` once every second and dispatch a custom event
// from a different thread.
info!("Starting to send user event every second");
loop {
_event_loop_proxy.wake_up();
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
{
let event_loop_proxy = event_loop.create_proxy();
let sender = sender.clone();
std::thread::spawn(move || {
// Wake up the `event_loop` once every second and dispatch a custom event
// from a different thread.
info!("Starting to send user event every second");
loop {
let _ = sender.send(Action::Message);
event_loop_proxy.wake_up();
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
}

let app = Application::new(&event_loop);
let app = Application::new(&event_loop, receiver, sender);
Ok(event_loop.run_app(app)?)
}

/// Application state and event handling.
struct Application {
/// Trigger actions through proxy wake up.
receiver: Receiver<Action>,
sender: Sender<Action>,
/// Custom cursors assets.
custom_cursors: Vec<CustomCursor>,
/// Application icon.
@@ -76,7 +87,7 @@ struct Application {
}

impl Application {
fn new(event_loop: &EventLoop) -> Self {
fn new(event_loop: &EventLoop, receiver: Receiver<Action>, sender: Sender<Action>) -> Self {
// SAFETY: we drop the context right before the event loop is stopped, thus making it safe.
#[cfg(not(any(android_platform, ios_platform)))]
let context = Some(
@@ -103,6 +114,8 @@ impl Application {
];

Self {
receiver,
sender,
#[cfg(not(any(android_platform, ios_platform)))]
context,
custom_cursors,
@@ -138,7 +151,6 @@ impl Application {

#[cfg(web_platform)]
{
use winit::platform::web::WindowAttributesExtWeb;
window_attributes = window_attributes.with_append(true);
}

@@ -160,7 +172,23 @@ impl Application {
Ok(window_id)
}

fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) {
fn handle_action_from_proxy(&mut self, _event_loop: &ActiveEventLoop, action: Action) {
match action {
#[cfg(web_platform)]
Action::DumpMonitors => self.dump_monitors(_event_loop),
Action::Message => {
info!("User wake up");
},
_ => unreachable!("Tried to execute invalid action without `WindowId`"),
}
}

fn handle_action_with_window(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
action: Action,
) {
// let cursor_position = self.cursor_position;
let window = self.windows.get_mut(&window_id).unwrap();
info!("Executing action: {action:?}");
@@ -217,6 +245,26 @@ impl Application {
}
},
Action::RequestResize => window.swap_dimensions(),
#[cfg(web_platform)]
Action::DumpMonitors => {
let future = event_loop.request_detailed_monitor_permission();
let proxy = event_loop.create_proxy();
let sender = self.sender.clone();
wasm_bindgen_futures::spawn_local(async move {
if let Err(error) = future.await {
error!("{error}")
}

let _ = sender.send(Action::DumpMonitors);
proxy.wake_up();
});
},
#[cfg(not(web_platform))]
Action::DumpMonitors => self.dump_monitors(event_loop),
Action::Message => {
self.sender.send(Action::Message).unwrap();
event_loop.create_proxy().wake_up();
},
}
}

@@ -300,8 +348,10 @@ impl Application {
}

impl ApplicationHandler for Application {
fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
info!("User wake up");
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
while let Ok(action) = self.receiver.try_recv() {
self.handle_action_from_proxy(event_loop, action)
}
}

fn window_event(
@@ -369,7 +419,7 @@ impl ApplicationHandler for Application {
};

if let Some(action) = action {
self.handle_action(event_loop, window_id, action);
self.handle_action_with_window(event_loop, window_id, action);
}
}
},
@@ -378,7 +428,7 @@ impl ApplicationHandler for Application {
if let Some(action) =
state.is_pressed().then(|| Self::process_mouse_binding(button, &mods)).flatten()
{
self.handle_action(event_loop, window_id, action);
self.handle_action_with_window(event_loop, window_id, action);
}
},
WindowEvent::CursorLeft { .. } => {
@@ -703,8 +753,6 @@ impl WindowState {
) {
use std::time::Duration;

use winit::platform::web::CustomCursorExtWeb;

let cursors = vec![
custom_cursors[0].clone(),
custom_cursors[1].clone(),
@@ -886,6 +934,8 @@ enum Action {
#[cfg(macos_platform)]
CreateNewTab,
RequestResize,
DumpMonitors,
Message,
}

impl Action {
@@ -920,6 +970,14 @@ impl Action {
#[cfg(macos_platform)]
Action::CreateNewTab => "Create new tab",
Action::RequestResize => "Request a resize",
#[cfg(not(web_platform))]
Action::DumpMonitors => "Dump monitor information",
#[cfg(web_platform)]
Action::DumpMonitors => {
"Request permission to query detailed monitor information and dump monitor \
information"
},
Action::Message => "Prints a message through a user wake up",
}
}
}
@@ -942,8 +1000,6 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
fn url_custom_cursor() -> CustomCursorSource {
use std::sync::atomic::{AtomicU64, Ordering};

use winit::platform::web::CustomCursorExtWeb;

static URL_COUNTER: AtomicU64 = AtomicU64::new(0);

CustomCursor::from_url(
@@ -1041,6 +1097,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
Binding::new("R", ModifiersState::ALT, Action::RequestResize),
// M.
Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors),
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
Binding::new("M", ModifiersState::ALT, Action::Minimize),
// N.
@@ -1069,6 +1126,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
#[cfg(macos_platform)]
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
Binding::new("S", ModifiersState::CONTROL, Action::Message),
];

const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[
7 changes: 7 additions & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
@@ -47,6 +47,13 @@ changelog entry.
- On Web, add `ActiveEventLoopExtWeb::is_cursor_lock_raw()` to determine if
`DeviceEvent::MouseMotion` is returning raw data, not OS accelerated, when using
`CursorGrabMode::Locked`.
- On Web, implement `MonitorHandle` and `VideoModeHandle`.

Without prompting the user for permission, only the current monitor is returned. But when
prompting and being granted permission through
`ActiveEventLoop::request_detailed_monitor_permission()`, access to all monitors and their
information is available. This "detailed monitors" can be used in `Window::set_fullscreen()` as
well.

### Changed

17 changes: 16 additions & 1 deletion src/event_loop.rs
Original file line number Diff line number Diff line change
@@ -380,6 +380,15 @@ impl ActiveEventLoop {
}

/// Returns the list of all the monitors available on the system.
///
/// ## Platform-specific
///
/// **Web:** Only returns the current monitor without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
#[inline]
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
let _span = tracing::debug_span!("winit::ActiveEventLoop::available_monitors",).entered();
@@ -394,7 +403,13 @@ impl ActiveEventLoop {
///
/// ## Platform-specific
///
/// **Wayland / Web:** Always returns `None`.
/// - **Wayland:** Always returns `None`.
/// - **Web:** Always returns `None` without
#[cfg_attr(
any(web_platform, docsrs),
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
#[inline]
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
let _span = tracing::debug_span!("winit::ActiveEventLoop::primary_monitor",).entered();
37 changes: 33 additions & 4 deletions src/monitor.rs
Original file line number Diff line number Diff line change
@@ -68,6 +68,10 @@ impl VideoModeHandle {
}

/// Returns the refresh rate of this video mode in mHz.
///
/// ## Platform-specific
///
/// **Web:** Always returns `0`.
#[inline]
pub fn refresh_rate_millihertz(&self) -> u32 {
self.video_mode.refresh_rate_millihertz()
@@ -108,6 +112,15 @@ impl MonitorHandle {
/// Returns a human-readable name of the monitor.
///
/// Returns `None` if the monitor doesn't exist anymore.
///
/// ## Platform-specific
///
/// **Web:** Always returns [`None`] without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
#[inline]
pub fn name(&self) -> Option<String> {
self.inner.name()
@@ -121,6 +134,15 @@ impl MonitorHandle {

/// Returns the top-left corner position of the monitor relative to the larger full
/// screen area.
///
/// ## Platform-specific
///
/// **Web:** Always returns [`Default`] without
#[cfg_attr(
any(web_platform, docsrs),
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
self.inner.position()
@@ -133,6 +155,10 @@ impl MonitorHandle {
///
/// When using exclusive fullscreen, the refresh rate of the [`VideoModeHandle`] that was
/// used to enter fullscreen should be used instead.
///
/// ## Platform-specific
///
/// **Web:** Always returns [`None`].
#[inline]
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
self.inner.refresh_rate_millihertz()
@@ -148,18 +174,21 @@ impl MonitorHandle {
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
/// - **Wayland:** May differ from [`Window::scale_factor`].
/// - **Android:** Always returns 1.0.
/// - **Web:** Always returns `0.0` without
#[cfg_attr(
any(web_platform, docsrs),
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
)]
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
///
#[rustfmt::skip]
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
#[inline]
pub fn scale_factor(&self) -> f64 {
self.inner.scale_factor()
}

/// Returns all fullscreen video modes supported by this monitor.
///
/// ## Platform-specific
///
/// - **Web:** Always returns an empty iterator
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoModeHandle> {
self.inner.video_modes().map(|video_mode| VideoModeHandle { video_mode })
Loading