Skip to content

Commit 8ad43af

Browse files
committed
Web: Implement MonitorHandle
1 parent e791cd5 commit 8ad43af

File tree

20 files changed

+1470
-106
lines changed

20 files changed

+1470
-106
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: 81 additions & 21 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));
53+
std::thread::spawn({
54+
let event_loop_proxy = event_loop.create_proxy();
55+
let sender = sender.clone();
56+
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+
}
5765
}
5866
});
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(&mut self, event_loop: &ActiveEventLoop, action: Action) {
176+
match action {
177+
Action::PrintHelp => self.print_help(),
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:?}");
@@ -200,7 +228,6 @@ impl Application {
200228
Action::DragWindow => window.drag_window(),
201229
Action::DragResizeWindow => window.drag_resize_window(),
202230
Action::ShowWindowMenu => window.show_menu(),
203-
Action::PrintHelp => self.print_help(),
204231
#[cfg(macos_platform)]
205232
Action::CycleOptionAsAlt => window.cycle_option_as_alt(),
206233
Action::SetTheme(theme) => {
@@ -217,6 +244,29 @@ impl Application {
217244
}
218245
},
219246
Action::RequestResize => window.swap_dimensions(),
247+
Action::DumpMonitors => {
248+
#[cfg(web_platform)]
249+
{
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+
self.dump_monitors(event_loop);
264+
},
265+
Action::Message => {
266+
self.sender.send(Action::Message).unwrap();
267+
event_loop.create_proxy().wake_up();
268+
},
269+
_ => self.handle_action(event_loop, action),
220270
}
221271
}
222272

@@ -300,8 +350,10 @@ impl Application {
300350
}
301351

302352
impl ApplicationHandler for Application {
303-
fn proxy_wake_up(&mut self, _event_loop: &ActiveEventLoop) {
304-
info!("User wake up");
353+
fn proxy_wake_up(&mut self, event_loop: &ActiveEventLoop) {
354+
while let Ok(action) = self.receiver.try_recv() {
355+
self.handle_action(event_loop, action)
356+
}
305357
}
306358

307359
fn window_event(
@@ -369,7 +421,7 @@ impl ApplicationHandler for Application {
369421
};
370422

371423
if let Some(action) = action {
372-
self.handle_action(event_loop, window_id, action);
424+
self.handle_action_with_window(event_loop, window_id, action);
373425
}
374426
}
375427
},
@@ -378,7 +430,7 @@ impl ApplicationHandler for Application {
378430
if let Some(action) =
379431
state.is_pressed().then(|| Self::process_mouse_binding(button, &mods)).flatten()
380432
{
381-
self.handle_action(event_loop, window_id, action);
433+
self.handle_action_with_window(event_loop, window_id, action);
382434
}
383435
},
384436
WindowEvent::CursorLeft { .. } => {
@@ -703,8 +755,6 @@ impl WindowState {
703755
) {
704756
use std::time::Duration;
705757

706-
use winit::platform::web::CustomCursorExtWeb;
707-
708758
let cursors = vec![
709759
custom_cursors[0].clone(),
710760
custom_cursors[1].clone(),
@@ -886,6 +936,8 @@ enum Action {
886936
#[cfg(macos_platform)]
887937
CreateNewTab,
888938
RequestResize,
939+
DumpMonitors,
940+
Message,
889941
}
890942

891943
impl Action {
@@ -920,6 +972,14 @@ impl Action {
920972
#[cfg(macos_platform)]
921973
Action::CreateNewTab => "Create new tab",
922974
Action::RequestResize => "Request a resize",
975+
#[cfg(not(web_platform))]
976+
Action::DumpMonitors => "Dump monitor information",
977+
#[cfg(web_platform)]
978+
Action::DumpMonitors => {
979+
"Request permission to query detailed monitor information and dump monitor \
980+
information"
981+
},
982+
Action::Message => "Prints a message through a user wake up",
923983
}
924984
}
925985
}
@@ -942,8 +1002,6 @@ fn decode_cursor(bytes: &[u8]) -> CustomCursorSource {
9421002
fn url_custom_cursor() -> CustomCursorSource {
9431003
use std::sync::atomic::{AtomicU64, Ordering};
9441004

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

9491007
CustomCursor::from_url(
@@ -1041,6 +1099,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
10411099
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
10421100
Binding::new("R", ModifiersState::ALT, Action::RequestResize),
10431101
// M.
1102+
Binding::new("M", ModifiersState::empty(), Action::DumpMonitors),
10441103
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
10451104
Binding::new("M", ModifiersState::ALT, Action::Minimize),
10461105
// N.
@@ -1069,6 +1128,7 @@ const KEY_BINDINGS: &[Binding<&'static str>] = &[
10691128
Binding::new("T", ModifiersState::SUPER, Action::CreateNewTab),
10701129
#[cfg(macos_platform)]
10711130
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
1131+
Binding::new("S", ModifiersState::empty(), Action::Message),
10721132
];
10731133

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

src/changelog/unreleased.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ changelog entry.
4444

4545
- Add `ActiveEventLoop::create_proxy()`.
4646
- On Web, implement `Error` for `platform::web::CustomCursorError`.
47+
- On Web, implement `MonitorHandle` and `VideoModeHandle`.
48+
49+
Without prompting the user for permission, only the current monitor is returned. But when
50+
prompting and being granted permission through
51+
`ActiveEventLoop::request_detailed_monitor_permission()`, access to all monitors and their
52+
information is available. This "detailed monitors" can be used in `Window::set_fullscreen()` as
53+
well.
4754

4855
### Changed
4956

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 `{ x: 0, y: 0 }` 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)