Skip to content

Commit

Permalink
Web: Implement MonitorHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda committed Jul 19, 2024
1 parent 652ff75 commit 61e4cca
Show file tree
Hide file tree
Showing 24 changed files with 1,484 additions and 205 deletions.
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -304,14 +304,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',
Expand Down
1 change: 1 addition & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
102 changes: 81 additions & 21 deletions examples/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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,
Expand All @@ -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));
std::thread::spawn({
let event_loop_proxy = event_loop.create_proxy();
let sender = sender.clone();
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.
Expand All @@ -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(
Expand All @@ -103,6 +114,8 @@ impl Application {
];

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

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

Expand All @@ -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(&mut self, event_loop: &ActiveEventLoop, action: Action) {
match action {
Action::PrintHelp => self.print_help(),
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:?}");
Expand Down Expand Up @@ -200,7 +228,6 @@ impl Application {
Action::DragWindow => window.drag_window(),
Action::DragResizeWindow => window.drag_resize_window(),
Action::ShowWindowMenu => window.show_menu(),
Action::PrintHelp => self.print_help(),
#[cfg(macos_platform)]
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
Action::SetTheme(theme) => {
Expand All @@ -217,6 +244,29 @@ impl Application {
}
},
Action::RequestResize => window.swap_dimensions(),
Action::DumpMonitors => {
#[cfg(web_platform)]
{
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))]
self.dump_monitors(event_loop);
},
Action::Message => {
self.sender.send(Action::Message).unwrap();
event_loop.create_proxy().wake_up();
},
_ => self.handle_action(event_loop, action),
}
}

Expand Down Expand Up @@ -300,8 +350,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(event_loop, action)
}
}

fn window_event(
Expand Down Expand Up @@ -369,7 +421,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);
}
}
},
Expand All @@ -378,7 +430,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 { .. } => {
Expand Down Expand Up @@ -703,8 +755,6 @@ impl WindowState {
) {
use std::time::Duration;

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

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

impl Action {
Expand Down Expand Up @@ -920,6 +972,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",
}
}
}
Expand All @@ -942,8 +1002,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(
Expand Down Expand Up @@ -1041,6 +1099,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::empty(), Action::DumpMonitors),
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
Binding::new("M", ModifiersState::ALT, Action::Minimize),
// N.
Expand Down Expand Up @@ -1069,6 +1128,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::empty(), Action::Message),
];

const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[
Expand Down
7 changes: 7 additions & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ changelog entry.

- Add `ActiveEventLoop::create_proxy()`.
- On Web, implement `Error` for `platform::web::CustomCursorError`.
- 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

Expand Down
17 changes: 16 additions & 1 deletion src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand Down
37 changes: 33 additions & 4 deletions src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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 `{ x: 0, y: 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.")]
#[inline]
pub fn position(&self) -> PhysicalPosition<i32> {
self.inner.position()
Expand All @@ -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()
Expand All @@ -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 })
Expand Down
Loading

0 comments on commit 61e4cca

Please sign in to comment.