Skip to content

Commit a3f956d

Browse files
committed
Web: Implement MonitorHandle
1 parent 2e97ab3 commit a3f956d

File tree

21 files changed

+1492
-120
lines changed

21 files changed

+1492
-120
lines changed

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,21 @@ web_sys = { package = "web-sys", version = "0.3.64", features = [
305305
'MessagePort',
306306
'Navigator',
307307
'Node',
308+
'OrientationType',
309+
'OrientationLockType',
308310
'PageTransitionEvent',
311+
'Permissions',
312+
'PermissionState',
313+
'PermissionStatus',
309314
'PointerEvent',
310315
'PremultiplyAlpha',
311316
'ResizeObserver',
312317
'ResizeObserverBoxOptions',
313318
'ResizeObserverEntry',
314319
'ResizeObserverOptions',
315320
'ResizeObserverSize',
321+
'Screen',
322+
'ScreenOrientation',
316323
'VisibilityState',
317324
'Window',
318325
'WheelEvent',

clippy.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ disallowed-methods = [
44
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
55
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
66
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
7+
{ path = "web_sys::Window::navigator", reason = "cache this to reduce calls to JS" },
78
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
89
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
910
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },

examples/window.rs

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::error::Error;
55
use std::fmt::Debug;
66
#[cfg(not(any(android_platform, ios_platform)))]
77
use std::num::NonZeroU32;
8+
use std::sync::mpsc::{self, Receiver, Sender};
89
use std::sync::Arc;
910
use std::{fmt, mem};
1011

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

4548
let event_loop = EventLoop::new()?;
46-
let _event_loop_proxy = event_loop.create_proxy();
49+
let (sender, receiver) = mpsc::channel();
4750

4851
// Wire the user event from another thread.
4952
#[cfg(not(web_platform))]
50-
std::thread::spawn(move || {
51-
// Wake up the `event_loop` once every second and dispatch a custom event
52-
// from a different thread.
53-
info!("Starting to send user event every second");
54-
loop {
55-
_event_loop_proxy.wake_up();
56-
std::thread::sleep(std::time::Duration::from_secs(1));
57-
}
58-
});
53+
{
54+
let event_loop_proxy = event_loop.create_proxy();
55+
let sender = sender.clone();
56+
std::thread::spawn(move || {
57+
// Wake up the `event_loop` once every second and dispatch a custom event
58+
// from a different thread.
59+
info!("Starting to send user event every second");
60+
loop {
61+
let _ = sender.send(Action::Message);
62+
event_loop_proxy.wake_up();
63+
std::thread::sleep(std::time::Duration::from_secs(1));
64+
}
65+
});
66+
}
5967

60-
let app = Application::new(&event_loop);
68+
let app = Application::new(&event_loop, receiver, sender);
6169
Ok(event_loop.run_app(app)?)
6270
}
6371

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

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

105116
Self {
117+
receiver,
118+
sender,
106119
#[cfg(not(any(android_platform, ios_platform)))]
107120
context,
108121
custom_cursors,
@@ -138,7 +151,6 @@ impl Application {
138151

139152
#[cfg(web_platform)]
140153
{
141-
use winit::platform::web::WindowAttributesExtWeb;
142154
window_attributes = window_attributes.with_append(true);
143155
}
144156

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

163-
fn handle_action(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, action: Action) {
175+
fn handle_action_from_proxy(&mut self, _event_loop: &ActiveEventLoop, action: Action) {
176+
match action {
177+
#[cfg(web_platform)]
178+
Action::DumpMonitors => self.dump_monitors(_event_loop),
179+
Action::Message => {
180+
info!("User wake up");
181+
},
182+
_ => unreachable!("Tried to execute invalid action without `WindowId`"),
183+
}
184+
}
185+
186+
fn handle_action_with_window(
187+
&mut self,
188+
event_loop: &ActiveEventLoop,
189+
window_id: WindowId,
190+
action: Action,
191+
) {
164192
// let cursor_position = self.cursor_position;
165193
let window = self.windows.get_mut(&window_id).unwrap();
166194
info!("Executing action: {action:?}");
@@ -217,6 +245,26 @@ impl Application {
217245
}
218246
},
219247
Action::RequestResize => window.swap_dimensions(),
248+
#[cfg(web_platform)]
249+
Action::DumpMonitors => {
250+
let future = event_loop.request_detailed_monitor_permission();
251+
let proxy = event_loop.create_proxy();
252+
let sender = self.sender.clone();
253+
wasm_bindgen_futures::spawn_local(async move {
254+
if let Err(error) = future.await {
255+
error!("{error}")
256+
}
257+
258+
let _ = sender.send(Action::DumpMonitors);
259+
proxy.wake_up();
260+
});
261+
},
262+
#[cfg(not(web_platform))]
263+
Action::DumpMonitors => self.dump_monitors(event_loop),
264+
Action::Message => {
265+
self.sender.send(Action::Message).unwrap();
266+
event_loop.create_proxy().wake_up();
267+
},
220268
}
221269
}
222270

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

302350
impl ApplicationHandler for Application {
303-
fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
304-
info!("User wake up");
351+
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
352+
while let Ok(action) = self.receiver.try_recv() {
353+
self.handle_action_from_proxy(event_loop, action)
354+
}
305355
}
306356

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

371421
if let Some(action) = action {
372-
self.handle_action(event_loop, window_id, action);
422+
self.handle_action_with_window(event_loop, window_id, action);
373423
}
374424
}
375425
},
@@ -378,7 +428,7 @@ impl ApplicationHandler for Application {
378428
if let Some(action) =
379429
state.is_pressed().then(|| Self::process_mouse_binding(button, &mods)).flatten()
380430
{
381-
self.handle_action(event_loop, window_id, action);
431+
self.handle_action_with_window(event_loop, window_id, action);
382432
}
383433
},
384434
WindowEvent::CursorLeft { .. } => {
@@ -703,8 +753,6 @@ impl WindowState {
703753
) {
704754
use std::time::Duration;
705755

706-
use winit::platform::web::CustomCursorExtWeb;
707-
708756
let cursors = vec![
709757
custom_cursors[0].clone(),
710758
custom_cursors[1].clone(),
@@ -886,6 +934,8 @@ enum Action {
886934
#[cfg(macos_platform)]
887935
CreateNewTab,
888936
RequestResize,
937+
DumpMonitors,
938+
Message,
889939
}
890940

891941
impl Action {
@@ -920,6 +970,14 @@ impl Action {
920970
#[cfg(macos_platform)]
921971
Action::CreateNewTab => "Create new tab",
922972
Action::RequestResize => "Request a resize",
973+
#[cfg(not(web_platform))]
974+
Action::DumpMonitors => "Dump monitor information",
975+
#[cfg(web_platform)]
976+
Action::DumpMonitors => {
977+
"Request permission to query detailed monitor information and dump monitor \
978+
information"
979+
},
980+
Action::Message => "Prints a message through a user wake up",
923981
}
924982
}
925983
}
@@ -942,8 +1000,6 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
9421000
fn url_custom_cursor() -> CustomCursorSource {
9431001
use std::sync::atomic::{AtomicU64, Ordering};
9441002

945-
use winit::platform::web::CustomCursorExtWeb;
946-
9471003
static URL_COUNTER: AtomicU64 = AtomicU64::new(0);
9481004

9491005
CustomCursor::from_url(
@@ -1041,6 +1097,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
10411097
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
10421098
Binding::new("R", ModifiersState::ALT, Action::RequestResize),
10431099
// M.
1100+
Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors),
10441101
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
10451102
Binding::new("M", ModifiersState::ALT, Action::Minimize),
10461103
// N.
@@ -1069,6 +1126,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
10691126
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
10701127
#[cfg(macos_platform)]
10711128
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
1129+
Binding::new("S", ModifiersState::CONTROL, Action::Message),
10721130
];
10731131

10741132
const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[

src/changelog/unreleased.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ changelog entry.
4747
- On Web, add `ActiveEventLoopExtWeb::is_cursor_lock_raw()` to determine if
4848
`DeviceEvent::MouseMotion` is returning raw data, not OS accelerated, when using
4949
`CursorGrabMode::Locked`.
50+
- On Web, implement `MonitorHandle` and `VideoModeHandle`.
51+
52+
Without prompting the user for permission, only the current monitor is returned. But when
53+
prompting and being granted permission through
54+
`ActiveEventLoop::request_detailed_monitor_permission()`, access to all monitors and their
55+
information is available. This "detailed monitors" can be used in `Window::set_fullscreen()` as
56+
well.
5057

5158
### Changed
5259

src/event_loop.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,15 @@ impl ActiveEventLoop {
380380
}
381381

382382
/// Returns the list of all the monitors available on the system.
383+
///
384+
/// ## Platform-specific
385+
///
386+
/// **Web:** Only returns the current monitor without
387+
#[cfg_attr(
388+
any(web_platform, docsrs),
389+
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
390+
)]
391+
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
383392
#[inline]
384393
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
385394
let _span = tracing::debug_span!("winit::ActiveEventLoop::available_monitors",).entered();
@@ -394,7 +403,13 @@ impl ActiveEventLoop {
394403
///
395404
/// ## Platform-specific
396405
///
397-
/// **Wayland / Web:** Always returns `None`.
406+
/// - **Wayland:** Always returns `None`.
407+
/// - **Web:** Always returns `None` without
408+
#[cfg_attr(
409+
any(web_platform, docsrs),
410+
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
411+
)]
412+
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
398413
#[inline]
399414
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
400415
let _span = tracing::debug_span!("winit::ActiveEventLoop::primary_monitor",).entered();

src/monitor.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ impl VideoModeHandle {
6868
}
6969

7070
/// Returns the refresh rate of this video mode in mHz.
71+
///
72+
/// ## Platform-specific
73+
///
74+
/// **Web:** Always returns `0`.
7175
#[inline]
7276
pub fn refresh_rate_millihertz(&self) -> u32 {
7377
self.video_mode.refresh_rate_millihertz()
@@ -108,6 +112,15 @@ impl MonitorHandle {
108112
/// Returns a human-readable name of the monitor.
109113
///
110114
/// Returns `None` if the monitor doesn't exist anymore.
115+
///
116+
/// ## Platform-specific
117+
///
118+
/// **Web:** Always returns [`None`] without
119+
#[cfg_attr(
120+
any(web_platform, docsrs),
121+
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
122+
)]
123+
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
111124
#[inline]
112125
pub fn name(&self) -> Option<String> {
113126
self.inner.name()
@@ -121,6 +134,15 @@ impl MonitorHandle {
121134

122135
/// Returns the top-left corner position of the monitor relative to the larger full
123136
/// screen area.
137+
///
138+
/// ## Platform-specific
139+
///
140+
/// **Web:** Always returns [`Default`] without
141+
#[cfg_attr(
142+
any(web_platform, docsrs),
143+
doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
144+
)]
145+
#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")]
124146
#[inline]
125147
pub fn position(&self) -> PhysicalPosition<i32> {
126148
self.inner.position()
@@ -133,6 +155,10 @@ impl MonitorHandle {
133155
///
134156
/// When using exclusive fullscreen, the refresh rate of the [`VideoModeHandle`] that was
135157
/// used to enter fullscreen should be used instead.
158+
///
159+
/// ## Platform-specific
160+
///
161+
/// **Web:** Always returns [`None`].
136162
#[inline]
137163
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
138164
self.inner.refresh_rate_millihertz()
@@ -148,18 +174,21 @@ impl MonitorHandle {
148174
/// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable.
149175
/// - **Wayland:** May differ from [`Window::scale_factor`].
150176
/// - **Android:** Always returns 1.0.
177+
/// - **Web:** Always returns `0.0` without
178+
#[cfg_attr(
179+
any(web_platform, docsrs),
180+
doc = " [detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]."
181+
)]
182+
#[cfg_attr(not(any(web_platform, docsrs)), doc = " detailed monitor permissions.")]
151183
///
184+
#[rustfmt::skip]
152185
/// [`Window::scale_factor`]: crate::window::Window::scale_factor
153186
#[inline]
154187
pub fn scale_factor(&self) -> f64 {
155188
self.inner.scale_factor()
156189
}
157190

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

0 commit comments

Comments
 (0)