From 400f75a2b33fc765a3173992cccc7d94defafde5 Mon Sep 17 00:00:00 2001 From: Osspial Date: Wed, 4 Dec 2019 03:55:49 -0500 Subject: [PATCH 001/239] Make WindowStore::for_each less terrifying to rebase (#1304) --- src/platform_impl/linux/wayland/event_loop.rs | 93 +++++++++---------- src/platform_impl/linux/wayland/window.rs | 50 +++++----- 2 files changed, 69 insertions(+), 74 deletions(-) diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 146dcb373b..4135e3685f 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -664,60 +664,53 @@ impl EventLoop { } } // process pending resize/refresh - window_target.store.lock().unwrap().for_each( - |newsize, - size, - new_dpi, - refresh, - frame_refresh, - closed, - grab_cursor, - surface, - wid, - frame| { - if let Some(frame) = frame { - if let Some(newsize) = newsize { - // Drop resize events equaled to the current size - if newsize != *size { - let (w, h) = newsize; - frame.resize(w, h); - frame.refresh(); - let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64); - sink.send_window_event( - crate::event::WindowEvent::Resized(logical_size), - wid, - ); - *size = (w, h); - } else { - // Refresh csd, etc, otherwise - frame.refresh(); - } - } else if frame_refresh { + window_target.store.lock().unwrap().for_each(|window| { + if let Some(frame) = window.frame { + if let Some(newsize) = window.newsize { + // Drop resize events equaled to the current size + if newsize != *window.size { + let (w, h) = newsize; + frame.resize(w, h); + frame.refresh(); + let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64); + sink.send_window_event( + crate::event::WindowEvent::Resized(logical_size), + window.wid, + ); + *window.size = (w, h); + } else { + // Refresh csd, etc, otherwise frame.refresh(); - if !refresh { - frame.surface().commit() - } + } + } else if window.frame_refresh { + frame.refresh(); + if !window.refresh { + frame.surface().commit() } } - if let Some(dpi) = new_dpi { - sink.send_window_event( - crate::event::WindowEvent::HiDpiFactorChanged(dpi as f64), - wid, - ); - } - if refresh { - sink.send_window_event(crate::event::WindowEvent::RedrawRequested, wid); - } - if closed { - sink.send_window_event(crate::event::WindowEvent::CloseRequested, wid); - } + } + if let Some(dpi) = window.new_dpi { + sink.send_window_event( + crate::event::WindowEvent::HiDpiFactorChanged(dpi as f64), + window.wid, + ); + } + if window.refresh { + sink.send_window_event(crate::event::WindowEvent::RedrawRequested, window.wid); + } + if window.closed { + sink.send_window_event(crate::event::WindowEvent::CloseRequested, window.wid); + } - if let Some(grab_cursor) = grab_cursor { - let surface = if grab_cursor { Some(surface) } else { None }; - self.cursor_manager.lock().unwrap().grab_pointer(surface); - } - }, - ) + if let Some(grab_cursor) = window.grab_cursor { + let surface = if grab_cursor { + Some(window.surface) + } else { + None + }; + self.cursor_manager.lock().unwrap().grab_pointer(surface); + } + }) } } diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 49849ba6cc..609430a44b 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -385,6 +385,19 @@ pub struct WindowStore { windows: Vec, } +pub struct WindowStoreForEach<'a> { + pub newsize: Option<(u32, u32)>, + pub size: &'a mut (u32, u32), + pub new_dpi: Option, + pub refresh: bool, + pub frame_refresh: bool, + pub closed: bool, + pub grab_cursor: Option, + pub surface: &'a wl_surface::WlSurface, + pub wid: WindowId, + pub frame: Option<&'a mut SWindow>, +} + impl WindowStore { pub fn new() -> WindowStore { WindowStore { @@ -434,34 +447,23 @@ impl WindowStore { pub fn for_each(&mut self, mut f: F) where - F: FnMut( - Option<(u32, u32)>, - &mut (u32, u32), - Option, - bool, - bool, - bool, - Option, - &wl_surface::WlSurface, - WindowId, - Option<&mut SWindow>, - ), + F: FnMut(WindowStoreForEach<'_>), { for window in &mut self.windows { let opt_arc = window.frame.upgrade(); let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); - f( - window.newsize.take(), - &mut *(window.size.lock().unwrap()), - window.new_dpi, - replace(&mut *window.need_refresh.lock().unwrap(), false), - replace(&mut *window.need_frame_refresh.lock().unwrap(), false), - window.closed, - window.cursor_grab_changed.lock().unwrap().take(), - &window.surface, - make_wid(&window.surface), - opt_mutex_lock.as_mut().map(|m| &mut **m), - ); + f(WindowStoreForEach { + newsize: window.newsize.take(), + size: &mut *(window.size.lock().unwrap()), + new_dpi: window.new_dpi, + refresh: replace(&mut *window.need_refresh.lock().unwrap(), false), + frame_refresh: replace(&mut *window.need_frame_refresh.lock().unwrap(), false), + closed: window.closed, + grab_cursor: window.cursor_grab_changed.lock().unwrap().take(), + surface: &window.surface, + wid: make_wid(&window.surface), + frame: opt_mutex_lock.as_mut().map(|m| &mut **m), + }); if let Some(dpi) = window.new_dpi.take() { window.current_dpi = dpi; } From 2888d5c6cfc3cf3136b15b3e2e4556c74ffe3294 Mon Sep 17 00:00:00 2001 From: Osspial Date: Wed, 4 Dec 2019 12:02:33 -0500 Subject: [PATCH 002/239] Fix array_into_iter warning on Windows (#1308) --- src/platform_impl/windows/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 89171874b1..96487ed7ed 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -39,7 +39,7 @@ unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option= 1 { - char::decode_utf16(unicode_bytes.into_iter().cloned()) + char::decode_utf16(unicode_bytes.iter().cloned()) .next() .and_then(|c| c.ok()) } else { From 1a514dff3881771af1ccc5e3ffa786b1ddd0200f Mon Sep 17 00:00:00 2001 From: Murarth Date: Wed, 4 Dec 2019 10:18:20 -0700 Subject: [PATCH 003/239] X11: Fix incorrect DPI factor when waking from suspend (#1303) --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/event_processor.rs | 12 +++++++----- src/platform_impl/linux/x11/monitor.rs | 5 +++++ src/platform_impl/linux/x11/window.rs | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c609d072..271cbea769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - On X11, fix key modifiers being incorrectly reported. - On X11, fix window creation hanging when another window is fullscreen. - On Windows, fix focusing unfocused windows when switching from fullscreen to windowed. +- On X11, fix reporting incorrect DPI factor when waking from suspend. # 0.20.0 Alpha 4 (2019-10-18) diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 2758aa3581..87c644f0c8 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -406,14 +406,16 @@ impl EventProcessor { let last_hidpi_factor = shared_state_lock.last_monitor.hidpi_factor; let new_hidpi_factor = { let window_rect = util::AaRect::new(new_outer_position, new_inner_size); - monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); - let new_hidpi_factor = monitor.hidpi_factor; + let new_monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); - // Avoid caching an invalid dummy monitor handle - if monitor.id != 0 { + if new_monitor.is_dummy() { + // Avoid updating monitor using a dummy monitor handle + last_hidpi_factor + } else { + monitor = new_monitor; shared_state_lock.last_monitor = monitor.clone(); + monitor.hidpi_factor } - new_hidpi_factor }; if last_hidpi_factor != new_hidpi_factor { events.dpi_changed = diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 1fe2fd2000..2612643e2a 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -143,6 +143,11 @@ impl MonitorHandle { } } + pub(crate) fn is_dummy(&self) -> bool { + // Zero is an invalid XID value; no real monitor will have it + self.id == 0 + } + pub fn name(&self) -> Option { Some(self.name.clone()) } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index f967a10060..5829cc0ec0 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -651,7 +651,7 @@ impl UnownedWindow { }; // Don't set fullscreen on an invalid dummy monitor handle - if monitor.id == 0 { + if monitor.is_dummy() { return None; } From 830d47a5f7978bb46741821c181e5a0fa61359f6 Mon Sep 17 00:00:00 2001 From: zserik Date: Sat, 7 Dec 2019 18:22:03 +0100 Subject: [PATCH 004/239] Have EventLoopClosed contain the original event (#1294) * Fix issue #1292 * Remove "optionally" from changelog entry --- CHANGELOG.md | 1 + src/event_loop.rs | 12 ++++++------ src/platform_impl/android/mod.rs | 2 +- src/platform_impl/ios/event_loop.rs | 6 ++++-- src/platform_impl/linux/mod.rs | 2 +- src/platform_impl/linux/wayland/event_loop.rs | 10 ++++++++-- src/platform_impl/linux/x11/mod.rs | 18 +++++++++++++++--- src/platform_impl/macos/event_loop.rs | 6 ++++-- src/platform_impl/web/event_loop/proxy.rs | 2 +- src/platform_impl/windows/event_loop.rs | 4 ++-- 10 files changed, 43 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 271cbea769..a9f2e19d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - On X11, fix window creation hanging when another window is fullscreen. - On Windows, fix focusing unfocused windows when switching from fullscreen to windowed. - On X11, fix reporting incorrect DPI factor when waking from suspend. +- Change `EventLoopClosed` to contain the original event. # 0.20.0 Alpha 4 (2019-10-18) diff --git a/src/event_loop.rs b/src/event_loop.rs index fd9e034ff6..146412cdd0 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -199,7 +199,7 @@ impl EventLoopProxy { /// function. /// /// Returns an `Err` if the associated `EventLoop` no longer exists. - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.event_loop_proxy.send_event(event) } } @@ -211,17 +211,17 @@ impl fmt::Debug for EventLoopProxy { } /// The error that is returned when an `EventLoopProxy` attempts to wake up an `EventLoop` that -/// no longer exists. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct EventLoopClosed; +/// no longer exists. Contains the original event given to `send_event`. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct EventLoopClosed(pub T); -impl fmt::Display for EventLoopClosed { +impl fmt::Display for EventLoopClosed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", error::Error::description(self)) } } -impl error::Error for EventLoopClosed { +impl error::Error for EventLoopClosed { fn description(&self) -> &str { "Tried to wake up a closed `EventLoop`" } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 2c05e4bbe7..5fe4398f49 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -157,7 +157,7 @@ impl EventLoop { } impl EventLoopProxy { - pub fn wakeup(&self) -> Result<(), ::EventLoopClosed> { + pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> { android_glue::wake_event_loop(); Ok(()) } diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index b26b1eb646..657d29ebb4 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -165,8 +165,10 @@ impl EventLoopProxy { } } - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.sender.send(event).map_err(|_| EventLoopClosed)?; + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.sender + .send(event) + .map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?; unsafe { // let the main thread know there's a new event CFRunLoopSourceSignal(self.source); diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 3e56b82498..e30ea88dca 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -650,7 +650,7 @@ impl EventLoop { } impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { match *self { EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event), EventLoopProxy::X(ref proxy) => proxy.send_event(event), diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 4135e3685f..61f24c0a85 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -282,8 +282,14 @@ impl Clone for EventLoopProxy { } impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.user_sender.send(event).map_err(|_| EventLoopClosed) + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.user_sender.send(event).map_err(|e| { + EventLoopClosed(if let ::calloop::channel::SendError::Disconnected(x) = e { + x + } else { + unreachable!() + }) + }) } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 25e8b07ac8..8cfc3556a0 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -1,4 +1,10 @@ -#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] +#![cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] mod dnd; mod event_processor; @@ -425,8 +431,14 @@ impl EventLoopWindowTarget { } impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.user_sender.send(event).map_err(|_| EventLoopClosed) + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.user_sender.send(event).map_err(|e| { + EventLoopClosed(if let ::calloop::channel::SendError::Disconnected(x) = e { + x + } else { + unreachable!() + }) + }) } } diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index d6d89092a8..8469e3ffc8 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -142,8 +142,10 @@ impl Proxy { } } - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.sender.send(event).map_err(|_| EventLoopClosed)?; + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.sender + .send(event) + .map_err(|mpsc::SendError(x)| EventLoopClosed(x))?; unsafe { // let the main thread know there's a new event CFRunLoopSourceSignal(self.source); diff --git a/src/platform_impl/web/event_loop/proxy.rs b/src/platform_impl/web/event_loop/proxy.rs index e98585a3b3..6e569c054c 100644 --- a/src/platform_impl/web/event_loop/proxy.rs +++ b/src/platform_impl/web/event_loop/proxy.rs @@ -11,7 +11,7 @@ impl Proxy { Proxy { runner } } - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.runner.send_event(Event::UserEvent(event)); Ok(()) } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 0968db83a1..3c19702dbf 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -742,13 +742,13 @@ impl Clone for EventLoopProxy { } impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { unsafe { if winuser::PostMessageW(self.target_window, *USER_EVENT_MSG_ID, 0, 0) != 0 { self.event_send.send(event).ok(); Ok(()) } else { - Err(EventLoopClosed) + Err(EventLoopClosed(event)) } } } From 35505a31147b3c39e6c4673f7d3a7e502a2c1552 Mon Sep 17 00:00:00 2001 From: Murarth Date: Sat, 7 Dec 2019 15:51:37 -0700 Subject: [PATCH 005/239] X11: Sync key press/release with window focus (#1296) * X11: Sync key press/release with window focus * When a window loses focus, key release events are issued for all pressed keys * When a window gains focus, key press events are issued for all pressed keys * Adds `is_synthetic` field to `WindowEvent` variant `KeyboardInput` to indicate that these events are synthetic. * Adds `is_synthetic: false` to `WindowEvent::KeyboardInput` events issued on all other platforms * Clarify code with comments --- CHANGELOG.md | 3 + src/event.rs | 9 ++ src/platform_impl/linux/wayland/keyboard.rs | 3 + .../linux/x11/event_processor.rs | 95 +++++++++++++------ src/platform_impl/linux/x11/util/keys.rs | 92 ++++++++++++++++++ src/platform_impl/linux/x11/util/mod.rs | 1 + src/platform_impl/macos/event.rs | 1 + src/platform_impl/macos/view.rs | 3 + .../web/event_loop/window_target.rs | 2 + src/platform_impl/windows/event_loop.rs | 2 + 10 files changed, 180 insertions(+), 31 deletions(-) create mode 100644 src/platform_impl/linux/x11/util/keys.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a9f2e19d1b..20a58b4a5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ - On Windows, fix focusing unfocused windows when switching from fullscreen to windowed. - On X11, fix reporting incorrect DPI factor when waking from suspend. - Change `EventLoopClosed` to contain the original event. +- Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`, + indicating that the event is generated by winit. +- On X11, generate synthetic key events for keys held when a window gains or loses focus. # 0.20.0 Alpha 4 (2019-10-18) diff --git a/src/event.rs b/src/event.rs index ff94aa84e4..318a35786a 100644 --- a/src/event.rs +++ b/src/event.rs @@ -132,6 +132,15 @@ pub enum WindowEvent { KeyboardInput { device_id: DeviceId, input: KeyboardInput, + /// If `true`, the event was generated synthetically by winit + /// in one of the following circumstances: + /// + /// * **X11**: Synthetic key press events are generated for all keys pressed + /// when a window gains focus. Likewise, synthetic key release events + /// are generated for all keys pressed when a window goes out of focus. + /// + /// Otherwise, this value is always `false`. + is_synthetic: bool, }, /// The cursor has moved on the window. diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index 31811422c7..ef026c3a41 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -74,6 +74,7 @@ pub fn init_keyboard( virtual_keycode: vkcode, modifiers: modifiers_tracker.lock().unwrap().clone(), }, + is_synthetic: false, }, }) .unwrap(); @@ -125,6 +126,7 @@ pub fn init_keyboard( virtual_keycode: vkcode, modifiers: my_modifiers.lock().unwrap().clone(), }, + is_synthetic: false, }, }) .unwrap(); @@ -198,6 +200,7 @@ pub fn init_keyboard( virtual_keycode: None, modifiers: ModifiersState::default(), }, + is_synthetic: false, }, }) .unwrap(); diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 87c644f0c8..6f5447264f 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap, ptr, rc::Rc, slice}; +use std::{cell::RefCell, collections::HashMap, rc::Rc, slice}; use libc::{c_char, c_int, c_long, c_uint, c_ulong}; @@ -12,7 +12,7 @@ use util::modifiers::{ModifierKeyState, ModifierKeymap}; use crate::{ dpi::{LogicalPosition, LogicalSize}, - event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent}, + event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, event_loop::EventLoopWindowTarget as RootELW, }; @@ -557,22 +557,13 @@ impl EventProcessor { // value, though this should only be an issue under multiseat configurations. let device = util::VIRTUAL_CORE_KEYBOARD; let device_id = mkdid(device); + let keycode = xkev.keycode; // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with // a keycode of 0. - if xkev.keycode != 0 { - let keysym = unsafe { - let mut keysym = 0; - (wt.xconn.xlib.XLookupString)( - xkev, - ptr::null_mut(), - 0, - &mut keysym, - ptr::null_mut(), - ); - wt.xconn.check_errors().expect("Failed to lookup keysym"); - keysym - }; + if keycode != 0 { + let scancode = keycode - 8; + let keysym = wt.xconn.lookup_keysym(xkev); let virtual_keycode = events::keysym_to_element(keysym as c_uint); update_modifiers!( @@ -588,10 +579,11 @@ impl EventProcessor { device_id, input: KeyboardInput { state, - scancode: xkev.keycode - 8, + scancode, virtual_keycode, modifiers, }, + is_synthetic: false, }, }); } @@ -908,6 +900,10 @@ impl EventProcessor { event: Focused(true), }); + let modifiers = ModifiersState::from_x11(&xev.mods); + + update_modifiers!(modifiers, None); + // The deviceid for this event is for a keyboard instead of a pointer, // so we have to do a little extra work. let pointer_id = self @@ -926,9 +922,12 @@ impl EventProcessor { event: CursorMoved { device_id: mkdid(pointer_id), position, - modifiers: ModifiersState::from_x11(&xev.mods), + modifiers, }, }); + + // Issue key press events for all pressed keys + self.handle_pressed_keys(window_id, ElementState::Pressed, &mut callback); } ffi::XI_FocusOut => { let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; @@ -940,8 +939,13 @@ impl EventProcessor { .unfocus(xev.event) .expect("Failed to unfocus input context"); + let window_id = mkwid(xev.event); + + // Issue key release events for all pressed keys + self.handle_pressed_keys(window_id, ElementState::Released, &mut callback); + callback(Event::WindowEvent { - window_id: mkwid(xev.event), + window_id, event: Focused(false), }) } @@ -1058,20 +1062,8 @@ impl EventProcessor { return; } let scancode = (keycode - 8) as u32; - - let keysym = unsafe { - (wt.xconn.xlib.XKeycodeToKeysym)( - wt.xconn.display, - xev.detail as ffi::KeyCode, - 0, - ) - }; - wt.xconn - .check_errors() - .expect("Failed to lookup raw keysym"); - + let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode); let virtual_keycode = events::keysym_to_element(keysym as c_uint); - let modifiers = self.device_mod_state.modifiers(); callback(Event::DeviceEvent { @@ -1182,4 +1174,45 @@ impl EventProcessor { Err(_) => (), } } + + fn handle_pressed_keys( + &self, + window_id: crate::window::WindowId, + state: ElementState, + callback: &mut F, + ) where + F: FnMut(Event), + { + let wt = get_xtarget(&self.target); + + let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); + let modifiers = self.device_mod_state.modifiers(); + + // Get the set of keys currently pressed and apply Key events to each + let keys = wt.xconn.query_keymap(); + + for keycode in &keys { + if keycode < 8 { + continue; + } + + let scancode = (keycode - 8) as u32; + let keysym = wt.xconn.keycode_to_keysym(keycode); + let virtual_keycode = events::keysym_to_element(keysym as c_uint); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id, + input: KeyboardInput { + scancode, + state, + virtual_keycode, + modifiers, + }, + is_synthetic: true, + }, + }); + } + } } diff --git a/src/platform_impl/linux/x11/util/keys.rs b/src/platform_impl/linux/x11/util/keys.rs new file mode 100644 index 0000000000..fc0c9d9062 --- /dev/null +++ b/src/platform_impl/linux/x11/util/keys.rs @@ -0,0 +1,92 @@ +use std::{iter::Enumerate, ptr, slice::Iter}; + +use super::*; + +pub struct Keymap { + keys: [u8; 32], +} + +pub struct KeymapIter<'a> { + iter: Enumerate>, + index: usize, + item: Option, +} + +impl Keymap { + pub fn iter(&self) -> KeymapIter<'_> { + KeymapIter { + iter: self.keys.iter().enumerate(), + index: 0, + item: None, + } + } +} + +impl<'a> IntoIterator for &'a Keymap { + type Item = ffi::KeyCode; + type IntoIter = KeymapIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl Iterator for KeymapIter<'_> { + type Item = ffi::KeyCode; + + fn next(&mut self) -> Option { + if self.item.is_none() { + while let Some((index, &item)) = self.iter.next() { + if item != 0 { + self.index = index; + self.item = Some(item); + break; + } + } + } + + self.item.take().map(|item| { + debug_assert!(item != 0); + + let bit = first_bit(item); + + if item != bit { + // Remove the first bit; save the rest for further iterations + self.item = Some(item ^ bit); + } + + let shift = bit.trailing_zeros() + (self.index * 8) as u32; + shift as ffi::KeyCode + }) + } +} + +impl XConnection { + pub fn keycode_to_keysym(&self, keycode: ffi::KeyCode) -> ffi::KeySym { + unsafe { (self.xlib.XKeycodeToKeysym)(self.display, keycode, 0) } + } + + pub fn lookup_keysym(&self, xkev: &mut ffi::XKeyEvent) -> ffi::KeySym { + let mut keysym = 0; + + unsafe { + (self.xlib.XLookupString)(xkev, ptr::null_mut(), 0, &mut keysym, ptr::null_mut()); + } + + keysym + } + + pub fn query_keymap(&self) -> Keymap { + let mut keys = [0; 32]; + + unsafe { + (self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char); + } + + Keymap { keys } + } +} + +fn first_bit(b: u8) -> u8 { + 1 << b.trailing_zeros() +} diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 1410da28ff..4e8542361e 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -9,6 +9,7 @@ mod geometry; mod hint; mod icon; mod input; +pub mod keys; mod memory; pub mod modifiers; mod randr; diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index ebad248213..508347e65a 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -264,6 +264,7 @@ pub unsafe fn modifier_event( virtual_keycode, modifiers: event_mods(ns_event), }, + is_synthetic: false, }) } else { None diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 307573db98..8da79e4a82 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -582,6 +582,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { virtual_keycode, modifiers: event_mods(event), }, + is_synthetic: false, }, }; @@ -633,6 +634,7 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) { virtual_keycode, modifiers: event_mods(event), }, + is_synthetic: false, }, }; @@ -741,6 +743,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { virtual_keycode, modifiers: event_mods(event), }, + is_synthetic: false, }, }; diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index d302edb87b..2e0f32dbc2 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -67,6 +67,7 @@ impl WindowTarget { virtual_keycode, modifiers, }, + is_synthetic: false, }, }); }); @@ -83,6 +84,7 @@ impl WindowTarget { virtual_keycode, modifiers, }, + is_synthetic: false, }, }); }); diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3c19702dbf..c1b1a98351 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1277,6 +1277,7 @@ unsafe extern "system" fn public_window_callback( virtual_keycode: vkey, modifiers: event::get_key_mods(), }, + is_synthetic: false, }, }); // Windows doesn't emit a delete character by default, but in order to make it @@ -1305,6 +1306,7 @@ unsafe extern "system" fn public_window_callback( virtual_keycode: vkey, modifiers: event::get_key_mods(), }, + is_synthetic: false, }, }); } From e5291c9e28a430e03cd6c532c410070a2276fa77 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 9 Dec 2019 16:29:50 -0800 Subject: [PATCH 006/239] Release 0.20.0-alpha5 (#1315) --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20a58b4a5c..1de068a8e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# # 0.20.0 Alpha 5 (2019-12-09) + - On macOS, fix application termination on `ControlFlow::Exit` - On Windows, fix missing `ReceivedCharacter` events when Alt is held. - On macOS, stop emitting private corporate characters in `ReceivedCharacter` events. diff --git a/Cargo.toml b/Cargo.toml index 99586de807..95f5c9da53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.20.0-alpha4" +version = "0.20.0-alpha5" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" From 1f81e5c872491c540c50d4b636231ffdd2eb2b98 Mon Sep 17 00:00:00 2001 From: Murarth Date: Wed, 11 Dec 2019 17:23:55 -0700 Subject: [PATCH 007/239] X11: Report `CursorMoved` when touch event occurs (#1297) * X11: Report `CursorMoved` when touch event occurs * Only trigger CursorMoved events for the first touch ID * Fix testing for current touch events * Fix first touch logic --- CHANGELOG.md | 2 + .../linux/x11/event_processor.rs | 48 +++++++++++++++++-- src/platform_impl/linux/x11/mod.rs | 2 + 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1de068a8e0..801eb1162d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`, indicating that the event is generated by winit. - On X11, generate synthetic key events for keys held when a window gains or loses focus. +- On X11, issue a `CursorMoved` event when a `Touch` event occurs, + as X11 implicitly moves the cursor for such events. # 0.20.0 Alpha 4 (2019-10-18) diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 6f5447264f..8c27eefa0d 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -12,7 +12,9 @@ use util::modifiers::{ModifierKeyState, ModifierKeymap}; use crate::{ dpi::{LogicalPosition, LogicalSize}, - event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, + event::{ + DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent, + }, event_loop::EventLoopWindowTarget as RootELW, }; @@ -25,6 +27,9 @@ pub(super) struct EventProcessor { pub(super) target: Rc>, pub(super) mod_keymap: ModifierKeymap, pub(super) device_mod_state: ModifierKeyState, + // Number of touch events currently in progress + pub(super) num_touch: u32, + pub(super) first_touch: Option, } impl EventProcessor { @@ -620,7 +625,7 @@ impl EventProcessor { ElementState::{Pressed, Released}, MouseButton::{Left, Middle, Other, Right}, MouseScrollDelta::LineDelta, - Touch, TouchPhase, + Touch, WindowEvent::{ AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput, MouseWheel, @@ -962,10 +967,27 @@ impl EventProcessor { let dpi_factor = self.with_window(xev.event, |window| window.hidpi_factor()); if let Some(dpi_factor) = dpi_factor { + let id = xev.detail as u64; + let modifiers = self.device_mod_state.modifiers(); let location = LogicalPosition::from_physical( (xev.event_x as f64, xev.event_y as f64), dpi_factor, ); + + // Mouse cursor position changes when touch events are received. + // Only the first concurrently active touch ID moves the mouse cursor. + if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase) + { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::CursorMoved { + device_id: mkdid(util::VIRTUAL_CORE_POINTER), + position: location, + modifiers, + }, + }); + } + callback(Event::WindowEvent { window_id, event: WindowEvent::Touch(Touch { @@ -973,7 +995,7 @@ impl EventProcessor { phase, location, force: None, // TODO - id: xev.detail as u64, + id, }), }) } @@ -1216,3 +1238,23 @@ impl EventProcessor { } } } + +fn is_first_touch(first: &mut Option, num: &mut u32, id: u64, phase: TouchPhase) -> bool { + match phase { + TouchPhase::Started => { + if *num == 0 { + *first = Some(id); + } + *num += 1; + } + TouchPhase::Cancelled | TouchPhase::Ended => { + if *first == Some(id) { + *first = None; + } + *num = num.saturating_sub(1); + } + _ => (), + } + + *first == Some(id) +} diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 8cfc3556a0..71e538dab7 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -199,6 +199,8 @@ impl EventLoop { xi2ext, mod_keymap, device_mod_state: Default::default(), + num_touch: 0, + first_touch: None, }; // Register for device hotplug events From c1b93fc3d07c7b4c5b55e21f9df9e933feb227bc Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Thu, 12 Dec 2019 22:48:32 +0100 Subject: [PATCH 008/239] Add ModifiersChanged event for macOS (#1268) * Add ModifiersChanged event for macOS This implements the macOS portion of #1124. * Fix ModifiersChanged event import * Fix event passing window instead of device id --- src/platform_impl/macos/view.rs | 37 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 8da79e4a82..2b69bfbe50 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -18,8 +18,8 @@ use objc::{ use crate::{ event::{ - DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase, - VirtualKeyCode, WindowEvent, + DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseButton, + MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent, }, platform_impl::platform::{ app_state::AppState, @@ -35,21 +35,13 @@ use crate::{ window::WindowId, }; -#[derive(Default)] -struct Modifiers { - shift_pressed: bool, - ctrl_pressed: bool, - win_pressed: bool, - alt_pressed: bool, -} - struct ViewState { ns_window: id, pub cursor: Arc>, ime_spot: Option<(f64, f64)>, raw_characters: Option, is_key_down: bool, - modifiers: Modifiers, + modifiers: ModifiersState, } pub fn new_view(ns_window: id) -> (IdRef, Weak>) { @@ -654,36 +646,36 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) { if let Some(window_event) = modifier_event( event, NSEventModifierFlags::NSShiftKeyMask, - state.modifiers.shift_pressed, + state.modifiers.shift, ) { - state.modifiers.shift_pressed = !state.modifiers.shift_pressed; + state.modifiers.shift = !state.modifiers.shift; events.push_back(window_event); } if let Some(window_event) = modifier_event( event, NSEventModifierFlags::NSControlKeyMask, - state.modifiers.ctrl_pressed, + state.modifiers.ctrl, ) { - state.modifiers.ctrl_pressed = !state.modifiers.ctrl_pressed; + state.modifiers.ctrl = !state.modifiers.ctrl; events.push_back(window_event); } if let Some(window_event) = modifier_event( event, NSEventModifierFlags::NSCommandKeyMask, - state.modifiers.win_pressed, + state.modifiers.logo, ) { - state.modifiers.win_pressed = !state.modifiers.win_pressed; + state.modifiers.logo = !state.modifiers.logo; events.push_back(window_event); } if let Some(window_event) = modifier_event( event, NSEventModifierFlags::NSAlternateKeyMask, - state.modifiers.alt_pressed, + state.modifiers.alt, ) { - state.modifiers.alt_pressed = !state.modifiers.alt_pressed; + state.modifiers.alt = !state.modifiers.alt; events.push_back(window_event); } @@ -693,6 +685,13 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) { event, }); } + + AppState::queue_event(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::ModifiersChanged { + modifiers: state.modifiers, + }, + }); } trace!("Completed `flagsChanged`"); } From 3e1d16916033aa4113e5d8abed3170bcf8220ad1 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 18 Dec 2019 16:41:44 +0300 Subject: [PATCH 009/239] On Wayland, fix cursor icon updates on window borders when using CSD (#1322) * On Wayland, fix cursor icon updates on window borders when using CSD * Move changelog entry to a right place --- CHANGELOG.md | 2 ++ src/platform_impl/linux/wayland/pointer.rs | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 801eb1162d..b7e89bde18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- On Wayland, fix cursor icon updates on window borders when using CSD. + # # 0.20.0 Alpha 5 (2019-12-09) - On macOS, fix application termination on `ControlFlow::Exit` diff --git a/src/platform_impl/linux/wayland/pointer.rs b/src/platform_impl/linux/wayland/pointer.rs index cad299e470..d2785bfe98 100644 --- a/src/platform_impl/linux/wayland/pointer.rs +++ b/src/platform_impl/linux/wayland/pointer.rs @@ -56,6 +56,13 @@ pub fn implement_pointer( let wid = store.find_wid(&surface); if let Some(wid) = wid { mouse_focus = Some(wid); + + // Reload cursor style only when we enter winit's surface. Calling + // this function every time on `PtrEvent::Enter` could interfere with + // SCTK CSD handling, since it changes cursor icons when you hover + // cursor over the window borders. + cursor_manager.reload_cursor_style(); + sink.send_window_event( WindowEvent::CursorEntered { device_id: crate::event::DeviceId( @@ -75,8 +82,6 @@ pub fn implement_pointer( wid, ); } - - cursor_manager.reload_cursor_style(); } PtrEvent::Leave { surface, .. } => { mouse_focus = None; From 01203b247b804628abf5b215a24f20c2778c4d3a Mon Sep 17 00:00:00 2001 From: hatoo Date: Thu, 19 Dec 2019 18:10:47 +0900 Subject: [PATCH 010/239] Fix run_return in MacOS (#1321) * Fix run_return in MacOS * MacOS: Fix the way of getting a window in run_return * Fix CHANGELOG.md --- CHANGELOG.md | 1 + src/platform_impl/macos/app_state.rs | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7e89bde18..f1ef822fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix application not to terminate on `run_return`. - On Wayland, fix cursor icon updates on window borders when using CSD. # # 0.20.0 Alpha 5 (2019-12-09) diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index bc585a52a5..7e1165677a 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -11,7 +11,7 @@ use std::{ time::Instant, }; -use cocoa::{appkit::NSApp, base::nil}; +use cocoa::{appkit::NSApp, base::nil, foundation::NSString}; use crate::{ event::{Event, StartCause, WindowEvent}, @@ -19,6 +19,7 @@ use crate::{ platform_impl::platform::{observer::EventLoopWaker, util::Never}, window::WindowId, }; +use objc::runtime::Object; lazy_static! { static ref HANDLER: Handler = Default::default(); @@ -275,8 +276,25 @@ impl AppState { HANDLER.set_in_callback(false); } if HANDLER.should_exit() { - let _: () = unsafe { msg_send![NSApp(), terminate: nil] }; - return; + unsafe { + let _: () = msg_send![NSApp(), stop: nil]; + + let windows: *const Object = msg_send![NSApp(), windows]; + let window: *const Object = msg_send![windows, objectAtIndex:0]; + assert_ne!(window, nil); + + let title: *const Object = msg_send![window, title]; + assert_ne!(title, nil); + let postfix = NSString::alloc(nil).init_str("*"); + let some_unique_title: *const Object = + msg_send![title, stringByAppendingString: postfix]; + assert_ne!(some_unique_title, nil); + + // To stop event loop immediately, we need to send some UI event here. + let _: () = msg_send![window, setTitle: some_unique_title]; + // And restore it. + let _: () = msg_send![window, setTitle: title]; + }; } HANDLER.update_start_time(); match HANDLER.get_old_and_new_control_flow() { From 73248bdcedd781c2286e4c36dbf595cde15fa747 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 20 Dec 2019 03:08:28 +0300 Subject: [PATCH 011/239] On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode (#1324) Mutter can reposition window on resize, if it is behind mutter's "bounding box". So, when you start winit window in maximized mode with CSD, mutter places its CSD behind the GNOME's status bar initially, and then sends configure with the exact same size as your current window. If winit decides to optimize calling frame.resize(..) in this case, we won't call set_geometry(we're calling it through resize) and GNOME won't reposition your window to be inside the "bounding box", which is not a desired behavior for the end user. --- CHANGELOG.md | 1 + src/platform_impl/linux/wayland/event_loop.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1ef822fb0..f53bb569ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On macOS, fix application not to terminate on `run_return`. - On Wayland, fix cursor icon updates on window borders when using CSD. +- On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode. # # 0.20.0 Alpha 5 (2019-12-09) diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 61f24c0a85..fba70fa8f6 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -673,20 +673,22 @@ impl EventLoop { window_target.store.lock().unwrap().for_each(|window| { if let Some(frame) = window.frame { if let Some(newsize) = window.newsize { - // Drop resize events equaled to the current size + let (w, h) = newsize; + // mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case + // it overlaps mutter's `bounding box`, so we can't avoid this resize call, + // which calls `set_geometry` under the hood, for now. + frame.resize(w, h); + frame.refresh(); + // Don't send resize event downstream if the new size is identical to the + // current one. if newsize != *window.size { - let (w, h) = newsize; - frame.resize(w, h); - frame.refresh(); let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64); sink.send_window_event( crate::event::WindowEvent::Resized(logical_size), window.wid, ); + *window.size = (w, h); - } else { - // Refresh csd, etc, otherwise - frame.refresh(); } } else if window.frame_refresh { frame.refresh(); From 38c8cb9f4a052518ced5a4a7754f2287103462d2 Mon Sep 17 00:00:00 2001 From: hatoo Date: Fri, 20 Dec 2019 10:03:41 +0900 Subject: [PATCH 012/239] FIX Crash on macOS when starting maximized without decorations (#1323) * FIX #1288 * Fix CHANGELOG.md Co-authored-by: Freya Gentz --- CHANGELOG.md | 1 + src/platform_impl/macos/util/async.rs | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f53bb569ba..c8be853dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix crash when starting maximized without decorations. - On macOS, fix application not to terminate on `run_return`. - On Wayland, fix cursor icon updates on window borders when using CSD. - On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode. diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index ad1a891bc4..c2d117b61c 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -60,11 +60,15 @@ pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowSty } pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask); - dispatch_sync_f( - dispatch_get_main_queue(), - context as *mut _, - set_style_mask_callback, - ); + if msg_send![class!(NSThread), isMainThread] { + set_style_mask_callback(context as *mut _); + } else { + dispatch_sync_f( + dispatch_get_main_queue(), + context as *mut _, + set_style_mask_callback, + ); + } } struct SetContentSizeData { From 2f352ca5cf65c12f3f8e4bba39d41191f0b41767 Mon Sep 17 00:00:00 2001 From: Murarth Date: Sat, 21 Dec 2019 17:47:29 -0700 Subject: [PATCH 013/239] X11: Fix CursorEntered event for non-winit window (#1320) * X11: Fix CursorEntered event for non-winit window * Retry CI Co-authored-by: Osspial --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/event_processor.rs | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8be853dba..5aa8af257c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On X11, fix `CursorEntered` event being generated for non-winit windows. - On macOS, fix crash when starting maximized without decorations. - On macOS, fix application not to terminate on `run_return`. - On Wayland, fix cursor icon updates on window borders when using CSD. diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 8c27eefa0d..9a31a266d6 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -834,14 +834,15 @@ impl EventProcessor { } } } - callback(Event::WindowEvent { - window_id, - event: CursorEntered { device_id }, - }); if let Some(dpi_factor) = self.with_window(xev.event, |window| window.hidpi_factor()) { + callback(Event::WindowEvent { + window_id, + event: CursorEntered { device_id }, + }); + let position = LogicalPosition::from_physical( (xev.event_x as f64, xev.event_y as f64), dpi_factor, From 92741aa4ecb1fd5fa3af2ef4fc6c171e371955e1 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 21 Dec 2019 19:49:44 -0500 Subject: [PATCH 014/239] Fix array_into_iter warning on Windows (#1329) From 82889e23679077e906d0820d98e0249020027f87 Mon Sep 17 00:00:00 2001 From: Justin Miller Date: Sun, 22 Dec 2019 01:04:11 -0500 Subject: [PATCH 015/239] Window::set_minimized (#985) (#990) * Expose set_minimized. Implement for macOS (#985) * Implement set_minimized for Wayland (#985) Co-Authored-By: Victor Berger * Implement set_minimized for Windows (#985) * Remove debug logs (#985) * Implement Window::set_minimized for X11 * Remove extra param from set_window_flags call * Cargo fmt * Add example of usage * Update changelog * Update feature matrix * Cargo fmt * Update example to remove unnecessary event var * Stop setting window styles when minimizing (#985) * Add stub for WASM (#985) Co-authored-by: Victor Berger Co-authored-by: Murarth Co-authored-by: Freya Gentz Co-authored-by: Osspial --- CHANGELOG.md | 2 ++ FEATURES.md | 2 ++ examples/minimize.rs | 35 +++++++++++++++++++++++ src/platform_impl/android/mod.rs | 5 ++++ src/platform_impl/ios/window.rs | 4 +++ src/platform_impl/linux/mod.rs | 8 ++++++ src/platform_impl/linux/wayland/window.rs | 7 +++++ src/platform_impl/linux/x11/window.rs | 29 +++++++++++++++++++ src/platform_impl/macos/window.rs | 19 ++++++++++++ src/platform_impl/web/window.rs | 5 ++++ src/platform_impl/windows/event_loop.rs | 11 +++++++ src/platform_impl/windows/window.rs | 12 ++++++++ src/platform_impl/windows/window_state.rs | 25 ++++++++++++++-- src/window.rs | 10 +++++++ 14 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 examples/minimize.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa8af257c..b1bd2d6614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On all platforms except mobile and WASM, implement `Window::set_minimized`. - On X11, fix `CursorEntered` event being generated for non-winit windows. - On macOS, fix crash when starting maximized without decorations. - On macOS, fix application not to terminate on `run_return`. @@ -50,6 +51,7 @@ - On X11, return dummy monitor data to avoid panicking when no monitors exist. - On X11, prevent stealing input focus when creating a new window. Only steal input focus when entering fullscreen mode. +- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced - On Wayland, add support for set_cursor_visible and set_cursor_grab. - On Wayland, fixed DeviceEvents for relative mouse movement is not always produced. - Removed `derivative` crate dependency. diff --git a/FEATURES.md b/FEATURES.md index b83caac0cc..7a64fcb482 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -80,6 +80,7 @@ If your PR makes notable changes to Winit's features, please update this section - **Window maximization**: The windows created by winit can be maximized upon creation. - **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after creation. +- **Window minimization**: The windows created by winit can be minimized after creation. - **Fullscreen**: The windows created by winit can be put into fullscreen mode. - **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after creation. @@ -173,6 +174,7 @@ Legend: |Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A | |Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| +|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | |Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**| diff --git a/examples/minimize.rs b/examples/minimize.rs new file mode 100644 index 0000000000..5576f53c4e --- /dev/null +++ b/examples/minimize.rs @@ -0,0 +1,35 @@ +extern crate winit; + +use winit::event::{Event, VirtualKeyCode, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::WindowBuilder; + +fn main() { + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + + // Keyboard input event to handle minimize via a hotkey + Event::WindowEvent { + event: WindowEvent::KeyboardInput { input, .. }, + window_id, + } => { + if window_id == window.id() { + // Pressing the 'M' key will minimize the window + if input.virtual_keycode == Some(VirtualKeyCode::M) { + window.set_minimized(true); + } + } + } + _ => *control_flow = ControlFlow::Wait, + }); +} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 5fe4398f49..4304e25482 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -360,6 +360,11 @@ impl Window { Err(ExternalError::NotSupported(NotSupportedError::new())) } + #[inline] + pub fn set_minimized(&self, _minimized: bool) { + unimplemented!() + } + #[inline] pub fn set_maximized(&self, _maximized: bool) { // N/A diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index eea0c304e2..d1ad03f55f 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -169,6 +169,10 @@ impl Inner { debug!("`Window::set_cursor_visible` is ignored on iOS") } + pub fn set_minimized(&self, _minimized: bool) { + warn!("`Window::set_minimized` is ignored on iOS") + } + pub fn set_maximized(&self, _maximized: bool) { warn!("`Window::set_maximized` is ignored on iOS") } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index e30ea88dca..616c02fe61 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -367,6 +367,14 @@ impl Window { } } + #[inline] + pub fn set_minimized(&self, minimized: bool) { + match self { + &Window::X(ref w) => w.set_minimized(minimized), + &Window::Wayland(ref w) => w.set_minimized(minimized), + } + } + #[inline] pub fn fullscreen(&self) -> Option { match self { diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 609430a44b..7faed50277 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -259,6 +259,13 @@ impl Window { *(self.need_frame_refresh.lock().unwrap()) = true; } + pub fn set_minimized(&self, minimized: bool) { + // An app cannot un-minimize itself on Wayland + if minimized { + self.frame.lock().unwrap().set_minimized(); + } + } + pub fn set_maximized(&self, maximized: bool) { if maximized { self.frame.lock().unwrap().set_maximized(); diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 5829cc0ec0..fc12ba0afa 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -749,6 +749,35 @@ impl UnownedWindow { self.xconn.primary_monitor() } + fn set_minimized_inner(&self, minimized: bool) -> util::Flusher<'_> { + unsafe { + if minimized { + let screen = (self.xconn.xlib.XDefaultScreen)(self.xconn.display); + + (self.xconn.xlib.XIconifyWindow)(self.xconn.display, self.xwindow, screen); + + util::Flusher::new(&self.xconn) + } else { + let atom = self.xconn.get_atom_unchecked(b"_NET_ACTIVE_WINDOW\0"); + + self.xconn.send_client_msg( + self.xwindow, + self.root, + atom, + Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), + [1, ffi::CurrentTime as c_long, 0, 0, 0], + ) + } + } + } + + #[inline] + pub fn set_minimized(&self, minimized: bool) { + self.set_minimized_inner(minimized) + .flush() + .expect("Failed to change window minimization"); + } + fn set_maximized_inner(&self, maximized: bool) -> util::Flusher<'_> { let horz_atom = unsafe { self.xconn diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index ab19fe7a67..404eafa564 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -617,6 +617,25 @@ impl UnownedWindow { self.set_maximized(maximized); } + #[inline] + pub fn set_minimized(&self, minimized: bool) { + let is_minimized: BOOL = unsafe { msg_send![*self.ns_window, isMiniaturized] }; + let is_minimized: bool = is_minimized == YES; + if is_minimized == minimized { + return; + } + + if minimized { + unsafe { + NSWindow::miniaturize_(*self.ns_window, *self.ns_window); + } + } else { + unsafe { + NSWindow::deminiaturize_(*self.ns_window, *self.ns_window); + } + } + } + #[inline] pub fn set_maximized(&self, maximized: bool) { let is_zoomed = self.is_zoomed(); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 8752d8b639..c14df77f4b 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -199,6 +199,11 @@ impl Window { } } + #[inline] + pub fn set_minimized(&self, _minimized: bool) { + // Intentionally a no-op, as canvases cannot be 'minimized' + } + #[inline] pub fn set_maximized(&self, _maximized: bool) { // Intentionally a no-op, as canvases cannot be 'maximized' diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index c1b1a98351..183879b297 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1145,7 +1145,18 @@ unsafe extern "system" fn public_window_callback( 0 } + // this is necessary for us to maintain minimize/restore state winuser::WM_SYSCOMMAND => { + if wparam == winuser::SC_RESTORE { + let mut w = subclass_input.window_state.lock(); + w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, false)); + } + if wparam == winuser::SC_MINIMIZE { + let mut w = subclass_input.window_state.lock(); + w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, true)); + } + // Send `WindowEvent::Minimized` here if we decide to implement one + if wparam == winuser::SC_SCREENSAVE { let window_state = subclass_input.window_state.lock(); if window_state.fullscreen.is_some() { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 783ad5e7a8..f647a88cf7 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -452,6 +452,18 @@ impl Window { WindowId(self.window.0) } + #[inline] + pub fn set_minimized(&self, minimized: bool) { + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { + f.set(WindowFlags::MINIMIZED, minimized) + }); + }); + } + #[inline] pub fn set_maximized(&self, maximized: bool) { let window = self.window.clone(); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index be98bcc930..cee8026371 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -79,6 +79,8 @@ bitflags! { /// window's state to match our stored state. This controls whether to accept those changes. const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10; + const MINIMIZED = 1 << 11; + const FULLSCREEN_AND_MASK = !( WindowFlags::DECORATIONS.bits | WindowFlags::RESIZABLE.bits | @@ -212,6 +214,9 @@ impl WindowFlags { if self.contains(WindowFlags::CHILD) { style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually. } + if self.contains(WindowFlags::MINIMIZED) { + style |= WS_MINIMIZE; + } if self.contains(WindowFlags::MAXIMIZED) { style |= WS_MAXIMIZE; } @@ -276,14 +281,30 @@ impl WindowFlags { } } + // Minimize operations should execute after maximize for proper window animations + if diff.contains(WindowFlags::MINIMIZED) { + unsafe { + winuser::ShowWindow( + window, + match new.contains(WindowFlags::MINIMIZED) { + true => winuser::SW_MINIMIZE, + false => winuser::SW_RESTORE, + }, + ); + } + } + if diff != WindowFlags::empty() { let (style, style_ex) = new.to_window_styles(); unsafe { winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0); - winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); - winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); + // This condition is necessary to avoid having an unrestorable window + if !new.contains(WindowFlags::MINIMIZED) { + winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); + winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); + } let mut flags = winuser::SWP_NOZORDER | winuser::SWP_NOMOVE diff --git a/src/window.rs b/src/window.rs index fedd3d0ac1..a6b25ad39d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -572,6 +572,16 @@ impl Window { self.window.set_resizable(resizable) } + /// Sets the window to minimized or back + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect + #[inline] + pub fn set_minimized(&self, minimized: bool) { + self.window.set_minimized(minimized); + } + /// Sets the window to maximized or back. /// /// ## Platform-specific From c10c820311b58314b6b1ee5d3b07ecfdfd00cd41 Mon Sep 17 00:00:00 2001 From: simlay Date: Sun, 22 Dec 2019 00:39:22 -0800 Subject: [PATCH 016/239] Reimplement NativeDisplayMode on iOS for #1310 (#1330) * Reimplement NativeDisplayMode on iOS for #1310 * Type annotations from code review. Co-Authored-By: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Co-authored-by: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> --- src/platform_impl/ios/monitor.rs | 39 ++++++++++++++++++++++---------- src/platform_impl/ios/view.rs | 2 +- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index 14279f6643..ee455eaef8 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -18,31 +18,44 @@ pub struct VideoMode { pub(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate: u16, - pub(crate) screen_mode: id, + pub(crate) screen_mode: NativeDisplayMode, pub(crate) monitor: MonitorHandle, } +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct NativeDisplayMode(pub id); + +unsafe impl Send for NativeDisplayMode {} + +impl Drop for NativeDisplayMode { + fn drop(&mut self) { + unsafe { + let () = msg_send![self.0, release]; + } + } +} + +impl Clone for NativeDisplayMode { + fn clone(&self) -> Self { + unsafe { + let _: id = msg_send![self.0, retain]; + } + NativeDisplayMode(self.0) + } +} + impl Clone for VideoMode { fn clone(&self) -> VideoMode { VideoMode { size: self.size, bit_depth: self.bit_depth, refresh_rate: self.refresh_rate, - screen_mode: unsafe { msg_send![self.screen_mode, retain] }, + screen_mode: self.screen_mode.clone(), monitor: self.monitor.clone(), } } } -impl Drop for VideoMode { - fn drop(&mut self) { - unsafe { - assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS"); - let () = msg_send![self.screen_mode, release]; - } - } -} - impl VideoMode { unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode { assert_main_thread!("`VideoMode` can only be created on the main thread on iOS"); @@ -64,11 +77,13 @@ impl VideoMode { 60 }; let size: CGSize = msg_send![screen_mode, size]; + let screen_mode: id = msg_send![screen_mode, retain]; + let screen_mode = NativeDisplayMode(screen_mode); VideoMode { size: (size.width as u32, size.height as u32), bit_depth: 32, refresh_rate: refresh_rate as u16, - screen_mode: msg_send![screen_mode, retain], + screen_mode, monitor: MonitorHandle::retained_new(uiscreen), } } diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 5868059b47..f729299a3b 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -497,7 +497,7 @@ pub unsafe fn create_window( match window_attributes.fullscreen { Some(Fullscreen::Exclusive(ref video_mode)) => { let uiscreen = video_mode.monitor().ui_screen() as id; - let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode]; + let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; msg_send![window, setScreen:video_mode.monitor().ui_screen()] } Some(Fullscreen::Borderless(ref monitor)) => { From 0c151f9fb36471bdf1425f3d89d1720b54c3fc01 Mon Sep 17 00:00:00 2001 From: Murarth Date: Tue, 30 Jul 2019 23:31:12 -0700 Subject: [PATCH 017/239] Implement changes to `RedrawRequested` event (#1062) * Implement changes to `RedrawRequested` event Implements the changes described in #1041 for the X11 platform and for platform-independent public-facing code. * Fix `request_redraw` example * Fix examples in lib docs * Only issue `RedrawRequested` on final `Expose` event --- CHANGELOG.md | 5 +++ examples/request_redraw.rs | 7 +--- src/event.rs | 26 +++++++++---- src/lib.rs | 9 ++--- src/platform_impl/linux/wayland/event_loop.rs | 5 ++- .../linux/x11/event_processor.rs | 13 ++++--- src/platform_impl/linux/x11/mod.rs | 39 +++++++++++++------ 7 files changed, 67 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1bd2d6614..dd0db09bac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,11 @@ - On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number. - On iOS, RedrawRequested now works for gl/metal backed views. - On iOS, RedrawRequested is generally ordered after EventsCleared. +- Changes to the `RedrawRequested` event (#1041): + - `RedrawRequested` has been moved from `WindowEvent` to `Event`. + - `EventsCleared` has been renamed to `MainEventsCleared`. + - `RedrawRequested` is now issued only after `MainEventsCleared`. + - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/examples/request_redraw.rs b/examples/request_redraw.rs index b55bd970d8..a84cffe9ee 100644 --- a/examples/request_redraw.rs +++ b/examples/request_redraw.rs @@ -20,14 +20,11 @@ fn main() { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit, - Event::EventsCleared => { + Event::MainEventsCleared => { window.request_redraw(); *control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0)) } - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } => { + Event::RedrawRequested(_) => { println!("{:?}", event); } _ => (), diff --git a/src/event.rs b/src/event.rs index 318a35786a..9e3a838d78 100644 --- a/src/event.rs +++ b/src/event.rs @@ -30,9 +30,22 @@ pub enum Event { UserEvent(T), /// Emitted when new events arrive from the OS to be processed. NewEvents(StartCause), - /// Emitted when all of the event loop's events have been processed and control flow is about - /// to be taken away from the program. - EventsCleared, + /// Emitted when all events (except for `RedrawRequested`) have been reported. + /// + /// This event is followed by zero or more instances of `RedrawRequested` + /// and, finally, `RedrawEventsCleared`. + MainEventsCleared, + + /// The OS or application has requested that a window be redrawn. + /// + /// Emitted only after `MainEventsCleared`. + RedrawRequested(WindowId), + + /// Emitted after any `RedrawRequested` events. + /// + /// If there are no `RedrawRequested` events, it is reported immediately after + /// `MainEventsCleared`. + RedrawEventsCleared, /// Emitted when the event loop is being shut down. This is irreversable - if this event is /// emitted, it is guaranteed to be the last event emitted. @@ -53,7 +66,9 @@ impl Event { WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }), DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }), NewEvents(cause) => Ok(NewEvents(cause)), - EventsCleared => Ok(EventsCleared), + MainEventsCleared => Ok(MainEventsCleared), + RedrawRequested(wid) => Ok(RedrawRequested(wid)), + RedrawEventsCleared => Ok(RedrawEventsCleared), LoopDestroyed => Ok(LoopDestroyed), Suspended => Ok(Suspended), Resumed => Ok(Resumed), @@ -194,9 +209,6 @@ pub enum WindowEvent { value: f64, }, - /// The OS or application has requested that the window be redrawn. - RedrawRequested, - /// Touch event has been received Touch(Touch), diff --git a/src/lib.rs b/src/lib.rs index 92b35b410c..9d0340ee2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,19 +48,16 @@ //! //! event_loop.run(move |event, _, control_flow| { //! match event { -//! Event::EventsCleared => { +//! Event::MainEventsCleared => { //! // Application update code. //! //! // Queue a RedrawRequested event. //! window.request_redraw(); //! }, -//! Event::WindowEvent { -//! event: WindowEvent::RedrawRequested, -//! .. -//! } => { +//! Event::RedrawRequested(_) => { //! // Redraw the application. //! // -//! // It's preferrable to render in this event rather than in EventsCleared, since +//! // It's preferrable to render in this event rather than in MainEventsCleared, since //! // rendering in here allows the program to gracefully handle redraws requested //! // by the OS. //! }, diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index fba70fa8f6..97e134ce77 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -526,7 +526,7 @@ impl EventLoop { // send Events cleared { sticky_exit_callback( - crate::event::Event::EventsCleared, + crate::event::Event::MainEventsCleared, &self.window_target, &mut control_flow, &mut callback, @@ -704,7 +704,8 @@ impl EventLoop { ); } if window.refresh { - sink.send_window_event(crate::event::WindowEvent::RedrawRequested, window.wid); + unimplemented!() + //sink.send_window_event(crate::event::WindowEvent::RedrawRequested, window.wid); } if window.closed { sink.send_window_event(crate::event::WindowEvent::CloseRequested, window.wid); diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 9a31a266d6..762066ba25 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -534,13 +534,14 @@ impl EventProcessor { ffi::Expose => { let xev: &ffi::XExposeEvent = xev.as_ref(); - let window = xev.window; - let window_id = mkwid(window); + // Multiple Expose events may be received for subareas of a window. + // We issue `RedrawRequested` only for the last event of such a series. + if xev.count == 0 { + let window = xev.window; + let window_id = mkwid(window); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::RedrawRequested, - }); + callback(Event::RedrawRequested(window_id)); + } } ffi::KeyPress | ffi::KeyRelease => { diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 71e538dab7..a3bdea9c6b 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -45,7 +45,7 @@ use self::{ }; use crate::{ error::OsError as RootOsError, - event::{Event, StartCause, WindowEvent}, + event::{Event, StartCause}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes}, window::WindowAttributes, @@ -151,6 +151,8 @@ impl EventLoop { xconn.update_cached_wm_info(root); + let pending_redraws: Arc>> = Default::default(); + let mut mod_keymap = ModifierKeymap::new(); mod_keymap.reset_from_x_connection(&xconn); @@ -164,7 +166,7 @@ impl EventLoop { xconn, wm_delete_window, net_wm_ping, - pending_redraws: Default::default(), + pending_redraws: pending_redraws.clone(), }), _marker: ::std::marker::PhantomData, }); @@ -227,7 +229,9 @@ impl EventLoop { if evt.readiness.is_readable() { let mut processor = processor.borrow_mut(); let mut pending_events = pending_events.borrow_mut(); - drain_events(&mut processor, &mut pending_events); + let mut pending_redraws = pending_redraws.lock().unwrap(); + + drain_events(&mut processor, &mut pending_events, &mut pending_redraws); } } }) @@ -293,6 +297,15 @@ impl EventLoop { ); } } + // send MainEventsCleared + { + sticky_exit_callback( + crate::event::Event::MainEventsCleared, + &self.target, + &mut control_flow, + &mut callback, + ); + } // Empty the redraw requests { // Release the lock to prevent deadlock @@ -300,20 +313,17 @@ impl EventLoop { for wid in windows { sticky_exit_callback( - Event::WindowEvent { - window_id: crate::window::WindowId(super::WindowId::X(wid)), - event: WindowEvent::RedrawRequested, - }, + Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))), &self.target, &mut control_flow, &mut callback, ); } } - // send Events cleared + // send RedrawEventsCleared { sticky_exit_callback( - crate::event::Event::EventsCleared, + crate::event::Event::RedrawEventsCleared, &self.target, &mut control_flow, &mut callback, @@ -392,8 +402,10 @@ impl EventLoop { fn drain_events(&self) { let mut processor = self.event_processor.borrow_mut(); let mut pending_events = self.pending_events.borrow_mut(); + let wt = get_xtarget(&self.target); + let mut pending_redraws = wt.pending_redraws.lock().unwrap(); - drain_events(&mut processor, &mut pending_events); + drain_events(&mut processor, &mut pending_events, &mut pending_redraws); } fn events_waiting(&self) -> bool { @@ -404,9 +416,14 @@ impl EventLoop { fn drain_events( processor: &mut EventProcessor, pending_events: &mut VecDeque>, + pending_redraws: &mut HashSet, ) { let mut callback = |event| { - pending_events.push_back(event); + if let Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))) = event { + pending_redraws.insert(wid); + } else { + pending_events.push_back(event); + } }; // process all pending events From 8eb7853a1af2100a65ce2d50d97ab858c19792c4 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 26 Aug 2019 22:05:42 -0400 Subject: [PATCH 018/239] Implement revamped RedrawRequested on Windows (#1050) * Move event loop runner to runner module * Implement new redraw API --- examples/window.rs | 5 +- src/platform_impl/windows/event_loop.rs | 453 ++---------------- .../windows/event_loop/runner.rs | 415 ++++++++++++++++ src/platform_impl/windows/window.rs | 21 +- 4 files changed, 466 insertions(+), 428 deletions(-) create mode 100644 src/platform_impl/windows/event_loop/runner.rs diff --git a/examples/window.rs b/examples/window.rs index 12e0593e93..e2265dbda0 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -20,7 +20,10 @@ fn main() { event: WindowEvent::CloseRequested, window_id, } if window_id == window.id() => *control_flow = ControlFlow::Exit, - _ => *control_flow = ControlFlow::Wait, + Event::MainEventsCleared => { + window.request_redraw(); + } + _ => *control_flow = ControlFlow::Poll, } }); } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 183879b297..08b74204ad 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -13,16 +13,14 @@ //! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to //! add a `WindowState` entry to a list of window to be used by the callback. +mod runner; + use parking_lot::Mutex; use std::{ - any::Any, - cell::RefCell, - collections::VecDeque, marker::PhantomData, mem, panic, ptr, rc::Rc, sync::{ - atomic::{AtomicBool, Ordering}, mpsc::{self, Receiver, Sender}, Arc, }, @@ -44,9 +42,10 @@ use winapi::{ }, }; +use self::runner::{ELRShared, EventLoopRunnerShared}; use crate::{ dpi::{LogicalPosition, LogicalSize, PhysicalSize}, - event::{DeviceEvent, Event, Force, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent}, + event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, platform_impl::platform::{ dpi::{ @@ -127,7 +126,6 @@ pub struct EventLoop { pub struct EventLoopWindowTarget { thread_id: DWORD, - trigger_newevents_on_redraw: Arc, thread_msg_target: HWND, pub(crate) runner_shared: EventLoopRunnerShared, } @@ -167,10 +165,7 @@ impl EventLoop { pub fn new_dpi_unaware_any_thread() -> EventLoop { let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() }; - let runner_shared = Rc::new(ELRShared { - runner: RefCell::new(None), - buffer: RefCell::new(VecDeque::new()), - }); + let runner_shared = Rc::new(ELRShared::new()); let (thread_msg_target, thread_msg_sender) = thread_event_target_window(runner_shared.clone()); @@ -179,7 +174,6 @@ impl EventLoop { window_target: RootELW { p: EventLoopWindowTarget { thread_id, - trigger_newevents_on_redraw: Arc::new(AtomicBool::new(true)), thread_msg_target, runner_shared, }, @@ -206,44 +200,23 @@ impl EventLoop { { let event_loop_windows_ref = &self.window_target; - let mut runner = unsafe { - EventLoopRunner::new(self, move |event, control_flow| { - event_handler(event, event_loop_windows_ref, control_flow) - }) - }; - { - let runner_shared = self.window_target.p.runner_shared.clone(); - let mut runner_ref = runner_shared.runner.borrow_mut(); - loop { - let event = runner_shared.buffer.borrow_mut().pop_front(); - match event { - Some(e) => { - runner.process_event(e); - } - None => break, - } - } - *runner_ref = Some(runner); + unsafe { + self.window_target + .p + .runner_shared + .set_runner(self, move |event, control_flow| { + event_handler(event, event_loop_windows_ref, control_flow) + }) } - macro_rules! runner { - () => { - self.window_target - .p - .runner_shared - .runner - .borrow_mut() - .as_mut() - .unwrap() - }; - } + let runner = &self.window_target.p.runner_shared; unsafe { let mut msg = mem::zeroed(); let mut msg_unprocessed = false; 'main: loop { - runner!().new_events(); + runner.new_events(); loop { if !msg_unprocessed { if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) { @@ -255,13 +228,14 @@ impl EventLoop { msg_unprocessed = false; } - runner!().events_cleared(); - if let Some(payload) = runner!().panic_error.take() { + runner.events_cleared(); + if let Err(payload) = runner.take_panic_error() { + runner.destroy_runner(); panic::resume_unwind(payload); } if !msg_unprocessed { - let control_flow = runner!().control_flow; + let control_flow = runner.control_flow(); match control_flow { ControlFlow::Exit => break 'main, ControlFlow::Wait => { @@ -279,8 +253,10 @@ impl EventLoop { } } - runner!().call_event_handler(Event::LoopDestroyed); - *self.window_target.p.runner_shared.runner.borrow_mut() = None; + unsafe { + runner.call_event_handler(Event::LoopDestroyed); + } + runner.destroy_runner(); } pub fn create_proxy(&self) -> EventLoopProxy { @@ -296,7 +272,6 @@ impl EventLoopWindowTarget { pub(crate) fn create_thread_executor(&self) -> EventLoopThreadExecutor { EventLoopThreadExecutor { thread_id: self.thread_id, - trigger_newevents_on_redraw: self.trigger_newevents_on_redraw.clone(), target_window: self.thread_msg_target, } } @@ -317,291 +292,6 @@ fn main_thread_id() -> DWORD { unsafe { MAIN_THREAD_ID } } -pub(crate) type EventLoopRunnerShared = Rc>; -pub(crate) struct ELRShared { - runner: RefCell>>, - buffer: RefCell>>, -} -pub(crate) struct EventLoopRunner { - trigger_newevents_on_redraw: Arc, - control_flow: ControlFlow, - runner_state: RunnerState, - modal_redraw_window: HWND, - in_modal_loop: bool, - in_repaint: bool, - event_handler: Box, &mut ControlFlow)>, - panic_error: Option, -} -type PanicError = Box; - -impl ELRShared { - pub(crate) unsafe fn send_event(&self, event: Event) { - if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { - if let Some(ref mut runner) = *runner_ref { - runner.process_event(event); - - // Dispatch any events that were buffered during the call to `process_event`. - loop { - // We do this instead of using a `while let` loop because if we use a `while let` - // loop the reference returned `borrow_mut()` doesn't get dropped until the end - // of the loop's body and attempts to add events to the event buffer while in - // `process_event` will fail. - let buffered_event_opt = self.buffer.borrow_mut().pop_front(); - match buffered_event_opt { - Some(event) => runner.process_event(event), - None => break, - } - } - - return; - } - } - - // If the runner is already borrowed, we're in the middle of an event loop invocation. Add - // the event to a buffer to be processed later. - self.buffer.borrow_mut().push_back(event) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum RunnerState { - /// The event loop has just been created, and an `Init` event must be sent. - New, - /// The event loop is idling, and began idling at the given instant. - Idle(Instant), - /// The event loop has received a signal from the OS that the loop may resume, but no winit - /// events have been generated yet. We're waiting for an event to be processed or the events - /// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`. - DeferredNewEvents(Instant), - /// The event loop is handling the OS's events and sending them to the user's callback. - /// `NewEvents` has been sent, and `EventsCleared` hasn't. - HandlingEvents, -} - -impl EventLoopRunner { - unsafe fn new(event_loop: &EventLoop, f: F) -> EventLoopRunner - where - F: FnMut(Event, &mut ControlFlow), - { - EventLoopRunner { - trigger_newevents_on_redraw: event_loop - .window_target - .p - .trigger_newevents_on_redraw - .clone(), - control_flow: ControlFlow::default(), - runner_state: RunnerState::New, - in_modal_loop: false, - in_repaint: false, - modal_redraw_window: event_loop.window_target.p.thread_msg_target, - event_handler: mem::transmute::< - Box, &mut ControlFlow)>, - Box, &mut ControlFlow)>, - >(Box::new(f)), - panic_error: None, - } - } - - fn new_events(&mut self) { - self.runner_state = match self.runner_state { - // If we're already handling events or have deferred `NewEvents`, we don't need to do - // do any processing. - RunnerState::HandlingEvents | RunnerState::DeferredNewEvents(..) => self.runner_state, - - // Send the `Init` `NewEvents` and immediately move into event processing. - RunnerState::New => { - self.call_event_handler(Event::NewEvents(StartCause::Init)); - RunnerState::HandlingEvents - } - - // When `NewEvents` gets sent after an idle depends on the control flow... - RunnerState::Idle(wait_start) => { - match self.control_flow { - // If we're polling, send `NewEvents` and immediately move into event processing. - ControlFlow::Poll => { - self.call_event_handler(Event::NewEvents(StartCause::Poll)); - RunnerState::HandlingEvents - }, - // If the user was waiting until a specific time, the `NewEvents` call gets sent - // at varying times depending on the current time. - ControlFlow::WaitUntil(resume_time) => { - match Instant::now() >= resume_time { - // If the current time is later than the requested resume time, we can tell the - // user that the resume time has been reached with `NewEvents` and immdiately move - // into event processing. - true => { - self.call_event_handler(Event::NewEvents(StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - })); - RunnerState::HandlingEvents - }, - // However, if the current time is EARLIER than the requested resume time, we - // don't want to send the `WaitCancelled` event until we know an event is being - // sent. Defer. - false => RunnerState::DeferredNewEvents(wait_start) - } - }, - // If we're waiting, `NewEvents` doesn't get sent until winit gets an event, so - // we defer. - ControlFlow::Wait | - // `Exit` shouldn't really ever get sent here, but if it does do something somewhat sane. - ControlFlow::Exit => RunnerState::DeferredNewEvents(wait_start), - } - } - }; - } - - fn process_event(&mut self, event: Event) { - // If we're in the modal loop, we need to have some mechanism for finding when the event - // queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities - // for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have - // been processed. So, we send WM_PAINT to a dummy window which calls `events_cleared` when - // the events queue has been emptied. - if self.in_modal_loop { - unsafe { - winuser::RedrawWindow( - self.modal_redraw_window, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); - } - } - - // If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're - // already in processing nothing happens with this call. - self.new_events(); - - // Now that an event has been received, we have to send any `NewEvents` calls that were - // deferred. - if let RunnerState::DeferredNewEvents(wait_start) = self.runner_state { - match self.control_flow { - ControlFlow::Exit | ControlFlow::Wait => { - self.call_event_handler(Event::NewEvents(StartCause::WaitCancelled { - start: wait_start, - requested_resume: None, - })) - } - ControlFlow::WaitUntil(resume_time) => { - let start_cause = match Instant::now() >= resume_time { - // If the current time is later than the requested resume time, the resume time - // has been reached. - true => StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - }, - // Otherwise, the requested resume time HASN'T been reached and we send a WaitCancelled. - false => StartCause::WaitCancelled { - start: wait_start, - requested_resume: Some(resume_time), - }, - }; - self.call_event_handler(Event::NewEvents(start_cause)); - } - // This can be reached if the control flow is changed to poll during a `RedrawRequested` - // that was sent after `EventsCleared`. - ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)), - } - } - - self.runner_state = RunnerState::HandlingEvents; - match (self.in_repaint, &event) { - ( - true, - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - }, - ) - | (false, _) => self.call_event_handler(event), - (true, _) => { - self.events_cleared(); - self.new_events(); - self.process_event(event); - } - } - } - - fn events_cleared(&mut self) { - self.in_repaint = false; - - match self.runner_state { - // If we were handling events, send the EventsCleared message. - RunnerState::HandlingEvents => { - self.call_event_handler(Event::EventsCleared); - self.runner_state = RunnerState::Idle(Instant::now()); - } - - // If we *weren't* handling events, we don't have to do anything. - RunnerState::New | RunnerState::Idle(..) => (), - - // Some control flows require a NewEvents call even if no events were received. This - // branch handles those. - RunnerState::DeferredNewEvents(wait_start) => { - match self.control_flow { - // If we had deferred a Poll, send the Poll NewEvents and EventsCleared. - ControlFlow::Poll => { - self.call_event_handler(Event::NewEvents(StartCause::Poll)); - self.call_event_handler(Event::EventsCleared); - } - // If we had deferred a WaitUntil and the resume time has since been reached, - // send the resume notification and EventsCleared event. - ControlFlow::WaitUntil(resume_time) => { - if Instant::now() >= resume_time { - self.call_event_handler(Event::NewEvents( - StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - }, - )); - self.call_event_handler(Event::EventsCleared); - } - } - // If we deferred a wait and no events were received, the user doesn't have to - // get an event. - ControlFlow::Wait | ControlFlow::Exit => (), - } - // Mark that we've entered an idle state. - self.runner_state = RunnerState::Idle(wait_start) - } - } - } - - fn call_event_handler(&mut self, event: Event) { - match event { - Event::NewEvents(_) => self - .trigger_newevents_on_redraw - .store(true, Ordering::Relaxed), - Event::EventsCleared => self - .trigger_newevents_on_redraw - .store(false, Ordering::Relaxed), - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } => self.in_repaint = true, - _ => (), - } - - if self.panic_error.is_none() { - let EventLoopRunner { - ref mut panic_error, - ref mut event_handler, - ref mut control_flow, - .. - } = self; - *panic_error = panic::catch_unwind(panic::AssertUnwindSafe(|| { - if *control_flow != ControlFlow::Exit { - (*event_handler)(event, control_flow); - } else { - (*event_handler)(event, &mut ControlFlow::Exit); - } - })) - .err(); - } - } -} - // Returns true if the wait time was reached, and false if a message must be processed. unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool { let mut msg = mem::zeroed(); @@ -667,7 +357,6 @@ impl Drop for EventLoop { pub(crate) struct EventLoopThreadExecutor { thread_id: DWORD, - trigger_newevents_on_redraw: Arc, target_window: HWND, } @@ -681,10 +370,6 @@ impl EventLoopThreadExecutor { self.thread_id == cur_thread_id } - pub(super) fn trigger_newevents_on_redraw(&self) -> bool { - !self.in_event_loop_thread() || self.trigger_newevents_on_redraw.load(Ordering::Relaxed) - } - /// Executes a function in the event loop thread. If we're already in the event loop thread, /// we just call the function directly. /// @@ -784,12 +469,6 @@ lazy_static! { winuser::RegisterWindowMessageA("Winit::InitialDpiMsg\0".as_ptr() as LPCSTR) } }; - // Message sent by a `Window` if it's requesting a redraw without sending a NewEvents. - pub static ref REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID: u32 = { - unsafe { - winuser::RegisterWindowMessageA("Winit::RequestRedrawNoNewevents\0".as_ptr() as LPCSTR) - } - }; // WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the // documentation in the `window_state` module for more information. pub static ref SET_RETAIN_STATE_ON_SIZE_MSG_ID: u32 = unsafe { @@ -922,21 +601,15 @@ unsafe extern "system" fn public_window_callback( _: UINT_PTR, subclass_input_ptr: DWORD_PTR, ) -> LRESULT { - let subclass_input = &mut *(subclass_input_ptr as *mut SubclassInput); + let subclass_input = &*(subclass_input_ptr as *const SubclassInput); match msg { winuser::WM_ENTERSIZEMOVE => { - let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); - if let Some(ref mut runner) = *runner { - runner.in_modal_loop = true; - } + subclass_input.event_loop_runner.set_modal_loop(true); 0 } winuser::WM_EXITSIZEMOVE => { - let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); - if let Some(ref mut runner) = *runner { - runner.in_modal_loop = false; - } + subclass_input.event_loop_runner.set_modal_loop(false); 0 } winuser::WM_NCCREATE => { @@ -975,48 +648,13 @@ unsafe extern "system" fn public_window_callback( event: Destroyed, }); - Box::from_raw(subclass_input); drop(subclass_input); + Box::from_raw(subclass_input_ptr as *mut SubclassInput); 0 } - _ if msg == *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID => { - use crate::event::WindowEvent::RedrawRequested; - let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); - subclass_input.window_state.lock().queued_out_of_band_redraw = false; - if let Some(ref mut runner) = *runner { - // This check makes sure that calls to `request_redraw()` during `EventsCleared` - // handling dispatch `RedrawRequested` immediately after `EventsCleared`, without - // spinning up a new event loop iteration. We do this because that's what the API - // says to do. - let runner_state = runner.runner_state; - let mut request_redraw = || { - runner.call_event_handler(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: RedrawRequested, - }); - }; - match runner_state { - RunnerState::Idle(..) | RunnerState::DeferredNewEvents(..) => request_redraw(), - RunnerState::HandlingEvents => { - winuser::RedrawWindow( - window, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); - } - _ => (), - } - } - 0 - } winuser::WM_PAINT => { - use crate::event::WindowEvent::RedrawRequested; - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: RedrawRequested, - }); + subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); commctrl::DefSubclassProc(window, msg, wparam, lparam) } @@ -1984,14 +1622,7 @@ unsafe extern "system" fn thread_event_target_callback( winuser::RDW_INTERNALPAINT, ); }; - let in_modal_loop = { - let runner = subclass_input.event_loop_runner.runner.borrow_mut(); - if let Some(ref runner) = *runner { - runner.in_modal_loop - } else { - false - } - }; + let in_modal_loop = subclass_input.event_loop_runner.in_modal_loop(); if in_modal_loop { let mut msg = mem::zeroed(); loop { @@ -2019,21 +1650,19 @@ unsafe extern "system" fn thread_event_target_callback( } } - let mut runner = subclass_input.event_loop_runner.runner.borrow_mut(); - if let Some(ref mut runner) = *runner { - runner.events_cleared(); - match runner.control_flow { - // Waiting is handled by the modal loop. - ControlFlow::Exit | ControlFlow::Wait => runner.new_events(), - ControlFlow::WaitUntil(resume_time) => { - wait_until_time_or_msg(resume_time); - runner.new_events(); - queue_call_again(); - } - ControlFlow::Poll => { - runner.new_events(); - queue_call_again(); - } + let runner = &subclass_input.event_loop_runner; + runner.events_cleared(); + match runner.control_flow() { + // Waiting is handled by the modal loop. + ControlFlow::Exit | ControlFlow::Wait => runner.new_events(), + ControlFlow::WaitUntil(resume_time) => { + wait_until_time_or_msg(resume_time); + runner.new_events(); + queue_call_again(); + } + ControlFlow::Poll => { + runner.new_events(); + queue_call_again(); } } } diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs new file mode 100644 index 0000000000..243763ff59 --- /dev/null +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -0,0 +1,415 @@ +use std::{ + any::Any, + cell::RefCell, + collections::VecDeque, + mem, panic, ptr, + rc::Rc, + time::Instant, +}; + +use winapi::{ + shared::{ + windef::HWND, + }, + um::winuser, +}; + +use crate::{ + event::{Event, StartCause}, + event_loop::ControlFlow, + platform_impl::platform::{ + event_loop::EventLoop, + }, + window::WindowId, +}; + +pub(crate) type EventLoopRunnerShared = Rc>; +pub(crate) struct ELRShared { + runner: RefCell>>, + buffer: RefCell>>, + redraw_buffer: Rc>>, +} +struct EventLoopRunner { + control_flow: ControlFlow, + runner_state: RunnerState, + modal_redraw_window: HWND, + in_modal_loop: bool, + event_handler: Box, &mut ControlFlow)>, + panic_error: Option, + redraw_buffer: Rc>>, +} +pub type PanicError = Box; + +impl ELRShared { + pub(crate) fn new() -> ELRShared { + ELRShared { + runner: RefCell::new(None), + buffer: RefCell::new(VecDeque::new()), + redraw_buffer: Default::default(), + } + } + + pub(crate) unsafe fn set_runner(&self, event_loop: &EventLoop, f: F) + where + F: FnMut(Event, &mut ControlFlow), + { + let mut runner = EventLoopRunner::new(event_loop, self.redraw_buffer.clone(), f); + { + let mut runner_ref = self.runner.borrow_mut(); + loop { + let event = self.buffer.borrow_mut().pop_front(); + match event { + Some(e) => { + runner.process_event(e); + } + None => break, + } + } + *runner_ref = Some(runner); + } + } + + pub(crate) fn destroy_runner(&self) { + *self.runner.borrow_mut() = None; + } + + pub(crate) fn new_events(&self) { + let mut runner_ref = self.runner.borrow_mut(); + if let Some(ref mut runner) = *runner_ref { + runner.new_events(); + } + } + + pub(crate) unsafe fn send_event(&self, event: Event) { + if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { + if let Some(ref mut runner) = *runner_ref { + runner.process_event(event); + + // Dispatch any events that were buffered during the call to `process_event`. + loop { + // We do this instead of using a `while let` loop because if we use a `while let` + // loop the reference returned `borrow_mut()` doesn't get dropped until the end + // of the loop's body and attempts to add events to the event buffer while in + // `process_event` will fail. + let buffered_event_opt = self.buffer.borrow_mut().pop_front(); + match buffered_event_opt { + Some(event) => runner.process_event(event), + None => break, + } + } + + return; + } + } + + // If the runner is already borrowed, we're in the middle of an event loop invocation. Add + // the event to a buffer to be processed later. + self.buffer_event(event); + } + + pub(crate) unsafe fn call_event_handler(&self, event: Event) { + if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { + if let Some(ref mut runner) = *runner_ref { + runner.call_event_handler(event); + return; + } + } + } + + pub(crate) fn events_cleared(&self) { + let mut runner_ref = self.runner.borrow_mut(); + if let Some(ref mut runner) = *runner_ref { + runner.events_cleared(); + } + } + + pub(crate) fn take_panic_error(&self) -> Result<(), PanicError> { + let mut runner_ref = self.runner.borrow_mut(); + if let Some(ref mut runner) = *runner_ref { + runner.take_panic_error() + } else { + Ok(()) + } + } + + pub(crate) fn set_modal_loop(&self, in_modal_loop: bool) { + let mut runner_ref = self.runner.borrow_mut(); + if let Some(ref mut runner) = *runner_ref { + runner.in_modal_loop = in_modal_loop; + } + } + + pub(crate) fn in_modal_loop(&self) -> bool { + let runner = self.runner.borrow(); + if let Some(ref runner) = *runner { + runner.in_modal_loop + } else { + false + } + } + + pub fn control_flow(&self) -> ControlFlow { + let runner_ref = self.runner.borrow(); + if let Some(ref runner) = *runner_ref { + runner.control_flow + } else { + ControlFlow::Exit + } + } + + fn buffer_event(&self, event: Event) { + match event { + Event::RedrawRequested(window_id) => self.redraw_buffer.borrow_mut().push_back(window_id), + _ => self.buffer.borrow_mut().push_back(event), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum RunnerState { + /// The event loop has just been created, and an `Init` event must be sent. + New, + /// The event loop is idling, and began idling at the given instant. + Idle(Instant), + /// The event loop has received a signal from the OS that the loop may resume, but no winit + /// events have been generated yet. We're waiting for an event to be processed or the events + /// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`. + DeferredNewEvents(Instant), + /// The event loop is handling the OS's events and sending them to the user's callback. + /// `NewEvents` has been sent, and `EventsCleared` hasn't. + HandlingEvents, + HandlingRedraw, +} + +impl EventLoopRunner { + unsafe fn new(event_loop: &EventLoop, redraw_buffer: Rc>>, f: F) -> EventLoopRunner + where + F: FnMut(Event, &mut ControlFlow), + { + EventLoopRunner { + control_flow: ControlFlow::default(), + runner_state: RunnerState::New, + in_modal_loop: false, + modal_redraw_window: event_loop.window_target.p.thread_msg_target, + event_handler: mem::transmute::< + Box, &mut ControlFlow)>, + Box, &mut ControlFlow)>, + >(Box::new(f)), + panic_error: None, + redraw_buffer, + } + } + + fn take_panic_error(&mut self) -> Result<(), PanicError> { + match self.panic_error.take() { + Some(err) => Err(err), + None => Ok(()), + } + } + + fn new_events(&mut self) { + self.runner_state = match self.runner_state { + // If we're already handling events or have deferred `NewEvents`, we don't need to do + // do any processing. + RunnerState::HandlingEvents | RunnerState::HandlingRedraw | RunnerState::DeferredNewEvents(..) => self.runner_state, + + // Send the `Init` `NewEvents` and immediately move into event processing. + RunnerState::New => { + self.call_event_handler(Event::NewEvents(StartCause::Init)); + RunnerState::HandlingEvents + } + + // When `NewEvents` gets sent after an idle depends on the control flow... + RunnerState::Idle(wait_start) => { + match self.control_flow { + // If we're polling, send `NewEvents` and immediately move into event processing. + ControlFlow::Poll => { + self.call_event_handler(Event::NewEvents(StartCause::Poll)); + RunnerState::HandlingEvents + }, + // If the user was waiting until a specific time, the `NewEvents` call gets sent + // at varying times depending on the current time. + ControlFlow::WaitUntil(resume_time) => { + match Instant::now() >= resume_time { + // If the current time is later than the requested resume time, we can tell the + // user that the resume time has been reached with `NewEvents` and immdiately move + // into event processing. + true => { + self.call_event_handler(Event::NewEvents(StartCause::ResumeTimeReached { + start: wait_start, + requested_resume: resume_time, + })); + RunnerState::HandlingEvents + }, + // However, if the current time is EARLIER than the requested resume time, we + // don't want to send the `WaitCancelled` event until we know an event is being + // sent. Defer. + false => RunnerState::DeferredNewEvents(wait_start) + } + }, + // If we're waiting, `NewEvents` doesn't get sent until winit gets an event, so + // we defer. + ControlFlow::Wait | + // `Exit` shouldn't really ever get sent here, but if it does do something somewhat sane. + ControlFlow::Exit => RunnerState::DeferredNewEvents(wait_start), + } + } + }; + } + + fn process_event(&mut self, event: Event) { + // If we're in the modal loop, we need to have some mechanism for finding when the event + // queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities + // for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have + // been processed. So, we send WM_PAINT to a dummy window which calls `events_cleared` when + // the events queue has been emptied. + if self.in_modal_loop { + unsafe { + winuser::RedrawWindow( + self.modal_redraw_window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } + } + + // If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're + // already in processing nothing happens with this call. + self.new_events(); + + // Now that an event has been received, we have to send any `NewEvents` calls that were + // deferred. + if let RunnerState::DeferredNewEvents(wait_start) = self.runner_state { + match self.control_flow { + ControlFlow::Exit | ControlFlow::Wait => { + self.call_event_handler(Event::NewEvents(StartCause::WaitCancelled { + start: wait_start, + requested_resume: None, + })) + } + ControlFlow::WaitUntil(resume_time) => { + let start_cause = match Instant::now() >= resume_time { + // If the current time is later than the requested resume time, the resume time + // has been reached. + true => StartCause::ResumeTimeReached { + start: wait_start, + requested_resume: resume_time, + }, + // Otherwise, the requested resume time HASN'T been reached and we send a WaitCancelled. + false => StartCause::WaitCancelled { + start: wait_start, + requested_resume: Some(resume_time), + }, + }; + self.call_event_handler(Event::NewEvents(start_cause)); + } + // This can be reached if the control flow is changed to poll during a `RedrawRequested` + // that was sent after `EventsCleared`. + ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)), + } + } + + match (self.runner_state, &event) { + (RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => self.call_event_handler(event), + (_, Event::RedrawRequested(_)) => { + self.call_event_handler(Event::MainEventsCleared); + self.runner_state = RunnerState::HandlingRedraw; + self.call_event_handler(event); + }, + (RunnerState::HandlingRedraw, _) => { + warn!("Non-redraw event dispatched durning redraw phase"); + self.events_cleared(); + self.new_events(); + self.call_event_handler(event); + } + (_, _) => { + self.runner_state = RunnerState::HandlingEvents; + self.call_event_handler(event); + } + } + } + + fn flush_redraws(&mut self) { + loop { + let redraw_window_opt = self.redraw_buffer.borrow_mut().pop_front(); + match redraw_window_opt { + Some(window_id) => self.process_event(Event::RedrawRequested(window_id)), + None => break, + } + } + } + + fn events_cleared(&mut self) { + match self.runner_state { + // If we were handling events, send the EventsCleared message. + RunnerState::HandlingEvents => { + self.call_event_handler(Event::MainEventsCleared); + self.flush_redraws(); + self.call_event_handler(Event::RedrawEventsCleared); + self.runner_state = RunnerState::Idle(Instant::now()); + } + + RunnerState::HandlingRedraw => { + self.call_event_handler(Event::RedrawEventsCleared); + self.runner_state = RunnerState::Idle(Instant::now()); + } + + // If we *weren't* handling events, we don't have to do anything. + RunnerState::New | RunnerState::Idle(..) => (), + + // Some control flows require a NewEvents call even if no events were received. This + // branch handles those. + RunnerState::DeferredNewEvents(wait_start) => { + match self.control_flow { + // If we had deferred a Poll, send the Poll NewEvents and EventsCleared. + ControlFlow::Poll => { + self.call_event_handler(Event::NewEvents(StartCause::Poll)); + self.call_event_handler(Event::MainEventsCleared); + self.flush_redraws(); + self.call_event_handler(Event::RedrawEventsCleared); + } + // If we had deferred a WaitUntil and the resume time has since been reached, + // send the resume notification and EventsCleared event. + ControlFlow::WaitUntil(resume_time) => { + if Instant::now() >= resume_time { + self.call_event_handler(Event::NewEvents( + StartCause::ResumeTimeReached { + start: wait_start, + requested_resume: resume_time, + }, + )); + self.call_event_handler(Event::MainEventsCleared); + self.flush_redraws(); + self.call_event_handler(Event::RedrawEventsCleared); + } + } + // If we deferred a wait and no events were received, the user doesn't have to + // get an event. + ControlFlow::Wait | ControlFlow::Exit => (), + } + // Mark that we've entered an idle state. + self.runner_state = RunnerState::Idle(wait_start) + } + } + } + + fn call_event_handler(&mut self, event: Event) { + if self.panic_error.is_none() { + let EventLoopRunner { + ref mut panic_error, + ref mut event_handler, + ref mut control_flow, + .. + } = self; + *panic_error = panic::catch_unwind(panic::AssertUnwindSafe(|| { + if *control_flow != ControlFlow::Exit { + (*event_handler)(event, control_flow); + } else { + (*event_handler)(event, &mut ControlFlow::Exit); + } + })) + .err(); + } + } +} diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index f647a88cf7..0877de465f 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -38,7 +38,6 @@ use crate::{ drop_handler::FileDropHandler, event_loop::{ self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID, - REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID, }, icon::{self, IconType, WinIcon}, monitor, @@ -142,20 +141,12 @@ impl Window { #[inline] pub fn request_redraw(&self) { unsafe { - if self.thread_executor.trigger_newevents_on_redraw() { - winuser::RedrawWindow( - self.window.0, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); - } else { - let mut window_state = self.window_state.lock(); - if !window_state.queued_out_of_band_redraw { - window_state.queued_out_of_band_redraw = true; - winuser::PostMessageW(self.window.0, *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID, 0, 0); - } - } + winuser::RedrawWindow( + self.window.0, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); } } From eb38ff453a1895f0a35fa4133fe771db1d47dfc6 Mon Sep 17 00:00:00 2001 From: Osspial Date: Thu, 24 Oct 2019 14:33:50 -0400 Subject: [PATCH 019/239] Run rustfmt --- .../windows/event_loop/runner.rs | 40 +++++++++---------- src/platform_impl/windows/window.rs | 4 +- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index 243763ff59..c9b05fb695 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -1,25 +1,11 @@ -use std::{ - any::Any, - cell::RefCell, - collections::VecDeque, - mem, panic, ptr, - rc::Rc, - time::Instant, -}; +use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::Rc, time::Instant}; -use winapi::{ - shared::{ - windef::HWND, - }, - um::winuser, -}; +use winapi::{shared::windef::HWND, um::winuser}; use crate::{ event::{Event, StartCause}, event_loop::ControlFlow, - platform_impl::platform::{ - event_loop::EventLoop, - }, + platform_impl::platform::event_loop::EventLoop, window::WindowId, }; @@ -159,7 +145,9 @@ impl ELRShared { fn buffer_event(&self, event: Event) { match event { - Event::RedrawRequested(window_id) => self.redraw_buffer.borrow_mut().push_back(window_id), + Event::RedrawRequested(window_id) => { + self.redraw_buffer.borrow_mut().push_back(window_id) + } _ => self.buffer.borrow_mut().push_back(event), } } @@ -182,7 +170,11 @@ enum RunnerState { } impl EventLoopRunner { - unsafe fn new(event_loop: &EventLoop, redraw_buffer: Rc>>, f: F) -> EventLoopRunner + unsafe fn new( + event_loop: &EventLoop, + redraw_buffer: Rc>>, + f: F, + ) -> EventLoopRunner where F: FnMut(Event, &mut ControlFlow), { @@ -211,7 +203,9 @@ impl EventLoopRunner { self.runner_state = match self.runner_state { // If we're already handling events or have deferred `NewEvents`, we don't need to do // do any processing. - RunnerState::HandlingEvents | RunnerState::HandlingRedraw | RunnerState::DeferredNewEvents(..) => self.runner_state, + RunnerState::HandlingEvents + | RunnerState::HandlingRedraw + | RunnerState::DeferredNewEvents(..) => self.runner_state, // Send the `Init` `NewEvents` and immediately move into event processing. RunnerState::New => { @@ -311,12 +305,14 @@ impl EventLoopRunner { } match (self.runner_state, &event) { - (RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => self.call_event_handler(event), + (RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => { + self.call_event_handler(event) + } (_, Event::RedrawRequested(_)) => { self.call_event_handler(Event::MainEventsCleared); self.runner_state = RunnerState::HandlingRedraw; self.call_event_handler(event); - }, + } (RunnerState::HandlingRedraw, _) => { warn!("Non-redraw event dispatched durning redraw phase"); self.events_cleared(); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 0877de465f..452eac17a9 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -36,9 +36,7 @@ use crate::{ platform_impl::platform::{ dpi::{dpi_to_scale_factor, hwnd_dpi}, drop_handler::FileDropHandler, - event_loop::{ - self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID, - }, + event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID}, icon::{self, IconType, WinIcon}, monitor, raw_input::register_all_mice_and_keyboards_for_raw_input, From cdc32eb817f5f1ca4ba35cea7375e1e312530843 Mon Sep 17 00:00:00 2001 From: Heghedus Razvan Date: Fri, 25 Oct 2019 00:27:32 +0300 Subject: [PATCH 020/239] Implemented revamped `RedrawRequested` for linux wayland (#1237) Signed-off-by: Heghedus Razvan --- src/platform_impl/linux/wayland/event_loop.rs | 62 +++++++++++++------ src/platform_impl/linux/wayland/window.rs | 20 ++++-- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 97e134ce77..7ba2c59a67 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -509,24 +509,34 @@ impl EventLoop { ); } } - // do a second run of post-dispatch-triggers, to handle user-generated "request-redraw" - // in response of resize & friends - self.post_dispatch_triggers(); + // send Events cleared { - let mut guard = sink.lock().unwrap(); - guard.empty_with(|evt| { + sticky_exit_callback( + crate::event::Event::MainEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + + // handle request-redraw + { + self.redraw_triggers(|wid, window_target| { sticky_exit_callback( - evt, - &self.window_target, + crate::event::Event::RedrawRequested(crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(wid), + )), + window_target, &mut control_flow, &mut callback, ); }); } - // send Events cleared + + // send RedrawEventsCleared { sticky_exit_callback( - crate::event::Event::MainEventsCleared, + crate::event::Event::RedrawEventsCleared, &self.window_target, &mut control_flow, &mut callback, @@ -652,6 +662,31 @@ impl EventLoopWindowTarget { */ impl EventLoop { + fn redraw_triggers(&mut self, mut callback: F) + where + F: FnMut(WindowId, &RootELW), + { + let window_target = match self.window_target.p { + crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + _ => unreachable!(), + }; + window_target.store.lock().unwrap().for_each_redraw_trigger( + |refresh, frame_refresh, wid, frame| { + if let Some(frame) = frame { + if frame_refresh { + frame.refresh(); + if !refresh { + frame.surface().commit() + } + } + } + if refresh { + callback(wid, &self.window_target); + } + }, + ) + } + fn post_dispatch_triggers(&mut self) { let mut sink = self.sink.lock().unwrap(); let window_target = match self.window_target.p { @@ -690,11 +725,6 @@ impl EventLoop { *window.size = (w, h); } - } else if window.frame_refresh { - frame.refresh(); - if !window.refresh { - frame.surface().commit() - } } } if let Some(dpi) = window.new_dpi { @@ -703,10 +733,6 @@ impl EventLoop { window.wid, ); } - if window.refresh { - unimplemented!() - //sink.send_window_event(crate::event::WindowEvent::RedrawRequested, window.wid); - } if window.closed { sink.send_window_event(crate::event::WindowEvent::CloseRequested, window.wid); } diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 7faed50277..f6ed396410 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -396,8 +396,6 @@ pub struct WindowStoreForEach<'a> { pub newsize: Option<(u32, u32)>, pub size: &'a mut (u32, u32), pub new_dpi: Option, - pub refresh: bool, - pub frame_refresh: bool, pub closed: bool, pub grab_cursor: Option, pub surface: &'a wl_surface::WlSurface, @@ -463,8 +461,6 @@ impl WindowStore { newsize: window.newsize.take(), size: &mut *(window.size.lock().unwrap()), new_dpi: window.new_dpi, - refresh: replace(&mut *window.need_refresh.lock().unwrap(), false), - frame_refresh: replace(&mut *window.need_frame_refresh.lock().unwrap(), false), closed: window.closed, grab_cursor: window.cursor_grab_changed.lock().unwrap().take(), surface: &window.surface, @@ -478,4 +474,20 @@ impl WindowStore { window.closed = false; } } + + pub fn for_each_redraw_trigger(&mut self, mut f: F) + where + F: FnMut(bool, bool, WindowId, Option<&mut SWindow>), + { + for window in &mut self.windows { + let opt_arc = window.frame.upgrade(); + let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); + f( + replace(&mut *window.need_refresh.lock().unwrap(), false), + replace(&mut *window.need_frame_refresh.lock().unwrap(), false), + make_wid(&window.surface), + opt_mutex_lock.as_mut().map(|m| &mut **m), + ); + } + } } From 5b489284e4609a9a3f95e8c1eed8607112516a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n?= Date: Sun, 27 Oct 2019 16:09:54 +0100 Subject: [PATCH 021/239] Implement revamped `RedrawRequested` on macOS (#1235) --- src/platform_impl/macos/app_state.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 7e1165677a..be3aeb1213 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -266,13 +266,11 @@ impl AppState { for event in HANDLER.take_events() { HANDLER.handle_nonuser_event(event); } + HANDLER.handle_nonuser_event(Event::MainEventsCleared); for window_id in HANDLER.should_redraw() { - HANDLER.handle_nonuser_event(Event::WindowEvent { - window_id, - event: WindowEvent::RedrawRequested, - }); + HANDLER.handle_nonuser_event(Event::RedrawRequested(window_id)); } - HANDLER.handle_nonuser_event(Event::EventsCleared); + HANDLER.handle_nonuser_event(Event::RedrawEventsCleared); HANDLER.set_in_callback(false); } if HANDLER.should_exit() { From 133b11fa6da401f0acde942dd3002406ccde7673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n?= Date: Tue, 3 Dec 2019 18:16:06 +0100 Subject: [PATCH 022/239] Implement revamped `RedrawRequested` on Web (#1301) * Implement revamped `RedrawRequested` on Web * Add `web` example --- Cargo.toml | 3 + examples/web.rs | 72 ++++++++++++++++++++++ src/platform_impl/web/event_loop/runner.rs | 15 ++--- 3 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 examples/web.rs diff --git a/Cargo.toml b/Cargo.toml index 95f5c9da53..4887f364dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,3 +117,6 @@ package = "stdweb" version = "=0.4.20" optional = true features = ["experimental_features_which_may_break_on_minor_version_bumps"] + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +console_log = "0.1" diff --git a/examples/web.rs b/examples/web.rs new file mode 100644 index 0000000000..d5b92965af --- /dev/null +++ b/examples/web.rs @@ -0,0 +1,72 @@ +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +pub fn main() { + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + #[cfg(feature = "web-sys")] + { + use winit::platform::web::WindowExtWebSys; + + let canvas = window.canvas(); + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let body = document.body().unwrap(); + + body.append_child(&canvas) + .expect("Append canvas to HTML body"); + } + + #[cfg(feature = "stdweb")] + { + use std_web::web::INode; + use winit::platform::web::WindowExtStdweb; + + let canvas = window.canvas(); + + let document = std_web::web::document(); + let body: std_web::web::Node = document.body().expect("Get HTML body").into(); + + body.append_child(&canvas); + } + + event_loop.run(move |event, _, control_flow| { + #[cfg(feature = "web-sys")] + log::debug!("{:?}", event); + + #[cfg(feature = "stdweb")] + std_web::console!(log, "%s", format!("{:?}", event)); + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } if window_id == window.id() => *control_flow = ControlFlow::Exit, + Event::MainEventsCleared => { + window.request_redraw(); + } + _ => *control_flow = ControlFlow::Wait, + } + }); +} + +#[cfg(feature = "web-sys")] +mod wasm { + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(start)] + pub fn run() { + console_log::init_with_level(log::Level::Debug); + + super::main(); + } +} diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index b3889fad2a..da7d5bad27 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -1,5 +1,5 @@ use super::{backend, state::State}; -use crate::event::{Event, StartCause, WindowEvent}; +use crate::event::{Event, StartCause}; use crate::event_loop as root; use crate::window::WindowId; @@ -127,18 +127,15 @@ impl Shared { if !event_is_start { self.handle_event(event, &mut control); } + self.handle_event(Event::MainEventsCleared, &mut control); + // Collect all of the redraw events to avoid double-locking the RefCell let redraw_events: Vec = self.0.redraw_pending.borrow_mut().drain().collect(); for window_id in redraw_events { - self.handle_event( - Event::WindowEvent { - window_id, - event: WindowEvent::RedrawRequested, - }, - &mut control, - ); + self.handle_event(Event::RedrawRequested(window_id), &mut control); } - self.handle_event(Event::EventsCleared, &mut control); + self.handle_event(Event::RedrawEventsCleared, &mut control); + self.apply_control_flow(control); // If the event loop is closed, it has been closed this iteration and now the closing // event should be emitted From 530ff5420b6f6abba1cb5c1249d58398c91e48e2 Mon Sep 17 00:00:00 2001 From: simlay Date: Sat, 21 Dec 2019 16:35:18 -0800 Subject: [PATCH 023/239] Implement revamped `RedrawRequested` on iOS. (#1299) * Implement revamped `RedrawRequested` on iOS * Added RedrawEventsCleared to events_cleared logic * Fixed from comments * Added RedrawEventsCleared to draw_rect handler. * Fixed out of order `RedrawEventsCleared` events. * cargo fmt --- src/platform_impl/ios/app_state.rs | 22 ++++++++++------------ src/platform_impl/ios/view.rs | 8 ++++---- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 6009a26a7e..e1b82be194 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -12,7 +12,7 @@ use std::{ use objc::runtime::{BOOL, YES}; use crate::{ - event::{Event, StartCause, WindowEvent}, + event::{Event, StartCause}, event_loop::ControlFlow, platform_impl::platform::{ event_loop::{EventHandler, Never}, @@ -51,11 +51,7 @@ enum UserCallbackTransitionResult<'a> { impl Event { fn is_redraw(&self) -> bool { - if let Event::WindowEvent { - window_id: _, - event: WindowEvent::RedrawRequested, - } = self - { + if let Event::RedrawRequested(_) = self { true } else { false @@ -776,16 +772,18 @@ pub unsafe fn handle_main_events_cleared() { // User events are always sent out at the end of the "MainEventLoop" handle_user_events(); - handle_nonuser_event(Event::EventsCleared); + handle_nonuser_event(Event::MainEventsCleared); let mut this = AppState::get_mut(); - let redraw_events = this + let mut redraw_events: Vec> = this .main_events_cleared_transition() .into_iter() - .map(|window| Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::RedrawRequested, - }); + .map(|window| Event::RedrawRequested(RootWindowId(window.into()))) + .collect(); + + if !redraw_events.is_empty() { + redraw_events.push(Event::RedrawEventsCleared); + } drop(this); handle_nonuser_events(redraw_events); diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index f729299a3b..622b351bd7 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -102,10 +102,10 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { unsafe { let window: id = msg_send![object, window]; assert!(!window.is_null()); - app_state::handle_nonuser_event(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::RedrawRequested, - }); + app_state::handle_nonuser_events( + std::iter::once(Event::RedrawRequested(RootWindowId(window.into()))) + .chain(std::iter::once(Event::RedrawEventsCleared)), + ); let superclass: &'static Class = msg_send![object, superclass]; let () = msg_send![super(object, superclass), drawRect: rect]; } From 8a9a9cd92dd4dd43536722350496a44f73e85cfa Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 22 Dec 2019 00:47:39 -0500 Subject: [PATCH 024/239] Move changelog entry into proper position Fix window_run_return Make docs build --- CHANGELOG.md | 10 +++++----- examples/window_run_return.rs | 2 +- src/event_loop.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd0db09bac..c525ac40e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,11 @@ - On X11, generate synthetic key events for keys held when a window gains or loses focus. - On X11, issue a `CursorMoved` event when a `Touch` event occurs, as X11 implicitly moves the cursor for such events. +- Changes to the `RedrawRequested` event (#1041): + - `RedrawRequested` has been moved from `WindowEvent` to `Event`. + - `EventsCleared` has been renamed to `MainEventsCleared`. + - `RedrawRequested` is now issued only after `MainEventsCleared`. + - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. # 0.20.0 Alpha 4 (2019-10-18) @@ -100,11 +105,6 @@ - On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number. - On iOS, RedrawRequested now works for gl/metal backed views. - On iOS, RedrawRequested is generally ordered after EventsCleared. -- Changes to the `RedrawRequested` event (#1041): - - `RedrawRequested` has been moved from `WindowEvent` to `Event`. - - `EventsCleared` has been renamed to `MainEventsCleared`. - - `RedrawRequested` is now issued only after `MainEventsCleared`. - - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index 5096ef315d..05f50e9db9 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -40,7 +40,7 @@ fn main() { quit = true; *control_flow = ControlFlow::Exit; } - Event::EventsCleared => { + Event::MainEventsCleared => { *control_flow = ControlFlow::Exit; } _ => *control_flow = ControlFlow::Wait, diff --git a/src/event_loop.rs b/src/event_loop.rs index 146412cdd0..105af242e8 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -59,7 +59,7 @@ impl fmt::Debug for EventLoopWindowTarget { /// Set by the user callback given to the `EventLoop::run` method. /// -/// Indicates the desired behavior of the event loop after [`Event::EventsCleared`][events_cleared] +/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`][events_cleared] /// is emitted. Defaults to `Poll`. /// /// ## Persistency @@ -68,7 +68,7 @@ impl fmt::Debug for EventLoopWindowTarget { /// are **not** persistent between multiple calls to `run_return` - issuing a new call will reset /// the control flow to `Poll`. /// -/// [events_cleared]: crate::event::Event::EventsCleared +/// [events_cleared]: crate::event::Event::RedrawEventsCleared #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of From a8d6db0fc1ed3f250a7a2a7d3929a3714cea1425 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 22 Dec 2019 11:19:17 -0500 Subject: [PATCH 025/239] Update alpha version in readme.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c0549ad428..5511a4aa17 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ```toml [dependencies] -winit = "0.20.0-alpha4" +winit = "0.20.0-alpha5" ``` ## [Documentation](https://docs.rs/winit) From 25123bed23779d326e7869563245699266436748 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 22 Dec 2019 11:23:27 -0500 Subject: [PATCH 026/239] Rebasing moved the RedrawRequested changelog entry to the wrong position :/ --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c525ac40e4..f2bef0100b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ - On macOS, fix application not to terminate on `run_return`. - On Wayland, fix cursor icon updates on window borders when using CSD. - On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode. +- Changes to the `RedrawRequested` event (#1041): + - `RedrawRequested` has been moved from `WindowEvent` to `Event`. + - `EventsCleared` has been renamed to `MainEventsCleared`. + - `RedrawRequested` is now issued only after `MainEventsCleared`. + - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. # # 0.20.0 Alpha 5 (2019-12-09) @@ -24,11 +29,6 @@ - On X11, generate synthetic key events for keys held when a window gains or loses focus. - On X11, issue a `CursorMoved` event when a `Touch` event occurs, as X11 implicitly moves the cursor for such events. -- Changes to the `RedrawRequested` event (#1041): - - `RedrawRequested` has been moved from `WindowEvent` to `Event`. - - `EventsCleared` has been renamed to `MainEventsCleared`. - - `RedrawRequested` is now issued only after `MainEventsCleared`. - - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. # 0.20.0 Alpha 4 (2019-10-18) From 25e018d1ce914bf6a606720277291266ee159faf Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 22 Dec 2019 11:24:49 -0500 Subject: [PATCH 027/239] Fix extraneous `#` in `0.20.0 Alpha 5` --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2bef0100b..76f5290725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - `RedrawRequested` is now issued only after `MainEventsCleared`. - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. -# # 0.20.0 Alpha 5 (2019-12-09) +# 0.20.0 Alpha 5 (2019-12-09) - On macOS, fix application termination on `ControlFlow::Exit` - On Windows, fix missing `ReceivedCharacter` events when Alt is held. From d59eec4633a1ff10a0c22cdde6725dd14e83536f Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sun, 22 Dec 2019 19:04:09 +0000 Subject: [PATCH 028/239] Add support for Windows Dark Mode (#1217) * Add support for Windows Dark Mode * Add is_dark_mode() getter to WindowExtWindows * Add WindowEvent::DarkModeChanged * Add support for dark mode in Windows 10 builds > 18362 * Change strategy for querying windows 10 build version * Drop window state before sending event Co-Authored-By: daxpedda * Change implementation of windows dark mode support * Expand supported range of windows 10 versions with dark mode * Use get_function! macro where possible * Minor style fixes * Improve documentation for ThemeChanged * Use `as` conversion for `BOOL` * Correct CHANGELOG entry for dark mode Co-authored-by: daxpedda Co-authored-by: Osspial --- CHANGELOG.md | 2 + FEATURES.md | 1 + src/event.rs | 10 +- src/platform/windows.rs | 8 + src/platform_impl/windows/dark_mode.rs | 216 ++++++++++++++++++++++ src/platform_impl/windows/event_loop.rs | 23 +++ src/platform_impl/windows/mod.rs | 1 + src/platform_impl/windows/window.rs | 19 +- src/platform_impl/windows/window_state.rs | 3 + src/window.rs | 6 + 10 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 src/platform_impl/windows/dark_mode.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f5290725..ddb3d62936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - On macOS, fix application not to terminate on `run_return`. - On Wayland, fix cursor icon updates on window borders when using CSD. - On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode. +- On Windows, theme the title bar according to whether the system theme is "Light" or "Dark". +- Added `WindowEvent::ThemeChanged` variant to handle changes to the system theme. Currently only implemented on Windows. - Changes to the `RedrawRequested` event (#1041): - `RedrawRequested` has been moved from `WindowEvent` to `Event`. - `EventsCleared` has been renamed to `MainEventsCleared`. diff --git a/FEATURES.md b/FEATURES.md index 7a64fcb482..3d43958807 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -117,6 +117,7 @@ If your PR makes notable changes to Winit's features, please update this section * Setting the taskbar icon * Setting the parent window * `WS_EX_NOREDIRECTIONBITMAP` support +* Theme the title bar according to Windows 10 Dark Mode setting ### macOS * Window activation policy diff --git a/src/event.rs b/src/event.rs index 9e3a838d78..c2bb0eefc7 100644 --- a/src/event.rs +++ b/src/event.rs @@ -10,7 +10,7 @@ use std::path::PathBuf; use crate::{ dpi::{LogicalPosition, LogicalSize}, platform_impl, - window::WindowId, + window::{Theme, WindowId}, }; /// Describes a generic event. @@ -222,6 +222,14 @@ pub enum WindowEvent { /// /// For more information about DPI in general, see the [`dpi`](crate::dpi) module. HiDpiFactorChanged(f64), + + /// The system window theme has changed. + /// + /// Applications might wish to react to this to change the theme of the content of the window + /// when the system changes the window theme. + /// + /// At the moment this is only supported on Windows. + ThemeChanged(Theme), } /// Identifier of an input device. diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 3ab9b1dde6..6e43734c36 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -77,6 +77,9 @@ pub trait WindowExtWindows { /// This sets `ICON_BIG`. A good ceiling here is 256x256. fn set_taskbar_icon(&self, taskbar_icon: Option); + + /// Whether the system theme is currently Windows 10's "Dark Mode". + fn is_dark_mode(&self) -> bool; } impl WindowExtWindows for Window { @@ -94,6 +97,11 @@ impl WindowExtWindows for Window { fn set_taskbar_icon(&self, taskbar_icon: Option) { self.window.set_taskbar_icon(taskbar_icon) } + + #[inline] + fn is_dark_mode(&self) -> bool { + self.window.is_dark_mode() + } } /// Additional methods on `WindowBuilder` that are specific to Windows. diff --git a/src/platform_impl/windows/dark_mode.rs b/src/platform_impl/windows/dark_mode.rs new file mode 100644 index 0000000000..c50c4f21af --- /dev/null +++ b/src/platform_impl/windows/dark_mode.rs @@ -0,0 +1,216 @@ +/// This is a simple implementation of support for Windows Dark Mode, +/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode +use std::ffi::OsStr; +use std::os::windows::ffi::OsStrExt; + +use winapi::{ + shared::{ + basetsd::SIZE_T, + minwindef::{BOOL, DWORD, UINT, ULONG, WORD}, + ntdef::{LPSTR, NTSTATUS, NT_SUCCESS, PVOID, WCHAR}, + windef::HWND, + }, + um::{libloaderapi, uxtheme, winuser}, +}; + +lazy_static! { + static ref WIN10_BUILD_VERSION: Option = { + // FIXME: RtlGetVersion is a documented windows API, + // should be part of winapi! + + #[allow(non_snake_case)] + #[repr(C)] + struct OSVERSIONINFOW { + dwOSVersionInfoSize: ULONG, + dwMajorVersion: ULONG, + dwMinorVersion: ULONG, + dwBuildNumber: ULONG, + dwPlatformId: ULONG, + szCSDVersion: [WCHAR; 128], + } + + type RtlGetVersion = unsafe extern "system" fn (*mut OSVERSIONINFOW) -> NTSTATUS; + let handle = get_function!("ntdll.dll", RtlGetVersion); + + if let Some(rtl_get_version) = handle { + unsafe { + let mut vi = OSVERSIONINFOW { + dwOSVersionInfoSize: 0, + dwMajorVersion: 0, + dwMinorVersion: 0, + dwBuildNumber: 0, + dwPlatformId: 0, + szCSDVersion: [0; 128], + }; + + let status = (rtl_get_version)(&mut vi as _); + assert!(NT_SUCCESS(status)); + + if vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 { + Some(vi.dwBuildNumber) + } else { + None + } + } + } else { + None + } + }; + + static ref DARK_MODE_SUPPORTED: bool = { + // We won't try to do anything for windows versions < 17763 + // (Windows 10 October 2018 update) + match *WIN10_BUILD_VERSION { + Some(v) => v >= 17763, + None => false + } + }; + + static ref DARK_THEME_NAME: Vec = widestring("DarkMode_Explorer"); + static ref LIGHT_THEME_NAME: Vec = widestring(""); +} + +/// Attempt to set dark mode on a window, if necessary. +/// Returns true if dark mode was set, false if not. +pub fn try_dark_mode(hwnd: HWND) -> bool { + if *DARK_MODE_SUPPORTED { + let is_dark_mode = should_use_dark_mode(); + + let theme_name = if is_dark_mode { + DARK_THEME_NAME.as_ptr() + } else { + LIGHT_THEME_NAME.as_ptr() + }; + + unsafe { + assert_eq!( + 0, + uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) + ); + + set_dark_mode_for_window(hwnd, is_dark_mode) + } + + is_dark_mode + } else { + false + } +} + +fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) { + // Uses Windows undocumented API SetWindowCompositionAttribute, + // as seen in win32-darkmode example linked at top of file. + + type SetWindowCompositionAttribute = + unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL; + + #[allow(non_snake_case)] + type WINDOWCOMPOSITIONATTRIB = u32; + const WCA_USEDARKMODECOLORS: WINDOWCOMPOSITIONATTRIB = 26; + + #[allow(non_snake_case)] + #[repr(C)] + struct WINDOWCOMPOSITIONATTRIBDATA { + Attrib: WINDOWCOMPOSITIONATTRIB, + pvData: PVOID, + cbData: SIZE_T, + } + + lazy_static! { + static ref SET_WINDOW_COMPOSITION_ATTRIBUTE: Option = + get_function!("user32.dll", SetWindowCompositionAttribute); + } + + if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE { + unsafe { + // SetWindowCompositionAttribute needs a bigbool (i32), not bool. + let mut is_dark_mode_bigbool = is_dark_mode as BOOL; + + let mut data = WINDOWCOMPOSITIONATTRIBDATA { + Attrib: WCA_USEDARKMODECOLORS, + pvData: &mut is_dark_mode_bigbool as *mut _ as _, + cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _, + }; + + assert_eq!( + 1, + set_window_composition_attribute(hwnd, &mut data as *mut _) + ); + } + } +} + +fn should_use_dark_mode() -> bool { + should_apps_use_dark_mode() && !is_high_contrast() +} + +fn should_apps_use_dark_mode() -> bool { + type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool; + lazy_static! { + static ref SHOULD_APPS_USE_DARK_MODE: Option = { + unsafe { + const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: WORD = 132; + + let module = libloaderapi::LoadLibraryA("uxtheme.dll\0".as_ptr() as _); + + if module.is_null() { + return None; + } + + let handle = libloaderapi::GetProcAddress( + module, + winuser::MAKEINTRESOURCEA(UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL), + ); + + if handle.is_null() { + None + } else { + Some(std::mem::transmute(handle)) + } + } + }; + } + + SHOULD_APPS_USE_DARK_MODE + .map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() }) + .unwrap_or(false) +} + +// FIXME: This definition was missing from winapi. Can remove from +// here and use winapi once the following PR is released: +// https://github.com/retep998/winapi-rs/pull/815 +#[repr(C)] +#[allow(non_snake_case)] +struct HIGHCONTRASTA { + cbSize: UINT, + dwFlags: DWORD, + lpszDefaultScheme: LPSTR, +} + +const HCF_HIGHCONTRASTON: DWORD = 1; + +fn is_high_contrast() -> bool { + let mut hc = HIGHCONTRASTA { + cbSize: 0, + dwFlags: 0, + lpszDefaultScheme: std::ptr::null_mut(), + }; + + let ok = unsafe { + winuser::SystemParametersInfoA( + winuser::SPI_GETHIGHCONTRAST, + std::mem::size_of_val(&hc) as _, + &mut hc as *mut _ as _, + 0, + ) + }; + + (ok > 0) && ((HCF_HIGHCONTRASTON & hc.dwFlags) == 1) +} + +fn widestring(src: &'static str) -> Vec { + OsStr::new(src) + .encode_wide() + .chain(Some(0).into_iter()) + .collect() +} diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 08b74204ad..c1abdcc747 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -48,6 +48,7 @@ use crate::{ event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, platform_impl::platform::{ + dark_mode::try_dark_mode, dpi::{ become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_scale_factor, }, @@ -1540,6 +1541,28 @@ unsafe extern "system" fn public_window_callback( 0 } + winuser::WM_SETTINGCHANGE => { + use crate::event::WindowEvent::ThemeChanged; + + let is_dark_mode = try_dark_mode(window); + let mut window_state = subclass_input.window_state.lock(); + let changed = window_state.is_dark_mode != is_dark_mode; + + if changed { + use crate::window::Theme::*; + let theme = if is_dark_mode { Dark } else { Light }; + + window_state.is_dark_mode = is_dark_mode; + mem::drop(window_state); + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ThemeChanged(theme), + }); + } + + commctrl::DefSubclassProc(window, msg, wparam, lparam) + } + _ => { if msg == *DESTROY_MSG_ID { winuser::DestroyWindow(window); diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 34e9327d96..47bf7fb53f 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -69,6 +69,7 @@ impl WindowId { #[macro_use] mod util; +mod dark_mode; mod dpi; mod drop_handler; mod event; diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 452eac17a9..08e884d4a1 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -34,6 +34,7 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ + dark_mode::try_dark_mode, dpi::{dpi_to_scale_factor, hwnd_dpi}, drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID}, @@ -696,6 +697,11 @@ impl Window { pub fn set_ime_position(&self, _logical_spot: LogicalPosition) { unimplemented!(); } + + #[inline] + pub fn is_dark_mode(&self) -> bool { + self.window_state.lock().is_dark_mode + } } impl Drop for Window { @@ -903,8 +909,19 @@ unsafe fn init( window_flags.set(WindowFlags::VISIBLE, attributes.visible); window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized); + // If the system theme is dark, we need to set the window theme now + // before we update the window flags (and possibly show the + // window for the first time). + let dark_mode = try_dark_mode(real_window.0); + let window_state = { - let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor); + let window_state = WindowState::new( + &attributes, + window_icon, + taskbar_icon, + dpi_factor, + dark_mode, + ); let window_state = Arc::new(Mutex::new(window_state)); WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); window_state diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index cee8026371..97874e5935 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -32,6 +32,7 @@ pub struct WindowState { /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple /// times in `EventsCleared`. pub queued_out_of_band_redraw: bool, + pub is_dark_mode: bool, pub high_surrogate: Option, window_flags: WindowFlags, } @@ -98,6 +99,7 @@ impl WindowState { window_icon: Option, taskbar_icon: Option, dpi_factor: f64, + is_dark_mode: bool, ) -> WindowState { WindowState { mouse: MouseProperties { @@ -117,6 +119,7 @@ impl WindowState { fullscreen: None, queued_out_of_band_redraw: false, + is_dark_mode, high_surrogate: None, window_flags: WindowFlags::empty(), } diff --git a/src/window.rs b/src/window.rs index a6b25ad39d..4388524077 100644 --- a/src/window.rs +++ b/src/window.rs @@ -857,3 +857,9 @@ pub enum Fullscreen { Exclusive(VideoMode), Borderless(MonitorHandle), } + +#[derive(Clone, Debug, PartialEq)] +pub enum Theme { + Light, + Dark, +} From 5d99316c9678a92d81c0fc39b33f3cbcc4d47a8d Mon Sep 17 00:00:00 2001 From: hatoo Date: Wed, 25 Dec 2019 03:56:56 +0900 Subject: [PATCH 029/239] macOS: Don't change fullscreen state during fullscreen transition (#1331) * Register windowWillExitFullScreen * On macOS: Do not toggle fullscreen during fullscreen transition * Add CHANGELOG Co-authored-by: Freya Gentz --- CHANGELOG.md | 1 + src/platform_impl/macos/window.rs | 16 +++++++- src/platform_impl/macos/window_delegate.rs | 47 +++++++++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddb3d62936..f7210605a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix error when `set_fullscreen` is called during fullscreen transition. - On all platforms except mobile and WASM, implement `Window::set_minimized`. - On X11, fix `CursorEntered` event being generated for non-winit windows. - On macOS, fix crash when starting maximized without decorations. diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 404eafa564..ee95f17a91 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -243,6 +243,13 @@ lazy_static! { pub struct SharedState { pub resizable: bool, pub fullscreen: Option, + // This is true between windowWillEnterFullScreen and windowDidEnterFullScreen + // or windowWillExitFullScreen and windowDidExitFullScreen. + // We must not toggle fullscreen when this is true. + pub in_fullscreen_transition: bool, + // If it is attempted to toggle fullscreen when in_fullscreen_transition is true, + // Set target_fullscreen and do after fullscreen transition is end. + pub target_fullscreen: Option>, pub maximized: bool, pub standard_frame: Option, is_simple_fullscreen: bool, @@ -661,11 +668,18 @@ impl UnownedWindow { #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { trace!("Locked shared state in `set_fullscreen`"); - let shared_state_lock = self.shared_state.lock().unwrap(); + let mut shared_state_lock = self.shared_state.lock().unwrap(); if shared_state_lock.is_simple_fullscreen { trace!("Unlocked shared state in `set_fullscreen`"); return; } + if shared_state_lock.in_fullscreen_transition { + // We can't set fullscreen here. + // Set fullscreen after transition. + shared_state_lock.target_fullscreen = Some(fullscreen); + trace!("Unlocked shared state in `set_fullscreen`"); + return; + } let old_fullscreen = shared_state_lock.fullscreen.clone(); if fullscreen == old_fullscreen { trace!("Unlocked shared state in `set_fullscreen`"); diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index cb1bf3c26e..ecdd4eeff1 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -195,6 +195,10 @@ lazy_static! { sel!(windowDidExitFullScreen:), window_did_exit_fullscreen as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(windowWillExitFullScreen:), + window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(windowDidFailToEnterFullScreen:), window_did_fail_to_enter_fullscreen as extern "C" fn(&Object, Sel, id), @@ -419,13 +423,27 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { shared_state.fullscreen = Some(Fullscreen::Borderless(window.current_monitor())) } } - + shared_state.in_fullscreen_transition = true; trace!("Unlocked shared state in `window_will_enter_fullscreen`"); }) }); trace!("Completed `windowWillEnterFullscreen:`"); } +/// Invoked when before exit fullscreen +extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowWillExitFullScreen:`"); + with_state(this, |state| { + state.with_window(|window| { + trace!("Locked shared state in `window_will_exit_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.in_fullscreen_transition = true; + trace!("Unlocked shared state in `window_will_exit_fullscreen`"); + }); + }); + trace!("Completed `windowWillExitFullScreen:`"); +} + extern "C" fn window_will_use_fullscreen_presentation_options( _this: &Object, _: Sel, @@ -451,6 +469,17 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { trace!("Triggered `windowDidEnterFullscreen:`"); with_state(this, |state| { state.initial_fullscreen = false; + state.with_window(|window| { + trace!("Locked shared state in `window_did_enter_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.in_fullscreen_transition = false; + let target_fullscreen = shared_state.target_fullscreen.take(); + trace!("Unlocked shared state in `window_did_enter_fullscreen`"); + drop(shared_state); + if let Some(target_fullscreen) = target_fullscreen { + window.set_fullscreen(target_fullscreen); + } + }); }); trace!("Completed `windowDidEnterFullscreen:`"); } @@ -461,6 +490,15 @@ extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { with_state(this, |state| { state.with_window(|window| { window.restore_state_from_fullscreen(); + trace!("Locked shared state in `window_did_exit_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.in_fullscreen_transition = false; + let target_fullscreen = shared_state.target_fullscreen.take(); + trace!("Unlocked shared state in `window_did_exit_fullscreen`"); + drop(shared_state); + if let Some(target_fullscreen) = target_fullscreen { + window.set_fullscreen(target_fullscreen); + } }) }); trace!("Completed `windowDidExitFullscreen:`"); @@ -485,6 +523,13 @@ extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { extern "C" fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) { trace!("Triggered `windowDidFailToEnterFullscreen:`"); with_state(this, |state| { + state.with_window(|window| { + trace!("Locked shared state in `window_did_fail_to_enter_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.in_fullscreen_transition = false; + shared_state.target_fullscreen = None; + trace!("Unlocked shared state in `window_did_fail_to_enter_fullscreen`"); + }); if state.initial_fullscreen { let _: () = unsafe { msg_send![*state.ns_window, From cc206d31b7307980960df979f1edec3626fe2d32 Mon Sep 17 00:00:00 2001 From: Osspial Date: Fri, 27 Dec 2019 16:26:23 -0500 Subject: [PATCH 030/239] Implement windows focus key press/release on Windows (#1307) * X11: Sync key press/release with window focus * When a window loses focus, key release events are issued for all pressed keys * When a window gains focus, key press events are issued for all pressed keys * Adds `is_synthetic` field to `WindowEvent` variant `KeyboardInput` to indicate that these events are synthetic. * Adds `is_synthetic: false` to `WindowEvent::KeyboardInput` events issued on all other platforms * Implement windows focus key press/release on Windows * Docs Co-authored-by: Murarth --- CHANGELOG.md | 3 ++ src/event.rs | 3 +- src/platform_impl/windows/event.rs | 10 ++++++ src/platform_impl/windows/event_loop.rs | 44 +++++++++++++++++++++++-- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7210605a1..d4ac899329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ - On X11, generate synthetic key events for keys held when a window gains or loses focus. - On X11, issue a `CursorMoved` event when a `Touch` event occurs, as X11 implicitly moves the cursor for such events. +- Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`, + indicating that the event is generated by winit. +- On X11 and Windows, generate synthetic key events for keys held when a window gains or loses focus. # 0.20.0 Alpha 4 (2019-10-18) diff --git a/src/event.rs b/src/event.rs index c2bb0eefc7..a9be124a3a 100644 --- a/src/event.rs +++ b/src/event.rs @@ -150,9 +150,10 @@ pub enum WindowEvent { /// If `true`, the event was generated synthetically by winit /// in one of the following circumstances: /// - /// * **X11**: Synthetic key press events are generated for all keys pressed + /// * Synthetic key press events are generated for all keys pressed /// when a window gains focus. Likewise, synthetic key release events /// are generated for all keys pressed when a window goes out of focus. + /// ***Currently, this is only functional on X11 and Windows*** /// /// Otherwise, this value is always `false`. is_synthetic: bool, diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 96487ed7ed..60f6a9253d 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -27,6 +27,16 @@ pub fn get_key_mods() -> ModifiersState { mods } +pub fn get_pressed_keys() -> impl Iterator { + let mut keyboard_state = vec![0u8; 256]; + unsafe { winuser::GetKeyboardState(keyboard_state.as_mut_ptr()) }; + keyboard_state + .into_iter() + .enumerate() + .filter(|(_, p)| (*p & (1 << 7)) != 0) // whether or not a key is pressed is communicated via the high-order bit + .map(|(i, _)| i as c_int) +} + unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option { let mut unicode_bytes = [0u16; 5]; let len = winuser::ToUnicodeEx( diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index c1abdcc747..9d6b7eff84 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1421,7 +1421,27 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_SETFOCUS => { - use crate::event::WindowEvent::Focused; + use crate::event::{ElementState::Released, WindowEvent::Focused}; + for windows_keycode in event::get_pressed_keys() { + let scancode = + winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC); + let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + scancode, + virtual_keycode, + state: Released, + modifiers: event::get_key_mods(), + }, + is_synthetic: true, + }, + }) + } + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Focused(true), @@ -1431,7 +1451,27 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_KILLFOCUS => { - use crate::event::WindowEvent::Focused; + use crate::event::{ElementState::Released, WindowEvent::Focused}; + for windows_keycode in event::get_pressed_keys() { + let scancode = + winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC); + let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + scancode, + virtual_keycode, + state: Released, + modifiers: event::get_key_mods(), + }, + is_synthetic: true, + }, + }) + } + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Focused(false), From 027c52171d51bd78c04bfa758391d4e5cf16702d Mon Sep 17 00:00:00 2001 From: Osspial Date: Fri, 27 Dec 2019 16:28:06 -0500 Subject: [PATCH 031/239] Fix changelog --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ac899329..c0c3c1bce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - `EventsCleared` has been renamed to `MainEventsCleared`. - `RedrawRequested` is now issued only after `MainEventsCleared`. - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. +- Implement synthetic window focus key events on Windows. # 0.20.0 Alpha 5 (2019-12-09) @@ -32,9 +33,6 @@ - On X11, generate synthetic key events for keys held when a window gains or loses focus. - On X11, issue a `CursorMoved` event when a `Touch` event occurs, as X11 implicitly moves the cursor for such events. -- Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`, - indicating that the event is generated by winit. -- On X11 and Windows, generate synthetic key events for keys held when a window gains or loses focus. # 0.20.0 Alpha 4 (2019-10-18) From 20e81695ca29dc742e610e0c12edc96655f4024d Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 28 Dec 2019 15:36:06 -0500 Subject: [PATCH 032/239] Change ModifiersState to a bitflags struct (#1306) * Change ModifiersState to a bitflags struct * Make examples work * Add modifier state methods * all things considered, only erroring out in one file throughout all of these changes is kinda impressive * Make expansion plans more clear * Move changelog entry * Try to fix macos build * Revert modifiers println in cursor_grab * Make serde serialization less bug-prone --- CHANGELOG.md | 1 + Cargo.toml | 4 +- examples/cursor_grab.rs | 4 +- examples/multithreaded.rs | 4 +- src/event.rs | 110 +++++++++++++++--- src/lib.rs | 1 - src/platform_impl/linux/wayland/keyboard.rs | 12 +- src/platform_impl/linux/x11/util/input.rs | 12 +- src/platform_impl/linux/x11/util/modifiers.rs | 24 ++-- src/platform_impl/macos/app_state.rs | 2 +- src/platform_impl/macos/event.rs | 24 +++- src/platform_impl/macos/view.rs | 16 +-- src/platform_impl/web/stdweb/event.rs | 24 ++-- src/platform_impl/web/web_sys/event.rs | 24 ++-- src/platform_impl/windows/event.rs | 19 ++- 15 files changed, 189 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0c3c1bce5..957a2e4788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - `RedrawRequested` is now issued only after `MainEventsCleared`. - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. - Implement synthetic window focus key events on Windows. +- **Breaking**: Change `ModifiersState` to a `bitflags` struct. # 0.20.0 Alpha 5 (2019-12-09) diff --git a/Cargo.toml b/Cargo.toml index 4887f364dd..3f91840943 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ libc = "0.2.64" log = "0.4" serde = { version = "1", optional = true, features = ["serde_derive"] } raw-window-handle = "0.3" +bitflags = "1" [dev-dependencies] image = "0.21" @@ -48,9 +49,6 @@ version = "0.1.3" default_features = false features = ["display_link"] -[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies] -bitflags = "1" - [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" features = [ diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 726aba56bd..ac3a0f27b7 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -30,8 +30,8 @@ fn main() { use winit::event::VirtualKeyCode::*; match key { Escape => *control_flow = ControlFlow::Exit, - G => window.set_cursor_grab(!modifiers.shift).unwrap(), - H => window.set_cursor_visible(modifiers.shift), + G => window.set_cursor_grab(!modifiers.shift()).unwrap(), + H => window.set_cursor_visible(modifiers.shift()), _ => (), } } diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 8ce3607799..edfe72cfc8 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -60,7 +60,7 @@ fn main() { .. } => { window.set_title(&format!("{:?}", key)); - let state = !modifiers.shift; + let state = !modifiers.shift(); use VirtualKeyCode::*; match key { A => window.set_always_on_top(state), @@ -81,7 +81,7 @@ fn main() { video_modes.iter().nth(video_mode_id).unwrap() ); } - F => window.set_fullscreen(match (state, modifiers.alt) { + F => window.set_fullscreen(match (state, modifiers.alt()) { (true, false) => { Some(Fullscreen::Borderless(window.current_monitor())) } diff --git a/src/event.rs b/src/event.rs index a9be124a3a..b66ca99279 100644 --- a/src/event.rs +++ b/src/event.rs @@ -675,21 +675,99 @@ pub enum VirtualKeyCode { Cut, } -/// Represents the current state of the keyboard modifiers -/// -/// Each field of this struct represents a modifier and is `true` if this modifier is active. -#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(default))] -pub struct ModifiersState { - /// The "shift" key - pub shift: bool, - /// The "control" key - pub ctrl: bool, - /// The "alt" key - pub alt: bool, - /// The "logo" key +impl ModifiersState { + /// Returns `true` if the shift key is pressed. + pub fn shift(&self) -> bool { + self.intersects(Self::SHIFT) + } + /// Returns `true` if the control key is pressed. + pub fn ctrl(&self) -> bool { + self.intersects(Self::CTRL) + } + /// Returns `true` if the alt key is pressed. + pub fn alt(&self) -> bool { + self.intersects(Self::ALT) + } + /// Returns `true` if the logo key is pressed. + pub fn logo(&self) -> bool { + self.intersects(Self::LOGO) + } +} + +bitflags! { + /// Represents the current state of the keyboard modifiers /// - /// This is the "windows" key on PC and "command" key on Mac. - pub logo: bool, + /// Each flag represents a modifier and is set if this modifier is active. + #[derive(Default)] + pub struct ModifiersState: u32 { + // left and right modifiers are currently commented out, but we should be able to support + // them in a future release + /// The "shift" key. + const SHIFT = 0b100 << 0; + // const LSHIFT = 0b010 << 0; + // const RSHIFT = 0b001 << 0; + /// The "control" key. + const CTRL = 0b100 << 3; + // const LCTRL = 0b010 << 3; + // const RCTRL = 0b001 << 3; + /// The "alt" key. + const ALT = 0b100 << 6; + // const LALT = 0b010 << 6; + // const RALT = 0b001 << 6; + /// This is the "windows" key on PC and "command" key on Mac. + const LOGO = 0b100 << 9; + // const LLOGO = 0b010 << 9; + // const RLOGO = 0b001 << 9; + } +} + +#[cfg(feature = "serde")] +mod modifiers_serde { + use super::ModifiersState; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Default, Serialize, Deserialize)] + #[serde(default)] + #[serde(rename = "ModifiersState")] + pub struct ModifiersStateSerialize { + pub shift: bool, + pub ctrl: bool, + pub alt: bool, + pub logo: bool, + } + + impl Serialize for ModifiersState { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = ModifiersStateSerialize { + shift: self.shift(), + ctrl: self.ctrl(), + alt: self.alt(), + logo: self.logo(), + }; + s.serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for ModifiersState { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let ModifiersStateSerialize { + shift, + ctrl, + alt, + logo, + } = ModifiersStateSerialize::deserialize(deserializer)?; + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, shift); + m.set(ModifiersState::CTRL, ctrl); + m.set(ModifiersState::ALT, alt); + m.set(ModifiersState::LOGO, logo); + Ok(m) + } + } } diff --git a/src/lib.rs b/src/lib.rs index 9d0340ee2c..cd99b89fbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,7 +119,6 @@ extern crate log; #[macro_use] extern crate serde; #[macro_use] -#[cfg(any(target_os = "ios", target_os = "windows"))] extern crate bitflags; #[cfg(any(target_os = "macos", target_os = "ios"))] #[macro_use] diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index ef026c3a41..b361cb1b72 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -404,12 +404,12 @@ fn keysym_to_vkey(keysym: u32) -> Option { impl ModifiersState { pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState { - ModifiersState { - shift: mods.shift, - ctrl: mods.ctrl, - alt: mods.alt, - logo: mods.logo, - } + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, mods.shift); + m.set(ModifiersState::CTRL, mods.ctrl); + m.set(ModifiersState::ALT, mods.alt); + m.set(ModifiersState::LOGO, mods.logo); + m } } diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index 2fbabe1e63..5af033d336 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -17,12 +17,12 @@ impl ModifiersState { } pub(crate) fn from_x11_mask(mask: c_uint) -> Self { - ModifiersState { - alt: mask & ffi::Mod1Mask != 0, - shift: mask & ffi::ShiftMask != 0, - ctrl: mask & ffi::ControlMask != 0, - logo: mask & ffi::Mod4Mask != 0, - } + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, mask & ffi::Mod1Mask != 0); + m.set(ModifiersState::CTRL, mask & ffi::ShiftMask != 0); + m.set(ModifiersState::ALT, mask & ffi::ControlMask != 0); + m.set(ModifiersState::LOGO, mask & ffi::Mod4Mask != 0); + m } } diff --git a/src/platform_impl/linux/x11/util/modifiers.rs b/src/platform_impl/linux/x11/util/modifiers.rs index 9bdc6e99e5..7c951997ad 100644 --- a/src/platform_impl/linux/x11/util/modifiers.rs +++ b/src/platform_impl/linux/x11/util/modifiers.rs @@ -116,10 +116,10 @@ impl ModifierKeyState { let mut new_state = *state; match except { - Some(Modifier::Alt) => new_state.alt = self.state.alt, - Some(Modifier::Ctrl) => new_state.ctrl = self.state.ctrl, - Some(Modifier::Shift) => new_state.shift = self.state.shift, - Some(Modifier::Logo) => new_state.logo = self.state.logo, + Some(Modifier::Alt) => new_state.set(ModifiersState::ALT, self.state.alt()), + Some(Modifier::Ctrl) => new_state.set(ModifiersState::CTRL, self.state.ctrl()), + Some(Modifier::Shift) => new_state.set(ModifiersState::SHIFT, self.state.shift()), + Some(Modifier::Logo) => new_state.set(ModifiersState::LOGO, self.state.logo()), None => (), } @@ -170,18 +170,18 @@ impl ModifierKeyState { fn get_modifier(state: &ModifiersState, modifier: Modifier) -> bool { match modifier { - Modifier::Alt => state.alt, - Modifier::Ctrl => state.ctrl, - Modifier::Shift => state.shift, - Modifier::Logo => state.logo, + Modifier::Alt => state.alt(), + Modifier::Ctrl => state.ctrl(), + Modifier::Shift => state.shift(), + Modifier::Logo => state.logo(), } } fn set_modifier(state: &mut ModifiersState, modifier: Modifier, value: bool) { match modifier { - Modifier::Alt => state.alt = value, - Modifier::Ctrl => state.ctrl = value, - Modifier::Shift => state.shift = value, - Modifier::Logo => state.logo = value, + Modifier::Alt => state.set(ModifiersState::ALT, value), + Modifier::Ctrl => state.set(ModifiersState::CTRL, value), + Modifier::Shift => state.set(ModifiersState::SHIFT, value), + Modifier::Logo => state.set(ModifiersState::LOGO, value), } } diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index be3aeb1213..67de6ce657 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -14,7 +14,7 @@ use std::{ use cocoa::{appkit::NSApp, base::nil, foundation::NSString}; use crate::{ - event::{Event, StartCause, WindowEvent}, + event::{Event, StartCause}, event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, platform_impl::platform::{observer::EventLoopWaker, util::Never}, window::WindowId, diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 508347e65a..4f8123d4fe 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -224,12 +224,24 @@ pub fn check_function_keys(string: &str) -> Option { pub fn event_mods(event: id) -> ModifiersState { let flags = unsafe { NSEvent::modifierFlags(event) }; - ModifiersState { - shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask), - ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask), - alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask), - logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask), - } + let mut m = ModifiersState::empty(); + m.set( + ModifiersState::SHIFT, + flags.contains(NSEventModifierFlags::NSShiftKeyMask), + ); + m.set( + ModifiersState::CTRL, + flags.contains(NSEventModifierFlags::NSControlKeyMask), + ); + m.set( + ModifiersState::ALT, + flags.contains(NSEventModifierFlags::NSAlternateKeyMask), + ); + m.set( + ModifiersState::LOGO, + flags.contains(NSEventModifierFlags::NSCommandKeyMask), + ); + m } pub fn get_scancode(event: cocoa::base::id) -> c_ushort { diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 2b69bfbe50..eb373d98a6 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -646,36 +646,36 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) { if let Some(window_event) = modifier_event( event, NSEventModifierFlags::NSShiftKeyMask, - state.modifiers.shift, + state.modifiers.shift(), ) { - state.modifiers.shift = !state.modifiers.shift; + state.modifiers.toggle(ModifiersState::SHIFT); events.push_back(window_event); } if let Some(window_event) = modifier_event( event, NSEventModifierFlags::NSControlKeyMask, - state.modifiers.ctrl, + state.modifiers.ctrl(), ) { - state.modifiers.ctrl = !state.modifiers.ctrl; + state.modifiers.toggle(ModifiersState::CTRL); events.push_back(window_event); } if let Some(window_event) = modifier_event( event, NSEventModifierFlags::NSCommandKeyMask, - state.modifiers.logo, + state.modifiers.logo(), ) { - state.modifiers.logo = !state.modifiers.logo; + state.modifiers.toggle(ModifiersState::LOGO); events.push_back(window_event); } if let Some(window_event) = modifier_event( event, NSEventModifierFlags::NSAlternateKeyMask, - state.modifiers.alt, + state.modifiers.alt(), ) { - state.modifiers.alt = !state.modifiers.alt; + state.modifiers.toggle(ModifiersState::ALT); events.push_back(window_event); } diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index 14456dc054..223ef69325 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -15,12 +15,12 @@ pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton { } pub fn mouse_modifiers(event: &impl IMouseEvent) -> ModifiersState { - ModifiersState { - shift: event.shift_key(), - ctrl: event.ctrl_key(), - alt: event.alt_key(), - logo: event.meta_key(), - } + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, event.shift_key()); + m.set(ModifiersState::CTRL, event.ctrl_key()); + m.set(ModifiersState::ALT, event.alt_key()); + m.set(ModifiersState::LOGO, event.meta_key()); + m } pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition { @@ -213,12 +213,12 @@ pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option { } pub fn keyboard_modifiers(event: &impl IKeyboardEvent) -> ModifiersState { - ModifiersState { - shift: event.shift_key(), - ctrl: event.ctrl_key(), - alt: event.alt_key(), - logo: event.meta_key(), - } + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, event.shift_key()); + m.set(ModifiersState::CTRL, event.ctrl_key()); + m.set(ModifiersState::ALT, event.alt_key()); + m.set(ModifiersState::LOGO, event.meta_key()); + m } pub fn codepoint(event: &impl IKeyboardEvent) -> char { diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index af557b9941..9a11f3bfa1 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -14,12 +14,12 @@ pub fn mouse_button(event: &MouseEvent) -> MouseButton { } pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState { - ModifiersState { - shift: event.shift_key(), - ctrl: event.ctrl_key(), - alt: event.alt_key(), - logo: event.meta_key(), - } + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, event.shift_key()); + m.set(ModifiersState::CTRL, event.ctrl_key()); + m.set(ModifiersState::ALT, event.alt_key()); + m.set(ModifiersState::LOGO, event.meta_key()); + m } pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { @@ -211,12 +211,12 @@ pub fn virtual_key_code(event: &KeyboardEvent) -> Option { } pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState { - ModifiersState { - shift: event.shift_key(), - ctrl: event.ctrl_key(), - alt: event.alt_key(), - logo: event.meta_key(), - } + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, event.shift_key()); + m.set(ModifiersState::CTRL, event.ctrl_key()); + m.set(ModifiersState::ALT, event.alt_key()); + m.set(ModifiersState::LOGO, event.meta_key()); + m } pub fn codepoint(event: &KeyboardEvent) -> char { diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 60f6a9253d..04ef7692fb 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -17,13 +17,22 @@ fn key_pressed(vkey: c_int) -> bool { } pub fn get_key_mods() -> ModifiersState { - let mut mods = ModifiersState::default(); let filter_out_altgr = layout_uses_altgr() && key_pressed(winuser::VK_RMENU); - mods.shift = key_pressed(winuser::VK_SHIFT); - mods.ctrl = key_pressed(winuser::VK_CONTROL) && !filter_out_altgr; - mods.alt = key_pressed(winuser::VK_MENU) && !filter_out_altgr; - mods.logo = key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN); + let mut mods = ModifiersState::empty(); + mods.set(ModifiersState::SHIFT, key_pressed(winuser::VK_SHIFT)); + mods.set( + ModifiersState::CTRL, + key_pressed(winuser::VK_CONTROL) && !filter_out_altgr, + ); + mods.set( + ModifiersState::ALT, + key_pressed(winuser::VK_MENU) && !filter_out_altgr, + ); + mods.set( + ModifiersState::LOGO, + key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN), + ); mods } From 8a3a32f2866c0ad98f4e2a768e56fcaca28f7952 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 28 Dec 2019 16:22:51 -0500 Subject: [PATCH 033/239] Properly mark a few changes as breaking --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 957a2e4788..cf323212e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode. - On Windows, theme the title bar according to whether the system theme is "Light" or "Dark". - Added `WindowEvent::ThemeChanged` variant to handle changes to the system theme. Currently only implemented on Windows. -- Changes to the `RedrawRequested` event (#1041): +- **Breaking**: Changes to the `RedrawRequested` event (#1041): - `RedrawRequested` has been moved from `WindowEvent` to `Event`. - `EventsCleared` has been renamed to `MainEventsCleared`. - `RedrawRequested` is now issued only after `MainEventsCleared`. @@ -29,7 +29,7 @@ - On Windows, fix focusing unfocused windows when switching from fullscreen to windowed. - On X11, fix reporting incorrect DPI factor when waking from suspend. - Change `EventLoopClosed` to contain the original event. -- Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`, +- **Breaking**: Add `is_synthetic` field to `WindowEvent` variant `KeyboardInput`, indicating that the event is generated by winit. - On X11, generate synthetic key events for keys held when a window gains or loses focus. - On X11, issue a `CursorMoved` event when a `Touch` event occurs, From 468b6b83ecd6780281dab352248cf941a201369c Mon Sep 17 00:00:00 2001 From: Michael Tang Date: Sat, 28 Dec 2019 15:53:41 -0800 Subject: [PATCH 034/239] fix: remove deprecated usage of `mem::uninitialized`. (#1341) --- src/platform_impl/web/stdweb/timeout.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform_impl/web/stdweb/timeout.rs b/src/platform_impl/web/stdweb/timeout.rs index fceb111374..00ac2ab02d 100644 --- a/src/platform_impl/web/stdweb/timeout.rs +++ b/src/platform_impl/web/stdweb/timeout.rs @@ -3,7 +3,7 @@ use stdweb::web::{window, IWindowOrWorker, TimeoutHandle}; #[derive(Debug)] pub struct Timeout { - handle: TimeoutHandle, + handle: Option, } impl Timeout { @@ -12,14 +12,14 @@ impl Timeout { F: 'static + FnMut(), { Timeout { - handle: window().set_clearable_timeout(f, duration.as_millis() as u32), + handle: Some(window().set_clearable_timeout(f, duration.as_millis() as u32)), } } } impl Drop for Timeout { fn drop(&mut self) { - let handle = std::mem::replace(&mut self.handle, unsafe { std::mem::uninitialized() }); + let handle = self.handle.take().unwrap(); handle.clear(); } } From e4451d6786697c2034872c7e7051885bf65b4dd3 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 29 Dec 2019 10:39:15 -0500 Subject: [PATCH 035/239] Fix Window::set_visible not setting internal flags correctly (#1345) --- CHANGELOG.md | 1 + src/platform_impl/windows/window.rs | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf323212e3..92a8dfa2c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. - Implement synthetic window focus key events on Windows. - **Breaking**: Change `ModifiersState` to a `bitflags` struct. +- On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior. # 0.20.0 Alpha 5 (2019-12-09) diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 08e884d4a1..d99f86457b 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -127,14 +127,13 @@ impl Window { #[inline] pub fn set_visible(&self, visible: bool) { - match visible { - true => unsafe { - winuser::ShowWindow(self.window.0, winuser::SW_SHOW); - }, - false => unsafe { - winuser::ShowWindow(self.window.0, winuser::SW_HIDE); - }, - } + let window_state = Arc::clone(&self.window_state); + let window = self.window.clone(); + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { + f.set(WindowFlags::VISIBLE, visible) + }); + }); } #[inline] From fa7a3025ec1f7ea5407ad0c3a8ef0cc3148cfc34 Mon Sep 17 00:00:00 2001 From: hatoo Date: Mon, 30 Dec 2019 07:16:12 +0900 Subject: [PATCH 036/239] [MacOS] Fix memory management (#1342) * macOS, Reduce memory usage * macOS, Fix memory leak --- src/platform_impl/macos/app_state.rs | 10 +++++++++- src/platform_impl/macos/event_loop.rs | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 67de6ce657..626b95fc56 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -11,7 +11,11 @@ use std::{ time::Instant, }; -use cocoa::{appkit::NSApp, base::nil, foundation::NSString}; +use cocoa::{ + appkit::NSApp, + base::nil, + foundation::{NSAutoreleasePool, NSString}, +}; use crate::{ event::{Event, StartCause}, @@ -277,6 +281,8 @@ impl AppState { unsafe { let _: () = msg_send![NSApp(), stop: nil]; + let pool = NSAutoreleasePool::new(nil); + let windows: *const Object = msg_send![NSApp(), windows]; let window: *const Object = msg_send![windows, objectAtIndex:0]; assert_ne!(window, nil); @@ -292,6 +298,8 @@ impl AppState { let _: () = msg_send![window, setTitle: some_unique_title]; // And restore it. let _: () = msg_send![window, setTitle: title]; + + pool.drain(); }; } HANDLER.update_start_time(); diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 8469e3ffc8..57e07b928f 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -95,12 +95,13 @@ impl EventLoop { F: FnMut(Event, &RootWindowTarget, &mut ControlFlow), { unsafe { - let _pool = NSAutoreleasePool::new(nil); + let pool = NSAutoreleasePool::new(nil); let app = NSApp(); assert_ne!(app, nil); AppState::set_callback(callback, Rc::clone(&self.window_target)); let _: () = msg_send![app, run]; AppState::exit(); + pool.drain(); } } From d9bda3e985aad1e5364318597443c1a02ede5212 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 30 Dec 2019 14:11:11 -0500 Subject: [PATCH 037/239] Implement ModifiersChanged on Windows, and fix bugs discovered in implementation process (#1344) * Move DeviceEvent handling to the message target window. Previously, device events seem to have only been sent to one particular window, and when that window was closed Winit would stop receiving device events. This also allows users to create windowless event loops that process device events - an intriguing idea, to say the least. * Emit LWin and RWin VirtualKeyCodes on Windows * Implement ModifiersChanged on Windows * Make ModifiersChanged a tuple variant instead of a struct variant * Add changelog entries * Format * Update changelog entry * Fix AltGr handling * Reformat * Publicly expose ModifiersChanged and deprecate misc. modifiers fields --- CHANGELOG.md | 4 + examples/cursor_grab.rs | 6 +- src/event.rs | 18 +- src/platform_impl/linux/wayland/keyboard.rs | 2 +- .../linux/x11/event_processor.rs | 6 +- src/platform_impl/macos/view.rs | 4 +- src/platform_impl/windows/event.rs | 53 +++- src/platform_impl/windows/event_loop.rs | 284 ++++++++++-------- src/platform_impl/windows/window.rs | 7 +- 9 files changed, 243 insertions(+), 141 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92a8dfa2c5..d0459d48c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,11 @@ - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. - Implement synthetic window focus key events on Windows. - **Breaking**: Change `ModifiersState` to a `bitflags` struct. +- On Windows, implement `VirtualKeyCode` translation for `LWin` and `RWin`. +- On Windows, fix closing the last opened window causing `DeviceEvent`s to stop getting emitted. - On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior. +- Add `DeviceEvent::ModifiersChanged`. + - Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`. # 0.20.0 Alpha 5 (2019-12-09) diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index ac3a0f27b7..13f14282e9 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -1,5 +1,5 @@ use winit::{ - event::{DeviceEvent, ElementState, Event, KeyboardInput, WindowEvent}, + event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; @@ -12,6 +12,8 @@ fn main() { .build(&event_loop) .unwrap(); + let mut modifiers = ModifiersState::default(); + event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; match event { @@ -22,7 +24,6 @@ fn main() { KeyboardInput { state: ElementState::Released, virtual_keycode: Some(key), - modifiers, .. }, .. @@ -43,6 +44,7 @@ fn main() { ElementState::Pressed => println!("mouse button {} pressed", button), ElementState::Released => println!("mouse button {} released", button), }, + DeviceEvent::ModifiersChanged(m) => modifiers = m, _ => (), }, _ => (), diff --git a/src/event.rs b/src/event.rs index b66ca99279..d93324f7a8 100644 --- a/src/event.rs +++ b/src/event.rs @@ -167,6 +167,7 @@ pub enum WindowEvent { /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. position: LogicalPosition, + #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -181,6 +182,7 @@ pub enum WindowEvent { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, + #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -189,6 +191,7 @@ pub enum WindowEvent { device_id: DeviceId, state: ElementState, button: MouseButton, + #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -295,11 +298,15 @@ pub enum DeviceEvent { Key(KeyboardInput), - /// Keyboard modifiers have changed - #[doc(hidden)] - ModifiersChanged { - modifiers: ModifiersState, - }, + /// The keyboard modifiers have changed. + /// + /// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from + /// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere. + /// + /// Platform-specific behavior: + /// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an + /// issue, and it should get fixed - but it's the current state of the API. + ModifiersChanged(ModifiersState), Text { codepoint: char, @@ -329,6 +336,7 @@ pub struct KeyboardInput { /// /// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from /// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere. + #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] pub modifiers: ModifiersState, } diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index b361cb1b72..7e0d2e3eec 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -105,7 +105,7 @@ pub fn init_keyboard( my_sink .send(Event::DeviceEvent { device_id: device_id(), - event: DeviceEvent::ModifiersChanged { modifiers }, + event: DeviceEvent::ModifiersChanged(modifiers), }) .unwrap(); } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 762066ba25..162b09df27 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -137,7 +137,7 @@ impl EventProcessor { let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); callback(Event::DeviceEvent { device_id, - event: DeviceEvent::ModifiersChanged { modifiers }, + event: DeviceEvent::ModifiersChanged(modifiers), }); } } @@ -1114,9 +1114,7 @@ impl EventProcessor { if modifiers != new_modifiers { callback(Event::DeviceEvent { device_id, - event: DeviceEvent::ModifiersChanged { - modifiers: new_modifiers, - }, + event: DeviceEvent::ModifiersChanged(new_modifiers), }); } } diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index eb373d98a6..daf3a6f953 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -688,9 +688,7 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) { AppState::queue_event(Event::DeviceEvent { device_id: DEVICE_ID, - event: DeviceEvent::ModifiersChanged { - modifiers: state.modifiers, - }, + event: DeviceEvent::ModifiersChanged(state.modifiers), }); } trace!("Completed `flagsChanged`"); diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 04ef7692fb..49501f1a3c 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -36,6 +36,55 @@ pub fn get_key_mods() -> ModifiersState { mods } +bitflags! { + #[derive(Default)] + pub struct ModifiersStateSide: u32 { + const LSHIFT = 0b010 << 0; + const RSHIFT = 0b001 << 0; + + const LCTRL = 0b010 << 3; + const RCTRL = 0b001 << 3; + + const LALT = 0b010 << 6; + const RALT = 0b001 << 6; + + const LLOGO = 0b010 << 9; + const RLOGO = 0b001 << 9; + } +} + +impl ModifiersStateSide { + pub fn filter_out_altgr(&self) -> ModifiersStateSide { + match layout_uses_altgr() && self.contains(Self::RALT) { + false => *self, + true => *self & !(Self::LCTRL | Self::RCTRL | Self::LALT | Self::RALT), + } + } +} + +impl From for ModifiersState { + fn from(side: ModifiersStateSide) -> Self { + let mut state = ModifiersState::default(); + state.set( + Self::SHIFT, + side.intersects(ModifiersStateSide::LSHIFT | ModifiersStateSide::RSHIFT), + ); + state.set( + Self::CTRL, + side.intersects(ModifiersStateSide::LCTRL | ModifiersStateSide::RCTRL), + ); + state.set( + Self::ALT, + side.intersects(ModifiersStateSide::LALT | ModifiersStateSide::RALT), + ); + state.set( + Self::LOGO, + side.intersects(ModifiersStateSide::LLOGO | ModifiersStateSide::RLOGO), + ); + state + } +} + pub fn get_pressed_keys() -> impl Iterator { let mut keyboard_state = vec![0u8; 256]; unsafe { winuser::GetKeyboardState(keyboard_state.as_mut_ptr()) }; @@ -196,8 +245,8 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option { 0x58 => Some(VirtualKeyCode::X), 0x59 => Some(VirtualKeyCode::Y), 0x5A => Some(VirtualKeyCode::Z), - //winuser::VK_LWIN => Some(VirtualKeyCode::Lwin), - //winuser::VK_RWIN => Some(VirtualKeyCode::Rwin), + winuser::VK_LWIN => Some(VirtualKeyCode::LWin), + winuser::VK_RWIN => Some(VirtualKeyCode::RWin), winuser::VK_APPS => Some(VirtualKeyCode::Apps), winuser::VK_SLEEP => Some(VirtualKeyCode::Sleep), winuser::VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0), diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 9d6b7eff84..88de492ca2 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -53,10 +53,10 @@ use crate::{ become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_scale_factor, }, drop_handler::FileDropHandler, - event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, - monitor, - raw_input::{get_raw_input_data, get_raw_mouse_button_state}, - util, + event::{ + self, handle_extended_keys, process_key_params, vkey_to_winit_vkey, ModifiersStateSide, + }, + monitor, raw_input, util, window::adjust_size, window_state::{CursorFlags, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, @@ -112,6 +112,7 @@ impl SubclassInput { struct ThreadMsgTargetSubclassInput { event_loop_runner: EventLoopRunnerShared, user_event_receiver: Receiver, + modifiers_state: ModifiersStateSide, } impl ThreadMsgTargetSubclassInput { @@ -169,6 +170,7 @@ impl EventLoop { let runner_shared = Rc::new(ELRShared::new()); let (thread_msg_target, thread_msg_sender) = thread_event_target_window(runner_shared.clone()); + raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target); EventLoop { thread_msg_sender, @@ -535,6 +537,7 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> let subclass_input = ThreadMsgTargetSubclassInput { event_loop_runner, user_event_receiver: rx, + modifiers_state: ModifiersStateSide::default(), }; let input_ptr = Box::into_raw(Box::new(subclass_input)); let subclass_result = commctrl::SetWindowSubclass( @@ -1115,120 +1118,6 @@ unsafe extern "system" fn public_window_callback( 0 } - winuser::WM_INPUT_DEVICE_CHANGE => { - let event = match wparam as _ { - winuser::GIDC_ARRIVAL => DeviceEvent::Added, - winuser::GIDC_REMOVAL => DeviceEvent::Removed, - _ => unreachable!(), - }; - - subclass_input.send_event(Event::DeviceEvent { - device_id: wrap_device_id(lparam as _), - event, - }); - - 0 - } - - winuser::WM_INPUT => { - use crate::event::{ - DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, - ElementState::{Pressed, Released}, - MouseScrollDelta::LineDelta, - }; - - if let Some(data) = get_raw_input_data(lparam as _) { - let device_id = wrap_device_id(data.header.hDevice as _); - - if data.header.dwType == winuser::RIM_TYPEMOUSE { - let mouse = data.data.mouse(); - - if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { - let x = mouse.lLastX as f64; - let y = mouse.lLastY as f64; - - if x != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x }, - }); - } - - if y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y }, - }); - } - - if x != 0.0 || y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseMotion { delta: (x, y) }, - }); - } - } - - if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { - let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { - delta: LineDelta(0.0, delta as f32), - }, - }); - } - - let button_state = get_raw_mouse_button_state(mouse.usButtonFlags); - // Left, middle, and right, respectively. - for (index, state) in button_state.iter().enumerate() { - if let Some(state) = *state { - // This gives us consistency with X11, since there doesn't - // seem to be anything else reasonable to do for a mouse - // button ID. - let button = (index + 1) as _; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Button { button, state }, - }); - } - } - } else if data.header.dwType == winuser::RIM_TYPEKEYBOARD { - let keyboard = data.data.keyboard(); - - let pressed = keyboard.Message == winuser::WM_KEYDOWN - || keyboard.Message == winuser::WM_SYSKEYDOWN; - let released = keyboard.Message == winuser::WM_KEYUP - || keyboard.Message == winuser::WM_SYSKEYUP; - - if pressed || released { - let state = if pressed { Pressed } else { Released }; - - let scancode = keyboard.MakeCode as _; - let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) - | util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _); - if let Some((vkey, scancode)) = - handle_extended_keys(keyboard.VKey as _, scancode, extended) - { - let virtual_keycode = vkey_to_winit_vkey(vkey); - - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Key(KeyboardInput { - scancode, - state, - virtual_keycode, - modifiers: event::get_key_mods(), - }), - }); - } - } - } - } - - commctrl::DefSubclassProc(window, msg, wparam, lparam) - } - winuser::WM_TOUCH => { let pcount = LOWORD(wparam as DWORD) as usize; let mut inputs = Vec::with_capacity(pcount); @@ -1731,6 +1620,165 @@ unsafe extern "system" fn thread_event_target_callback( } 0 } + + winuser::WM_INPUT_DEVICE_CHANGE => { + let event = match wparam as _ { + winuser::GIDC_ARRIVAL => DeviceEvent::Added, + winuser::GIDC_REMOVAL => DeviceEvent::Removed, + _ => unreachable!(), + }; + + subclass_input.send_event(Event::DeviceEvent { + device_id: wrap_device_id(lparam as _), + event, + }); + + 0 + } + + winuser::WM_INPUT => { + use crate::event::{ + DeviceEvent::{Button, Key, ModifiersChanged, Motion, MouseMotion, MouseWheel}, + ElementState::{Pressed, Released}, + MouseScrollDelta::LineDelta, + VirtualKeyCode, + }; + + if let Some(data) = raw_input::get_raw_input_data(lparam as _) { + let device_id = wrap_device_id(data.header.hDevice as _); + + if data.header.dwType == winuser::RIM_TYPEMOUSE { + let mouse = data.data.mouse(); + + if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { + let x = mouse.lLastX as f64; + let y = mouse.lLastY as f64; + + if x != 0.0 { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 0, value: x }, + }); + } + + if y != 0.0 { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 1, value: y }, + }); + } + + if x != 0.0 || y != 0.0 { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: MouseMotion { delta: (x, y) }, + }); + } + } + + if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { + let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: MouseWheel { + delta: LineDelta(0.0, delta as f32), + }, + }); + } + + let button_state = raw_input::get_raw_mouse_button_state(mouse.usButtonFlags); + // Left, middle, and right, respectively. + for (index, state) in button_state.iter().enumerate() { + if let Some(state) = *state { + // This gives us consistency with X11, since there doesn't + // seem to be anything else reasonable to do for a mouse + // button ID. + let button = (index + 1) as _; + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Button { button, state }, + }); + } + } + } else if data.header.dwType == winuser::RIM_TYPEKEYBOARD { + let keyboard = data.data.keyboard(); + + let pressed = keyboard.Message == winuser::WM_KEYDOWN + || keyboard.Message == winuser::WM_SYSKEYDOWN; + let released = keyboard.Message == winuser::WM_KEYUP + || keyboard.Message == winuser::WM_SYSKEYUP; + + if pressed || released { + let state = if pressed { Pressed } else { Released }; + + let scancode = keyboard.MakeCode as _; + let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) + | util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _); + + if let Some((vkey, scancode)) = + handle_extended_keys(keyboard.VKey as _, scancode, extended) + { + let virtual_keycode = vkey_to_winit_vkey(vkey); + + // If we ever change the DeviceEvent API to only emit events when a + // window is focused, we'll need to emit synthetic `ModifiersChanged` + // events when Winit windows lose focus so that these don't drift out + // of sync with the actual modifier state. + let old_modifiers_state = + subclass_input.modifiers_state.filter_out_altgr().into(); + match virtual_keycode { + Some(VirtualKeyCode::LShift) => subclass_input + .modifiers_state + .set(ModifiersStateSide::LSHIFT, pressed), + Some(VirtualKeyCode::RShift) => subclass_input + .modifiers_state + .set(ModifiersStateSide::RSHIFT, pressed), + Some(VirtualKeyCode::LControl) => subclass_input + .modifiers_state + .set(ModifiersStateSide::LCTRL, pressed), + Some(VirtualKeyCode::RControl) => subclass_input + .modifiers_state + .set(ModifiersStateSide::RCTRL, pressed), + Some(VirtualKeyCode::LAlt) => subclass_input + .modifiers_state + .set(ModifiersStateSide::LALT, pressed), + Some(VirtualKeyCode::RAlt) => subclass_input + .modifiers_state + .set(ModifiersStateSide::RALT, pressed), + Some(VirtualKeyCode::LWin) => subclass_input + .modifiers_state + .set(ModifiersStateSide::LLOGO, pressed), + Some(VirtualKeyCode::RWin) => subclass_input + .modifiers_state + .set(ModifiersStateSide::RLOGO, pressed), + _ => (), + } + let new_modifiers_state = + subclass_input.modifiers_state.filter_out_altgr().into(); + if new_modifiers_state != old_modifiers_state { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: ModifiersChanged(new_modifiers_state), + }); + } + + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Key(KeyboardInput { + scancode, + state, + virtual_keycode, + modifiers: new_modifiers_state, + }), + }); + } + } + } + } + + commctrl::DefSubclassProc(window, msg, wparam, lparam) + } + _ if msg == *USER_EVENT_MSG_ID => { if let Ok(event) = subclass_input.user_event_receiver.recv() { subclass_input.send_event(Event::UserEvent(event)); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index d99f86457b..012b840209 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -39,9 +39,7 @@ use crate::{ drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID}, icon::{self, IconType, WinIcon}, - monitor, - raw_input::register_all_mice_and_keyboards_for_raw_input, - util, + monitor, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, @@ -845,9 +843,6 @@ unsafe fn init( WindowWrapper(handle) }; - // Set up raw input - register_all_mice_and_keyboards_for_raw_input(real_window.0); - // Register for touch events if applicable { let digitizer = winuser::GetSystemMetrics(winuser::SM_DIGITIZER) as u32; From dd768fe655a862ca50265bccd422290b9155f94c Mon Sep 17 00:00:00 2001 From: hatoo Date: Tue, 31 Dec 2019 05:32:37 +0900 Subject: [PATCH 038/239] MacOS fix `CursorEntered` and `CursorLeft` events fired at old window size. (#1335) * On macOS, Fix `CursorEntered` and `CursorLeft` * Add CHANGELOG Co-authored-by: Freya Gentz --- CHANGELOG.md | 1 + src/platform_impl/macos/view.rs | 52 +++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0459d48c4..c7023ed6d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size. - On macOS, fix error when `set_fullscreen` is called during fullscreen transition. - On all platforms except mobile and WASM, implement `Window::set_minimized`. - On X11, fix `CursorEntered` event being generated for non-winit windows. diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index daf3a6f953..5e448cc79a 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -9,7 +9,7 @@ use std::{ use cocoa::{ appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow}, base::{id, nil}, - foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger}, + foundation::{NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger}, }; use objc::{ declare::ClassDecl, @@ -42,6 +42,7 @@ struct ViewState { raw_characters: Option, is_key_down: bool, modifiers: ModifiersState, + tracking_rect: Option, } pub fn new_view(ns_window: id) -> (IdRef, Weak>) { @@ -54,6 +55,7 @@ pub fn new_view(ns_window: id) -> (IdRef, Weak>) { raw_characters: None, is_key_down: false, modifiers: Default::default(), + tracking_rect: None, }; unsafe { // This is free'd in `dealloc` @@ -228,6 +230,10 @@ lazy_static! { sel!(cancelOperation:), cancel_operation as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(frameDidChange:), + frame_did_change as extern "C" fn(&Object, Sel, id), + ); decl.add_ivar::<*mut c_void>("winitState"); decl.add_ivar::("markedText"); let protocol = Protocol::get("NSTextInputClient").unwrap(); @@ -253,6 +259,19 @@ extern "C" fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> i let marked_text = ::init(NSMutableAttributedString::alloc(nil)); (*this).set_ivar("markedText", marked_text); + let _: () = msg_send![this, setPostsFrameChangedNotifications: YES]; + + let notification_center: &Object = + msg_send![class!(NSNotificationCenter), defaultCenter]; + let notification_name = + NSString::alloc(nil).init_str("NSViewFrameDidChangeNotification"); + let _: () = msg_send![ + notification_center, + addObserver: this + selector: sel!(frameDidChange:) + name: notification_name + object: this + ]; } this } @@ -261,17 +280,46 @@ extern "C" fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> i extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) { trace!("Triggered `viewDidMoveToWindow`"); unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + if let Some(tracking_rect) = state.tracking_rect.take() { + let _: () = msg_send![this, removeTrackingRect: tracking_rect]; + } + let rect: NSRect = msg_send![this, visibleRect]; - let _: () = msg_send![this, + let tracking_rect: NSInteger = msg_send![this, addTrackingRect:rect owner:this userData:nil assumeInside:NO ]; + state.tracking_rect = Some(tracking_rect); } trace!("Completed `viewDidMoveToWindow`"); } +extern "C" fn frame_did_change(this: &Object, _sel: Sel, _event: id) { + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + if let Some(tracking_rect) = state.tracking_rect.take() { + let _: () = msg_send![this, removeTrackingRect: tracking_rect]; + } + + let rect: NSRect = msg_send![this, visibleRect]; + let tracking_rect: NSInteger = msg_send![this, + addTrackingRect:rect + owner:this + userData:nil + assumeInside:NO + ]; + + state.tracking_rect = Some(tracking_rect); + } +} + extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) { unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); From 7367b8be6c23d4be391c69634b41eeb7c223b364 Mon Sep 17 00:00:00 2001 From: hatoo Date: Fri, 3 Jan 2020 09:34:14 +0900 Subject: [PATCH 039/239] On Macos, Hide cursor only inside window (#1348) * On MacOS, hide cursor by invisible cursor * Add CHANGELOG * Fix variable name * Add comments for `CURSOR_BYTES` --- CHANGELOG.md | 1 + src/platform_impl/macos/util/cursor.rs | 38 +++++++++++++++++++++++++- src/platform_impl/macos/view.rs | 31 +++++++++++++++++---- src/platform_impl/macos/window.rs | 38 +++++++++++++------------- 4 files changed, 82 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7023ed6d3..ad7bfed0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix `set_cursor_visible` hides cursor outside of window. - On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size. - On macOS, fix error when `set_fullscreen` is called during fullscreen transition. - On all platforms except mobile and WASM, implement `Window::set_minimized`. diff --git a/src/platform_impl/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs index 7f0b57fe1b..5c4d1537a7 100644 --- a/src/platform_impl/macos/util/cursor.rs +++ b/src/platform_impl/macos/util/cursor.rs @@ -3,7 +3,8 @@ use cocoa::{ base::{id, nil}, foundation::{NSDictionary, NSPoint, NSString}, }; -use objc::runtime::Sel; +use objc::{runtime::Sel, runtime::NO}; +use std::cell::RefCell; use crate::window::CursorIcon; @@ -126,3 +127,38 @@ pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id { hotSpot:point ] } + +pub unsafe fn invisible_cursor() -> id { + // 16x16 GIF data for invisible cursor + // You can reproduce this via ImageMagick. + // $ convert -size 16x16 xc:none cursor.gif + static CURSOR_BYTES: &[u8] = &[ + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, + 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B, + ]; + + thread_local! { + // We can't initialize this at startup. + static CURSOR_OBJECT: RefCell = RefCell::new(nil); + } + + CURSOR_OBJECT.with(|cursor_obj| { + if *cursor_obj.borrow() == nil { + // Create a cursor from `CURSOR_BYTES` + let cursor_data: id = msg_send![class!(NSData), + dataWithBytesNoCopy:CURSOR_BYTES as *const [u8] + length:CURSOR_BYTES.len() + freeWhenDone:NO + ]; + + let ns_image: id = msg_send![class!(NSImage), alloc]; + let _: id = msg_send![ns_image, initWithData: cursor_data]; + let cursor: id = msg_send![class!(NSCursor), alloc]; + *cursor_obj.borrow_mut() = + msg_send![cursor, initWithImage:ns_image hotSpot: NSPoint::new(0.0, 0.0)]; + } + *cursor_obj.borrow() + }) +} diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 5e448cc79a..8795ea1815 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -35,9 +35,23 @@ use crate::{ window::WindowId, }; +pub struct CursorState { + pub visible: bool, + pub cursor: util::Cursor, +} + +impl Default for CursorState { + fn default() -> Self { + Self { + visible: true, + cursor: Default::default(), + } + } +} + struct ViewState { ns_window: id, - pub cursor: Arc>, + pub cursor_state: Arc>, ime_spot: Option<(f64, f64)>, raw_characters: Option, is_key_down: bool, @@ -45,12 +59,12 @@ struct ViewState { tracking_rect: Option, } -pub fn new_view(ns_window: id) -> (IdRef, Weak>) { - let cursor = Default::default(); - let cursor_access = Arc::downgrade(&cursor); +pub fn new_view(ns_window: id) -> (IdRef, Weak>) { + let cursor_state = Default::default(); + let cursor_access = Arc::downgrade(&cursor_state); let state = ViewState { ns_window, - cursor, + cursor_state, ime_spot: None, raw_characters: None, is_key_down: false, @@ -349,7 +363,12 @@ extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) { let state = &mut *(state_ptr as *mut ViewState); let bounds: NSRect = msg_send![this, bounds]; - let cursor = state.cursor.lock().unwrap().load(); + let cursor_state = state.cursor_state.lock().unwrap(); + let cursor = if cursor_state.visible { + cursor_state.cursor.load() + } else { + util::invisible_cursor() + }; let _: () = msg_send![this, addCursorRect:bounds cursor:cursor diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index ee95f17a91..ff66f82ac5 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -20,6 +20,7 @@ use crate::{ ffi, monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, + view::CursorState, view::{self, new_view}, window_delegate::new_delegate, OsError, @@ -90,8 +91,8 @@ fn create_app(activation_policy: ActivationPolicy) -> Option { unsafe fn create_view( ns_window: id, pl_attribs: &PlatformSpecificWindowBuilderAttributes, -) -> Option<(IdRef, Weak>)> { - let (ns_view, cursor) = new_view(ns_window); +) -> Option<(IdRef, Weak>)> { + let (ns_view, cursor_state) = new_view(ns_window); ns_view.non_nil().map(|ns_view| { if !pl_attribs.disallow_hidpi { ns_view.setWantsBestResolutionOpenGLSurface_(YES); @@ -108,7 +109,7 @@ unsafe fn create_view( ns_window.setContentView_(*ns_view); ns_window.makeFirstResponder_(*ns_view); - (ns_view, cursor) + (ns_view, cursor_state) }) } @@ -290,8 +291,7 @@ pub struct UnownedWindow { input_context: IdRef, // never changes pub shared_state: Arc>, decorations: AtomicBool, - cursor: Weak>, - cursor_visible: AtomicBool, + cursor_state: Weak>, } unsafe impl Send for UnownedWindow {} @@ -320,7 +320,7 @@ impl UnownedWindow { os_error!(OsError::CreationError("Couldn't create `NSWindow`")) })?; - let (ns_view, cursor) = + let (ns_view, cursor_state) = unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| { unsafe { pool.drain() }; os_error!(OsError::CreationError("Couldn't create `NSView`")) @@ -368,8 +368,7 @@ impl UnownedWindow { input_context, shared_state: Arc::new(Mutex::new(win_attribs.into())), decorations: AtomicBool::new(decorations), - cursor, - cursor_visible: AtomicBool::new(true), + cursor_state, }); let delegate = new_delegate(&window, fullscreen.is_some()); @@ -516,8 +515,8 @@ impl UnownedWindow { pub fn set_cursor_icon(&self, cursor: CursorIcon) { let cursor = util::Cursor::from(cursor); - if let Some(cursor_access) = self.cursor.upgrade() { - *cursor_access.lock().unwrap() = cursor; + if let Some(cursor_access) = self.cursor_state.upgrade() { + cursor_access.lock().unwrap().cursor = cursor; } unsafe { let _: () = msg_send![*self.ns_window, @@ -535,16 +534,17 @@ impl UnownedWindow { #[inline] pub fn set_cursor_visible(&self, visible: bool) { - let cursor_class = class!(NSCursor); - // macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once. - // (otherwise, `hide_cursor(false)` would need to be called n times!) - if visible != self.cursor_visible.load(Ordering::Acquire) { - if visible { - let _: () = unsafe { msg_send![cursor_class, unhide] }; - } else { - let _: () = unsafe { msg_send![cursor_class, hide] }; + if let Some(cursor_access) = self.cursor_state.upgrade() { + let mut cursor_state = cursor_access.lock().unwrap(); + if visible != cursor_state.visible { + cursor_state.visible = visible; + drop(cursor_state); + unsafe { + let _: () = msg_send![*self.ns_window, + invalidateCursorRectsForView:*self.ns_view + ]; + } } - self.cursor_visible.store(visible, Ordering::Release); } } From 114c18e70d520ed3191bd17154cdad5a6c988ca2 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 4 Jan 2020 02:15:47 +0300 Subject: [PATCH 040/239] On X11, make `WINIT_HIDPI_FACTOR` dominate `Xft.dpi` in some cases (#1354) * On X11, make `WINIT_HIDPI_FACTOR` dominate `Xft.dpi` in some cases This commit makes `WINIT_HIDPI_FACTOR` dominate `Xft.dpi` in general and adds a special value `0` for `WINIT_HIDPI_FACTOR` to use winit computed DPI factor with randr over Xft.dpi. * Use `randr` instead of `0` for auto dpi scaling * Update CHANGELOG * blow up on wrong env var * Allow empty string for env var --- CHANGELOG.md | 2 + src/platform_impl/linux/x11/util/randr.rs | 70 +++++++++++++++++------ 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad7bfed0c6..b0126faf86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ - On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior. - Add `DeviceEvent::ModifiersChanged`. - Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`. +- On X11, `WINIT_HIDPI_FACTOR` now dominates `Xft.dpi` when picking DPI factor for output. +- On X11, add special value `randr` for `WINIT_HIDPI_FACTOR` to make winit use self computed DPI factor instead of the one from `Xft.dpi`. # 0.20.0 Alpha 5 (2019-12-09) diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 28fcb601ca..4630389e8d 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -6,24 +6,17 @@ use super::{ }; use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode}; +/// Represents values of `WINIT_HIDPI_FACTOR`. +pub enum EnvVarDPI { + Randr, + Scale(f64), + NotSet, +} + pub fn calc_dpi_factor( (width_px, height_px): (u32, u32), (width_mm, height_mm): (u64, u64), ) -> f64 { - // Override DPI if `WINIT_HIDPI_FACTOR` variable is set - let dpi_override = env::var("WINIT_HIDPI_FACTOR") - .ok() - .and_then(|var| f64::from_str(&var).ok()); - if let Some(dpi_override) = dpi_override { - if !validate_hidpi_factor(dpi_override) { - panic!( - "`WINIT_HIDPI_FACTOR` invalid; DPI factors must be normal floats greater than 0. Got `{}`", - dpi_override, - ); - } - return dpi_override; - } - // See http://xpra.org/trac/ticket/728 for more information. if width_mm == 0 || height_mm == 0 { warn!("XRandR reported that the display's 0mm in size, which is certifiably insane"); @@ -107,16 +100,55 @@ impl XConnection { (*output_info).nameLen as usize, ); let name = String::from_utf8_lossy(name_slice).into(); - let hidpi_factor = if let Some(dpi) = self.get_xft_dpi() { - dpi / 96. - } else { - calc_dpi_factor( + // Override DPI if `WINIT_HIDPI_FACTOR` variable is set + let dpi_env = env::var("WINIT_HIDPI_FACTOR").ok().map_or_else( + || EnvVarDPI::NotSet, + |var| { + if var.to_lowercase() == "randr" { + EnvVarDPI::Randr + } else if let Ok(dpi) = f64::from_str(&var) { + EnvVarDPI::Scale(dpi) + } else if var.is_empty() { + EnvVarDPI::NotSet + } else { + panic!( + "`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", + var + ); + } + }, + ); + + let hidpi_factor = match dpi_env { + EnvVarDPI::Randr => calc_dpi_factor( ((*crtc).width as u32, (*crtc).height as u32), ( (*output_info).mm_width as u64, (*output_info).mm_height as u64, ), - ) + ), + EnvVarDPI::Scale(dpi_override) => { + if !validate_hidpi_factor(dpi_override) { + panic!( + "`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", + dpi_override, + ); + } + dpi_override + } + EnvVarDPI::NotSet => { + if let Some(dpi) = self.get_xft_dpi() { + dpi / 96. + } else { + calc_dpi_factor( + ((*crtc).width as u32, (*crtc).height as u32), + ( + (*output_info).mm_width as u64, + (*output_info).mm_height as u64, + ), + ) + } + } }; (self.xrandr.XRRFreeOutputInfo)(output_info); From c0b46a03b55a016906d7174368c011acb997bd34 Mon Sep 17 00:00:00 2001 From: Osspial Date: Fri, 3 Jan 2020 18:17:05 -0500 Subject: [PATCH 041/239] Relase alpha 6 (#1338) * Relase alpha 6 * Update CHANGELOG.md Co-authored-by: Freya Gentz --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0126faf86..10c48558ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 0.20.0 Alpha 6 (2020-01-03) + - On macOS, fix `set_cursor_visible` hides cursor outside of window. - On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size. - On macOS, fix error when `set_fullscreen` is called during fullscreen transition. diff --git a/Cargo.toml b/Cargo.toml index 3f91840943..1869b1c809 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.20.0-alpha5" +version = "0.20.0-alpha6" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index 5511a4aa17..ff5b59020b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ```toml [dependencies] -winit = "0.20.0-alpha5" +winit = "0.20.0-alpha6" ``` ## [Documentation](https://docs.rs/winit) From d1c6506865c7bddbb5fb4d80a613e43ddc1370b5 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 4 Jan 2020 05:11:00 +0000 Subject: [PATCH 042/239] Fix ModifiersChanged event on X11 (#1358) --- CHANGELOG.md | 2 ++ src/platform_impl/linux/x11/util/input.rs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10c48558ea..55033c9c89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- On X11, fix `ModifiersChanged` emitting incorrect modifier change events + # 0.20.0 Alpha 6 (2020-01-03) - On macOS, fix `set_cursor_visible` hides cursor outside of window. diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index 5af033d336..e9f45aee1c 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -18,9 +18,9 @@ impl ModifiersState { pub(crate) fn from_x11_mask(mask: c_uint) -> Self { let mut m = ModifiersState::empty(); - m.set(ModifiersState::SHIFT, mask & ffi::Mod1Mask != 0); - m.set(ModifiersState::CTRL, mask & ffi::ShiftMask != 0); - m.set(ModifiersState::ALT, mask & ffi::ControlMask != 0); + m.set(ModifiersState::ALT, mask & ffi::Mod1Mask != 0); + m.set(ModifiersState::SHIFT, mask & ffi::ShiftMask != 0); + m.set(ModifiersState::CTRL, mask & ffi::ControlMask != 0); m.set(ModifiersState::LOGO, mask & ffi::Mod4Mask != 0); m } From 028d3ec16d2f71e9d60675c395151b9596d2a1b5 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 5 Jan 2020 02:12:03 -0500 Subject: [PATCH 043/239] Make examples set control_flow in a more realistic way (#1363) * Make examples set control_flow in a more realistic way * Format --- examples/cursor.rs | 58 +++++++++++++++++++---------------- examples/cursor_grab.rs | 1 + examples/custom_events.rs | 18 ++++++----- examples/min_max_size.rs | 3 +- examples/minimize.rs | 34 +++++++++++--------- examples/multiwindow.rs | 1 + examples/request_redraw.rs | 37 ++++++++++++---------- examples/resizable.rs | 1 + examples/transparent.rs | 3 +- examples/web.rs | 4 ++- examples/window.rs | 3 +- examples/window_icon.rs | 1 + examples/window_run_return.rs | 2 +- 13 files changed, 96 insertions(+), 70 deletions(-) diff --git a/examples/cursor.rs b/examples/cursor.rs index d5bd34a980..70c2ec0320 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -12,35 +12,39 @@ fn main() { let mut cursor_idx = 0; - event_loop.run(move |event, _, control_flow| match event { - Event::WindowEvent { - event: - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - .. - }, - .. - }, - .. - } => { - println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); - window.set_cursor_icon(CURSORS[cursor_idx]); - if cursor_idx < CURSORS.len() - 1 { - cursor_idx += 1; - } else { - cursor_idx = 0; + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + .. + }, + .. + }, + .. + } => { + println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]); + window.set_cursor_icon(CURSORS[cursor_idx]); + if cursor_idx < CURSORS.len() - 1 { + cursor_idx += 1; + } else { + cursor_idx = 0; + } } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + *control_flow = ControlFlow::Exit; + return; + } + _ => (), } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - *control_flow = ControlFlow::Exit; - return; - } - _ => (), }); } diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 13f14282e9..dd25179c1d 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -16,6 +16,7 @@ fn main() { event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; + match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, diff --git a/examples/custom_events.rs b/examples/custom_events.rs index dba4362470..3ed0c8b2e8 100644 --- a/examples/custom_events.rs +++ b/examples/custom_events.rs @@ -31,13 +31,17 @@ fn main() { } }); - event_loop.run(move |event, _, control_flow| match event { - Event::UserEvent(event) => println!("user event: {:?}", event), - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - _ => *control_flow = ControlFlow::Wait, + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::UserEvent(event) => println!("user event: {:?}", event), + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + } }); } diff --git a/examples/min_max_size.rs b/examples/min_max_size.rs index 4b24d16191..8a5b6699a0 100644 --- a/examples/min_max_size.rs +++ b/examples/min_max_size.rs @@ -14,6 +14,7 @@ fn main() { window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0))); event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; println!("{:?}", event); match event { @@ -21,7 +22,7 @@ fn main() { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit, - _ => *control_flow = ControlFlow::Wait, + _ => (), } }); } diff --git a/examples/minimize.rs b/examples/minimize.rs index 5576f53c4e..cce72de316 100644 --- a/examples/minimize.rs +++ b/examples/minimize.rs @@ -12,24 +12,28 @@ fn main() { .build(&event_loop) .unwrap(); - event_loop.run(move |event, _, control_flow| match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; - // Keyboard input event to handle minimize via a hotkey - Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, - window_id, - } => { - if window_id == window.id() { - // Pressing the 'M' key will minimize the window - if input.virtual_keycode == Some(VirtualKeyCode::M) { - window.set_minimized(true); + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + + // Keyboard input event to handle minimize via a hotkey + Event::WindowEvent { + event: WindowEvent::KeyboardInput { input, .. }, + window_id, + } => { + if window_id == window.id() { + // Pressing the 'M' key will minimize the window + if input.virtual_keycode == Some(VirtualKeyCode::M) { + window.set_minimized(true); + } } } + _ => (), } - _ => *control_flow = ControlFlow::Wait, }); } diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 61dc1fd7ec..57a20c5bab 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -16,6 +16,7 @@ fn main() { event_loop.run(move |event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; + match event { Event::WindowEvent { event, window_id } => { match event { diff --git a/examples/request_redraw.rs b/examples/request_redraw.rs index a84cffe9ee..ea6760d65e 100644 --- a/examples/request_redraw.rs +++ b/examples/request_redraw.rs @@ -1,8 +1,5 @@ -use instant::Instant; -use std::time::Duration; - use winit::{ - event::{Event, WindowEvent}, + event::{ElementState, Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; @@ -15,18 +12,26 @@ fn main() { .build(&event_loop) .unwrap(); - event_loop.run(move |event, _, control_flow| match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - Event::MainEventsCleared => { - window.request_redraw(); - *control_flow = ControlFlow::WaitUntil(Instant::now() + Duration::new(1, 0)) - } - Event::RedrawRequested(_) => { - println!("{:?}", event); + event_loop.run(move |event, _, control_flow| { + println!("{:?}", event); + + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::MouseInput { + state: ElementState::Released, + .. + } => { + window.request_redraw(); + } + _ => (), + }, + Event::RedrawRequested(_) => { + println!("\nredrawing!\n"); + } + _ => (), } - _ => (), }); } diff --git a/examples/resizable.rs b/examples/resizable.rs index 8aa6f70631..3ebf26bfd5 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -18,6 +18,7 @@ fn main() { event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; + match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, diff --git a/examples/transparent.rs b/examples/transparent.rs index 8eddb90272..a1fdefe93a 100644 --- a/examples/transparent.rs +++ b/examples/transparent.rs @@ -16,6 +16,7 @@ fn main() { window.set_title("A fantastic window!"); event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; println!("{:?}", event); match event { @@ -23,7 +24,7 @@ fn main() { event: WindowEvent::CloseRequested, .. } => *control_flow = ControlFlow::Exit, - _ => *control_flow = ControlFlow::Wait, + _ => (), } }); } diff --git a/examples/web.rs b/examples/web.rs index d5b92965af..46b024ccf2 100644 --- a/examples/web.rs +++ b/examples/web.rs @@ -40,6 +40,8 @@ pub fn main() { } event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + #[cfg(feature = "web-sys")] log::debug!("{:?}", event); @@ -54,7 +56,7 @@ pub fn main() { Event::MainEventsCleared => { window.request_redraw(); } - _ => *control_flow = ControlFlow::Wait, + _ => (), } }); } diff --git a/examples/window.rs b/examples/window.rs index e2265dbda0..02a484a0aa 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -13,6 +13,7 @@ fn main() { .unwrap(); event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; println!("{:?}", event); match event { @@ -23,7 +24,7 @@ fn main() { Event::MainEventsCleared => { window.request_redraw(); } - _ => *control_flow = ControlFlow::Poll, + _ => (), } }); } diff --git a/examples/window_icon.rs b/examples/window_icon.rs index 8fdcdd405e..86ea9556ca 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -27,6 +27,7 @@ fn main() { event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; + if let Event::WindowEvent { event, .. } = event { use winit::event::WindowEvent::*; match event { diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index 05f50e9db9..703c12eee5 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -38,7 +38,6 @@ fn main() { .. } => { quit = true; - *control_flow = ControlFlow::Exit; } Event::MainEventsCleared => { *control_flow = ControlFlow::Exit; @@ -48,6 +47,7 @@ fn main() { }); // Sleep for 1/60 second to simulate rendering + println!("rendering"); sleep(Duration::from_millis(16)); } } From 57f29aa6d7ae173173476e9e4f98e0b4ef801a74 Mon Sep 17 00:00:00 2001 From: icefoxen Date: Sun, 5 Jan 2020 11:02:41 -0500 Subject: [PATCH 044/239] Added some "how" and "why" docs to event handling. (#1032) * Added some "how" and "why" docs to event handling. Basically I had these questions when I started exploring the new event API's, and as I figured out the answers I put down more info about how everything works. This is not final, and suggestions are welcome -- the code example in the `event` module docs is particularly dubious, but it's how I'm used to thinking abou things so it only made sense to me once I wrote that. Note that my bias is towards using winit for writing games, so that's the sort of things I was interested in. This may not be valid for more general use cases. * cargo fmt * Fix minor typos * Revise event documentation * Update lib.rs docs * Update root docs Co-authored-by: Osspial --- src/event.rs | 92 ++++++++++++++++++++++++++++++++++++++++++---------- src/lib.rs | 80 +++++++++++++++++++++++++-------------------- 2 files changed, 119 insertions(+), 53 deletions(-) diff --git a/src/event.rs b/src/event.rs index d93324f7a8..e84224f344 100644 --- a/src/event.rs +++ b/src/event.rs @@ -3,6 +3,35 @@ //! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get //! processed and used to modify the program state. For more details, see the root-level documentation. //! +//! Some of these events represent different "parts" of a traditional event-handling loop. You could +//! approximate the basic ordering loop of [`EventLoop::run(...)`][event_loop_run] like this: +//! +//! ```rust,ignore +//! let mut control_flow = ControlFlow::Poll; +//! let mut start_cause = StartCause::Init; +//! +//! while control_flow != ControlFlow::Exit { +//! event_handler(NewEvents(start_cause), ..., &mut control_flow); +//! +//! for e in (window events, user events, device events) { +//! event_handler(e, ..., &mut control_flow); +//! } +//! event_handler(MainEventsCleared, ..., &mut control_flow); +//! +//! for w in (redraw windows) { +//! event_handler(RedrawRequested(w), ..., &mut control_flow); +//! } +//! event_handler(RedrawEventsCleared, ..., &mut control_flow); +//! +//! start_cause = wait_if_necessary(control_flow); +//! } +//! +//! event_handler(LoopDestroyed, ..., &mut control_flow); +//! ``` +//! +//! This leaves out timing details like `ControlFlow::WaitUntil` but hopefully +//! describes what happens in what order. +//! //! [event_loop_run]: crate::event_loop::EventLoop::run use instant::Instant; use std::path::PathBuf; @@ -14,48 +43,75 @@ use crate::{ }; /// Describes a generic event. +/// +/// See the module-level docs for more information on the event loop manages each event. #[derive(Clone, Debug, PartialEq)] pub enum Event { + /// Emitted when new events arrive from the OS to be processed. + /// + /// This event type is useful as a place to put code that should be done before you start + /// processing events, such as updating frame timing information for benchmarking or checking + /// the [`StartCause`][crate::event::StartCause] to see if a timer set by + /// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed. + NewEvents(StartCause), + /// Emitted when the OS sends an event to a winit window. WindowEvent { window_id: WindowId, event: WindowEvent, }, + /// Emitted when the OS sends an event to a device. DeviceEvent { device_id: DeviceId, event: DeviceEvent, }, + /// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event) UserEvent(T), - /// Emitted when new events arrive from the OS to be processed. - NewEvents(StartCause), - /// Emitted when all events (except for `RedrawRequested`) have been reported. + + /// Emitted when the application has been suspended. + Suspended, + + /// Emitted when the application has been resumed. + Resumed, + + /// Emitted when all of the event loop's input events have been processed and redraw processing + /// is about to begin. /// - /// This event is followed by zero or more instances of `RedrawRequested` - /// and, finally, `RedrawEventsCleared`. + /// This event is useful as a place to put your code that should be run after all + /// state-changing events have been handled and you want to do stuff (updating state, performing + /// calculations, etc) that happens as the "main body" of your event loop. If your program draws + /// graphics, it's usually better to do it in response to + /// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted + /// immediately after this event. MainEventsCleared, - /// The OS or application has requested that a window be redrawn. + /// Emitted after `MainEventsCleared` when a window should be redrawn. /// - /// Emitted only after `MainEventsCleared`. + /// This gets triggered in two scenarios: + /// - The OS has performed an operation that's invalidated the window's contents (such as + /// resizing the window). + /// - The application has explicitly requested a redraw via + /// [`Window::request_redraw`](crate::window::Window::request_redraw). + /// + /// During each iteration of the event loop, Winit will aggregate duplicate redraw requests + /// into a single event, to help avoid duplicating rendering work. RedrawRequested(WindowId), - /// Emitted after any `RedrawRequested` events. + /// Emitted after all `RedrawRequested` events have been processed and control flow is about to + /// be taken away from the program. If there are no `RedrawRequested` events, it is emitted + /// immediately after `MainEventsCleared`. /// - /// If there are no `RedrawRequested` events, it is reported immediately after - /// `MainEventsCleared`. + /// This event is useful for doing any cleanup or bookkeeping work after all the rendering + /// tasks have been completed. RedrawEventsCleared, - /// Emitted when the event loop is being shut down. This is irreversable - if this event is - /// emitted, it is guaranteed to be the last event emitted. + /// Emitted when the event loop is being shut down. + /// + /// This is irreversable - if this event is emitted, it is guaranteed to be the last event that + /// gets emitted. You generally want to treat this as an "do on quit" event. LoopDestroyed, - - /// Emitted when the application has been suspended. - Suspended, - - /// Emitted when the application has been resumed. - Resumed, } impl Event { diff --git a/src/lib.rs b/src/lib.rs index cd99b89fbe..9a9fc02c9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ -//! Winit allows you to build a window on as many platforms as possible. +//! Winit is a cross-platform window creation and event loop management library. //! -//! # Building a window +//! # Building windows //! //! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the //! [`EventLoop::new()`] function. @@ -15,26 +15,31 @@ //! - Calling [`Window::new(&event_loop)`][window_new]. //! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build]. //! -//! The first way is the simplest way and will give you default values for everything. -//! -//! The second way allows you to customize the way your [`Window`] will look and behave by modifying -//! the fields of the [`WindowBuilder`] object before you create the [`Window`]. +//! The first method is the simplest, and will give you default values for everything. The second +//! method allows you to customize the way your [`Window`] will look and behave by modifying the +//! fields of the [`WindowBuilder`] object before you create the [`Window`]. //! //! # Event handling //! //! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can -//! generate a [`WindowEvent`] when certain things happen, like whenever the user moves their mouse -//! or presses a key inside the [`Window`]. Devices can generate a [`DeviceEvent`] directly as well, -//! which contains unfiltered event data that isn't specific to a certain window. Some user -//! activity, like mouse movement, can generate both a [`WindowEvent`] *and* a [`DeviceEvent`]. You -//! can also create and handle your own custom [`UserEvent`]s, if desired. +//! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the +//! window or a key getting pressed while the window is focused. Devices can generate +//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window. +//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a +//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired. +//! +//! You can retreive events by calling [`EventLoop::run`][event_loop_run]. This function will +//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and +//! will run until the `control_flow` argument given to the closure is set to +//! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the +//! entire program terminates. //! -//! Events can be retreived by using an [`EventLoop`]. A [`Window`] will send its events to the -//! [`EventLoop`] object it was created with. +//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator`-based event loop +//! model, since that can't be implemented properly on web and mobile platforms and works poorly on +//! most desktop platforms. However, this model can be re-implemented to an extent on desktops with +//! [`EventLoopExtDesktop::run_return`]. See that method's documentation for more reasons about why +//! it's discouraged, beyond mobile/web compatibility reasons. //! -//! You do this by calling [`event_loop.run(...)`][event_loop_run]. This function will run forever -//! unless `control_flow` is set to [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] -//! is emitted and the entire program terminates. //! //! ```no_run //! use winit::{ @@ -47,7 +52,23 @@ //! let window = WindowBuilder::new().build(&event_loop).unwrap(); //! //! event_loop.run(move |event, _, control_flow| { +//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't +//! // dispatched any events. This is ideal for games and similar applications. +//! *control_flow = ControlFlow::Poll; +//! +//! // ControlFlow::Wait pauses the event loop if no events are available to process. +//! // This is ideal for non-game applications that only update in response to user +//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. +//! *control_flow = ControlFlow::Wait; +//! //! match event { +//! Event::WindowEvent { +//! event: WindowEvent::CloseRequested, +//! .. +//! } => { +//! println!("The close button was pressed; stopping"); +//! *control_flow = ControlFlow::Exit +//! }, //! Event::MainEventsCleared => { //! // Application update code. //! @@ -61,40 +82,29 @@ //! // rendering in here allows the program to gracefully handle redraws requested //! // by the OS. //! }, -//! Event::WindowEvent { -//! event: WindowEvent::CloseRequested, -//! .. -//! } => { -//! println!("The close button was pressed; stopping"); -//! *control_flow = ControlFlow::Exit -//! }, -//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't -//! // dispatched any events. This is ideal for games and similar applications. -//! _ => *control_flow = ControlFlow::Poll, -//! // ControlFlow::Wait pauses the event loop if no events are available to process. -//! // This is ideal for non-game applications that only update in response to user -//! // input, and uses significantly less power/CPU time than ControlFlow::Poll. -//! // _ => *control_flow = ControlFlow::Wait, +//! _ => () //! } //! }); //! ``` //! -//! If you use multiple [`Window`]s, [`Event`]`::`[`WindowEvent`] has a member named `window_id`. You can -//! compare it with the value returned by the [`id()`][window_id_fn] method of [`Window`] in order to know which -//! [`Window`] has received the event. +//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be +//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`] +//! dispatched the event. //! //! # Drawing on the window //! -//! Winit doesn't provide any function that allows drawing on a [`Window`]. However it allows you to +//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to //! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you -//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that will draw on the [`Window`]. +//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. //! //! [`EventLoop`]: event_loop::EventLoop +//! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return //! [`EventLoop::new()`]: event_loop::EventLoop::new //! [event_loop_run]: event_loop::EventLoop::run //! [`ControlFlow`]: event_loop::ControlFlow //! [`Exit`]: event_loop::ControlFlow::Exit //! [`Window`]: window::Window +//! [`WindowId`]: window::WindowId //! [`WindowBuilder`]: window::WindowBuilder //! [window_new]: window::Window::new //! [window_builder_new]: window::WindowBuilder::new From 2da24089de5c5634c04f57d12bdb5367d770e91f Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 5 Jan 2020 14:13:05 -0500 Subject: [PATCH 045/239] Replace Appveyor and Travis with Github Actions (#1309) * Experiment with github actions * Only use stable for testing simplicity * Disable fail-fast * Never fail when rustup target add fails * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Update rust.yml * Split formatting check into separate job * Update and rename rust.yml to ci.yml * Attempt to add web support * Fix things * Fixings * The "I'm not familiar with YAML" update * The empty string update * Fix target interpolation * Add gcc-multilib on linux x86 * Update ci.yml * Update ci.yml * Update ci.yml * Update ci.yml * Update ci.yml * Update ci.yml * Use correct host on Windows GNU * Update ci.yml * Update ci.yml * in my defense it was like 2 AM when I wrote this * Update ci.yml * Update ci.yml * Update ci.yml * Update ci.yml * try caching * Update ci.yml * Update ci.yml * Update on * Update ci.yml * Update ci.yml * Remove travis and appveyor testing * Cache entire cargo folder * Make cargo cache key more appropriately named * Reduce key collisions * Make key work * Add publish workflow and path qualifiers * Remove -f in cargo install cargo-web * continue-on-error for cargo web * ping * Try to shorten matrix * attempt two * attempt three * attempt four * Use bash * web feature formatting * ping --- .github/workflows/ci.yml | 107 ++++++++++++++++++++++++++++++++++ .github/workflows/publish.yml | 18 ++++++ .travis.yml | 104 --------------------------------- appveyor.yml | 35 ----------- 4 files changed, 125 insertions(+), 139 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..d552c4c1d7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,107 @@ +name: CI + +on: + pull_request: + paths: + - '**.rs' + - '**.toml' + - '.github/workflows/ci.yml' + push: + branches: [master] + paths: + - '**.rs' + - '**.toml' + - '.github/workflows/ci.yml' + +jobs: + Check_Formatting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: stable + components: rustfmt + - name: Check Formatting + run: cargo +stable fmt --all -- --check + + Tests: + strategy: + fail-fast: false + matrix: + rust_version: [stable, nightly] + platform: + - { target: x86_64-pc-windows-msvc, os: windows-latest, } + - { target: i686-pc-windows-msvc, os: windows-latest, } + - { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu } + - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } + - { target: i686-unknown-linux-gnu, os: ubuntu-latest, } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } + - { target: x86_64-apple-darwin, os: macos-latest, } + - { target: x86_64-apple-ios, os: macos-latest, } + - { target: armv7-apple-ios, os: macos-latest, } + - { target: aarch64-apple-ios, os: macos-latest, } + # We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web + # doesn't currently work on Linux. + - { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web } + - { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web } + + env: + RUST_BACKTRACE: 1 + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-C debuginfo=0" + FEATURES: ${{ format(',{0}', matrix.platform.features ) }} + WEB: ${{ matrix.platform.web }} + + runs-on: ${{ matrix.platform.os }} + steps: + - uses: actions/checkout@v1 + # Used to cache cargo-web + - name: Cache cargo folder + uses: actions/cache@v1 + with: + path: ~/.cargo + key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }} + + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }} + targets: ${{ matrix.platform.target }} + + - name: Install GCC Multilib + if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') + run: sudo apt-get update && sudo apt-get install gcc-multilib + - name: Install cargo-web + continue-on-error: true + if: contains(matrix.platform.target, 'wasm32') + run: cargo install cargo-web + + - name: Check documentation + shell: bash + if: matrix.platform.target != 'wasm32-unknown-unknown' + run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES + + - name: Build + shell: bash + run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES + + - name: Build tests + shell: bash + run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES + - name: Run tests + shell: bash + if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) + run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES + + + - name: Build with serde enabled + shell: bash + run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + + - name: Build tests with serde enabled + shell: bash + run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + - name: Run tests with serde enabled + shell: bash + if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) + run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000000..eac13a0a99 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,18 @@ +name: Publish + +on: + push: + branches: [master] + paths: "Cargo.toml" + +jobs: + Publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: stable + components: rustfmt + - name: Publish to crates.io + run: cargo publish --token ${{ secrets.cratesio_token }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 649b0106c9..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,104 +0,0 @@ -language: rust - -matrix: - include: - # Linux 32bit - - env: TARGET=i686-unknown-linux-gnu - os: linux - rust: nightly - addons: - apt: - # Cross compiler and cross compiled C libraries - packages: &i686_packages - - gcc-multilib - - env: TARGET=i686-unknown-linux-gnu - os: linux - rust: stable - addons: - apt: - packages: *i686_packages - - # Linux 64bit - - env: TARGET=x86_64-unknown-linux-gnu - os: linux - rust: nightly - - env: TARGET=x86_64-unknown-linux-gnu - os: linux - rust: stable - - # macOS - - env: TARGET=x86_64-apple-darwin - os: osx - rust: nightly - - env: TARGET=x86_64-apple-darwin - os: osx - rust: stable - - # iOS x86_64 - - env: TARGET=x86_64-apple-ios - os: osx - rust: nightly - - env: TARGET=x86_64-apple-ios - os: osx - rust: stable - - # iOS armv7 - - env: TARGET=armv7-apple-ios - os: osx - rust: nightly - - env: TARGET=armv7-apple-ios - os: osx - rust: stable - - # iOS arm64 - - env: TARGET=aarch64-apple-ios - os: osx - rust: nightly - - env: TARGET=aarch64-apple-ios - os: osx - rust: stable - - # wasm stdweb - - env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb - os: linux - rust: stable - - env: TARGET=wasm32-unknown-unknown WEB=web FEATURES=stdweb - os: linux - rust: nightly - # wasm web-sys - - env: TARGET=wasm32-unknown-unknown FEATURES=web-sys - os: linux - rust: stable - - env: TARGET=wasm32-unknown-unknown FEATURES=web-sys - os: linux - rust: nightly - -install: - - rustup self update - - rustup target add $TARGET; true - - rustup toolchain install stable - - rustup component add rustfmt --toolchain stable - -script: - - cargo +stable fmt --all -- --check - # Ensure that the documentation builds properly. - - cargo doc --no-deps - # Install cargo-web to build stdweb - - if [[ $WEB = "web" ]]; then cargo install -f cargo-web; fi - # Build without serde then with serde - - if [[ -z "$FEATURES" ]]; then - cargo $WEB build --target $TARGET --verbose; - else - cargo $WEB build --target $TARGET --features $FEATURES --verbose; - fi - - cargo $WEB build --target $TARGET --features serde,$FEATURES --verbose - # Running iOS apps on macOS requires the Simulator so we skip that for now - # The web targets also don't support running tests - - if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --verbose; fi - - if [[ $TARGET != *-apple-ios && $TARGET != wasm32-* ]]; then cargo test --target $TARGET --features serde --verbose; fi - -after_success: - - | - [ $TRAVIS_BRANCH = master ] && - [ $TRAVIS_PULL_REQUEST = false ] && - cargo publish --token ${CRATESIO_TOKEN} diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 20ee2cc7b4..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,35 +0,0 @@ -environment: - matrix: - - TARGET: x86_64-pc-windows-msvc - CHANNEL: stable - - TARGET: i686-pc-windows-msvc - CHANNEL: stable - - TARGET: x86_64-pc-windows-gnu - CHANNEL: stable - - TARGET: i686-pc-windows-gnu - CHANNEL: stable - - TARGET: x86_64-pc-windows-msvc - CHANNEL: nightly - - TARGET: i686-pc-windows-msvc - CHANNEL: nightly - - TARGET: x86_64-pc-windows-gnu - CHANNEL: nightly - - TARGET: i686-pc-windows-gnu - CHANNEL: nightly -matrix: - allow_failures: - - CHANNEL: nightly -install: - - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - rustup-init -yv --default-toolchain %CHANNEL% --default-host %TARGET% - - SET PATH=%PATH%;%USERPROFILE%\.cargo\bin - - SET PATH=%PATH%;C:\MinGW\bin - - rustc -V - - cargo -V - -build: false - -test_script: - - cargo test --verbose - - cargo test --features serde --verbose - - cargo doc --no-deps From f379d069b9eb3234e70edfbeba17153f9367eb9a Mon Sep 17 00:00:00 2001 From: Osspial Date: Wed, 19 Jun 2019 16:49:43 -0400 Subject: [PATCH 046/239] WIP - Make EL2 DPI changes and implement on Windows (#895) * Modify DPI API publicly and on Windows * Add generic Position and make dpi creation functions const * Make examples work * Fix fullscreen windows not appearing * Replace Logical coordinates in window events with Physical coordinates * Update HiDpiFactorChanged * Document to_static --- examples/multithreaded.rs | 32 +-- examples/resizable.rs | 3 +- examples/window.rs | 1 + src/dpi.rs | 116 +++++++--- src/event.rs | 128 +++++++++-- src/event_loop.rs | 2 +- src/platform/desktop.rs | 12 +- src/platform_impl/windows/dpi.rs | 4 - src/platform_impl/windows/drop_handler.rs | 6 +- src/platform_impl/windows/event_loop.rs | 158 +++++++------- .../windows/event_loop/runner.rs | 43 ++-- src/platform_impl/windows/monitor.rs | 12 +- src/platform_impl/windows/util.rs | 6 +- src/platform_impl/windows/window.rs | 201 +++++------------- src/platform_impl/windows/window_state.rs | 6 +- src/window.rs | 58 +++-- 16 files changed, 423 insertions(+), 365 deletions(-) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index edfe72cfc8..8ac3bbaad8 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -5,20 +5,21 @@ fn main() { use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use winit::{ + dpi::{PhysicalPosition, PhysicalSize}, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::{CursorIcon, Fullscreen, WindowBuilder}, }; const WINDOW_COUNT: usize = 3; - const WINDOW_SIZE: (u32, u32) = (600, 400); + const WINDOW_SIZE: PhysicalSize = PhysicalSize::new(600, 400); env_logger::init(); let event_loop = EventLoop::new(); let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); for _ in 0..WINDOW_COUNT { let window = WindowBuilder::new() - .with_inner_size(WINDOW_SIZE.into()) + .with_inner_size(WINDOW_SIZE) .build(&event_loop) .unwrap(); @@ -101,7 +102,7 @@ fn main() { println!("-> fullscreen : {:?}", window.fullscreen()); } L => window.set_min_inner_size(match state { - true => Some(WINDOW_SIZE.into()), + true => Some(WINDOW_SIZE), false => None, }), M => window.set_maximized(state), @@ -114,17 +115,18 @@ fn main() { }), Q => window.request_redraw(), R => window.set_resizable(state), - S => window.set_inner_size( - match state { - true => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100), - false => WINDOW_SIZE, - } - .into(), - ), + S => window.set_inner_size(match state { + true => PhysicalSize::new( + WINDOW_SIZE.width + 100, + WINDOW_SIZE.height + 100, + ), + false => WINDOW_SIZE, + }), W => window - .set_cursor_position( - (WINDOW_SIZE.0 as i32 / 2, WINDOW_SIZE.1 as i32 / 2).into(), - ) + .set_cursor_position(PhysicalPosition::new( + WINDOW_SIZE.width as f64 / 2.0, + WINDOW_SIZE.height as f64 / 2.0, + )) .unwrap(), Z => { window.set_visible(false); @@ -161,7 +163,9 @@ fn main() { } _ => { if let Some(tx) = window_senders.get(&window_id) { - tx.send(event).unwrap(); + if let Some(event) = event.to_static() { + tx.send(event).unwrap(); + } } } }, diff --git a/examples/resizable.rs b/examples/resizable.rs index 3ebf26bfd5..2ddb8b25e9 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -1,4 +1,5 @@ use winit::{ + dpi::LogicalSize, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, @@ -11,7 +12,7 @@ fn main() { let window = WindowBuilder::new() .with_title("Hit space to toggle resizability.") - .with_inner_size((400, 200).into()) + .with_inner_size(LogicalSize::new(400.0, 200.0)) .with_resizable(resizable) .build(&event_loop) .unwrap(); diff --git a/examples/window.rs b/examples/window.rs index 02a484a0aa..331ee98aa4 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -9,6 +9,7 @@ fn main() { let window = WindowBuilder::new() .with_title("A fantastic window!") + .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) .build(&event_loop) .unwrap(); diff --git a/src/dpi.rs b/src/dpi.rs index 8e6c80fa99..4fc89e528d 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -100,7 +100,7 @@ pub struct LogicalPosition { impl LogicalPosition { #[inline] - pub fn new(x: f64, y: f64) -> Self { + pub const fn new(x: f64, y: f64) -> Self { LogicalPosition { x, y } } @@ -161,7 +161,7 @@ pub struct PhysicalPosition { impl PhysicalPosition { #[inline] - pub fn new(x: f64, y: f64) -> Self { + pub const fn new(x: f64, y: f64) -> Self { PhysicalPosition { x, y } } @@ -222,7 +222,7 @@ pub struct LogicalSize { impl LogicalSize { #[inline] - pub fn new(width: f64, height: f64) -> Self { + pub const fn new(width: f64, height: f64) -> Self { LogicalSize { width, height } } @@ -236,7 +236,7 @@ impl LogicalSize { assert!(validate_hidpi_factor(dpi_factor)); let width = self.width * dpi_factor; let height = self.height * dpi_factor; - PhysicalSize::new(width, height) + PhysicalSize::new(width.round() as _, height.round() as _) } } @@ -270,20 +270,16 @@ impl Into<(u32, u32)> for LogicalSize { } /// A size represented in physical pixels. -/// -/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, -/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which -/// does the rounding for you. -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PhysicalSize { - pub width: f64, - pub height: f64, + pub width: u32, + pub height: u32, } impl PhysicalSize { #[inline] - pub fn new(width: f64, height: f64) -> Self { + pub const fn new(width: u32, height: u32) -> Self { PhysicalSize { width, height } } @@ -295,37 +291,105 @@ impl PhysicalSize { #[inline] pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize { assert!(validate_hidpi_factor(dpi_factor)); - let width = self.width / dpi_factor; - let height = self.height / dpi_factor; + let width = self.width as f64 / dpi_factor; + let height = self.height as f64 / dpi_factor; LogicalSize::new(width, height) } } -impl From<(f64, f64)> for PhysicalSize { +impl From<(u32, u32)> for PhysicalSize { #[inline] - fn from((width, height): (f64, f64)) -> Self { + fn from((width, height): (u32, u32)) -> Self { Self::new(width, height) } } -impl From<(u32, u32)> for PhysicalSize { +impl Into<(u32, u32)> for PhysicalSize { + /// Note that this rounds instead of truncating. #[inline] - fn from((width, height): (u32, u32)) -> Self { - Self::new(width as f64, height as f64) + fn into(self) -> (u32, u32) { + (self.width, self.height) } } -impl Into<(f64, f64)> for PhysicalSize { +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Size { + Physical(PhysicalSize), + Logical(LogicalSize), +} + +impl Size { + pub fn new>(size: S) -> Size { + size.into() + } + + pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize { + match *self { + Size::Physical(size) => size.to_logical(dpi_factor), + Size::Logical(size) => size, + } + } + + pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize { + match *self { + Size::Physical(size) => size, + Size::Logical(size) => size.to_physical(dpi_factor), + } + } +} + +impl From for Size { #[inline] - fn into(self) -> (f64, f64) { - (self.width, self.height) + fn from(size: PhysicalSize) -> Size { + Size::Physical(size) } } -impl Into<(u32, u32)> for PhysicalSize { - /// Note that this rounds instead of truncating. +impl From for Size { #[inline] - fn into(self) -> (u32, u32) { - (self.width.round() as _, self.height.round() as _) + fn from(size: LogicalSize) -> Size { + Size::Logical(size) + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Position { + Physical(PhysicalPosition), + Logical(LogicalPosition), +} + +impl Position { + pub fn new>(position: S) -> Position { + position.into() + } + + pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition { + match *self { + Position::Physical(position) => position.to_logical(dpi_factor), + Position::Logical(position) => position, + } + } + + pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition { + match *self { + Position::Physical(position) => position, + Position::Logical(position) => position.to_physical(dpi_factor), + } + } +} + +impl From for Position { + #[inline] + fn from(position: PhysicalPosition) -> Position { + Position::Physical(position) + } +} + +impl From for Position { + #[inline] + fn from(position: LogicalPosition) -> Position { + Position::Logical(position) } } diff --git a/src/event.rs b/src/event.rs index e84224f344..85899d3e03 100644 --- a/src/event.rs +++ b/src/event.rs @@ -37,7 +37,7 @@ use instant::Instant; use std::path::PathBuf; use crate::{ - dpi::{LogicalPosition, LogicalSize}, + dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}, platform_impl, window::{Theme, WindowId}, }; @@ -45,8 +45,8 @@ use crate::{ /// Describes a generic event. /// /// See the module-level docs for more information on the event loop manages each event. -#[derive(Clone, Debug, PartialEq)] -pub enum Event { +#[derive(Debug, PartialEq)] +pub enum Event<'a, T: 'static> { /// Emitted when new events arrive from the OS to be processed. /// /// This event type is useful as a place to put code that should be done before you start @@ -58,7 +58,7 @@ pub enum Event { /// Emitted when the OS sends an event to a winit window. WindowEvent { window_id: WindowId, - event: WindowEvent, + event: WindowEvent<'a>, }, /// Emitted when the OS sends an event to a device. @@ -114,8 +114,8 @@ pub enum Event { LoopDestroyed, } -impl Event { - pub fn map_nonuser_event(self) -> Result, Event> { +impl<'a, T> Event<'a, T> { + pub fn map_nonuser_event(self) -> Result, Event<'a, T>> { use self::Event::*; match self { UserEvent(_) => Err(self), @@ -130,6 +130,26 @@ impl Event { Resumed => Ok(Resumed), } } + + /// If the event doesn't contain a reference, turn it into an event with a `'static` lifetime. + /// Otherwise, return `None`. + pub fn to_static(self) -> Option> { + use self::Event::*; + match self { + WindowEvent { window_id, event } => event + .to_static() + .map(|event| WindowEvent { window_id, event }), + UserEvent(_) => None, + DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }), + NewEvents(cause) => Some(NewEvents(cause)), + MainEventsCleared => Some(MainEventsCleared), + RedrawRequested(wid) => Some(RedrawRequested(wid)), + RedrawEventsCleared => Some(RedrawEventsCleared), + LoopDestroyed => Some(LoopDestroyed), + Suspended => Some(Suspended), + Resumed => Some(Resumed), + } + } } /// Describes the reason the event loop is resuming. @@ -159,13 +179,13 @@ pub enum StartCause { } /// Describes an event from a `Window`. -#[derive(Clone, Debug, PartialEq)] -pub enum WindowEvent { +#[derive(Debug, PartialEq)] +pub enum WindowEvent<'a> { /// The size of the window has changed. Contains the client area's new dimensions. - Resized(LogicalSize), + Resized(PhysicalSize), /// The position of the window has changed. Contains the window's new position. - Moved(LogicalPosition), + Moved(PhysicalPosition), /// The window has been requested to close. CloseRequested, @@ -222,7 +242,7 @@ pub enum WindowEvent { /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. - position: LogicalPosition, + position: PhysicalPosition, #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -280,8 +300,16 @@ pub enum WindowEvent { /// * Changing the display's DPI factor (e.g. in Control Panel on Windows). /// * Moving the window to a display with a different DPI factor. /// - /// For more information about DPI in general, see the [`dpi`](crate::dpi) module. - HiDpiFactorChanged(f64), + /// After this event callback has been processed, the window will be resized to whatever value + /// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested + /// by the OS, but it can be changed to any value. If `new_inner_size` is set to `None`, no resizing + /// will occur. + /// + /// For more information about DPI in general, see the [`dpi`](dpi/index.html) module. + HiDpiFactorChanged { + hidpi_factor: f64, + new_inner_size: &'a mut Option, + }, /// The system window theme has changed. /// @@ -292,6 +320,78 @@ pub enum WindowEvent { ThemeChanged(Theme), } +impl<'a> WindowEvent<'a> { + pub fn to_static(self) -> Option> { + use self::WindowEvent::*; + match self { + Resized(size) => Some(Resized(size)), + Moved(position) => Some(Moved(position)), + CloseRequested => Some(CloseRequested), + Destroyed => Some(Destroyed), + DroppedFile(file) => Some(DroppedFile(file)), + HoveredFile(file) => Some(HoveredFile(file)), + HoveredFileCancelled => Some(HoveredFileCancelled), + ReceivedCharacter(c) => Some(ReceivedCharacter(c)), + Focused(focused) => Some(Focused(focused)), + KeyboardInput { device_id, input, is_synthetic } => Some(KeyboardInput { device_id, input, is_synthetic }), + CursorMoved { + device_id, + position, + modifiers, + } => Some(CursorMoved { + device_id, + position, + modifiers, + }), + CursorEntered { device_id } => Some(CursorEntered { device_id }), + CursorLeft { device_id } => Some(CursorLeft { device_id }), + MouseWheel { + device_id, + delta, + phase, + modifiers, + } => Some(MouseWheel { + device_id, + delta, + phase, + modifiers, + }), + MouseInput { + device_id, + state, + button, + modifiers, + } => Some(MouseInput { + device_id, + state, + button, + modifiers, + }), + TouchpadPressure { + device_id, + pressure, + stage, + } => Some(TouchpadPressure { + device_id, + pressure, + stage, + }), + AxisMotion { + device_id, + axis, + value, + } => Some(AxisMotion { + device_id, + axis, + value, + }), + Touch(touch) => Some(Touch(touch)), + ThemeChanged(theme) => Some(ThemeChanged(theme)), + HiDpiFactorChanged { .. } => None, + } + } +} + /// Identifier of an input device. /// /// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which @@ -426,7 +526,7 @@ pub enum TouchPhase { pub struct Touch { pub device_id: DeviceId, pub phase: TouchPhase, - pub location: LogicalPosition, + pub location: PhysicalPosition, /// Describes how hard the screen was pressed. May be `None` if the platform /// does not support pressure sensitivity. /// diff --git a/src/event_loop.rs b/src/event_loop.rs index 105af242e8..e205c2316a 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -143,7 +143,7 @@ impl EventLoop { #[inline] pub fn run(self, event_handler: F) -> ! where - F: 'static + FnMut(Event, &EventLoopWindowTarget, &mut ControlFlow), + F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget, &mut ControlFlow), { self.event_loop.run(event_handler) } diff --git a/src/platform/desktop.rs b/src/platform/desktop.rs index 1ec2056230..df80143162 100644 --- a/src/platform/desktop.rs +++ b/src/platform/desktop.rs @@ -30,7 +30,11 @@ pub trait EventLoopExtDesktop { /// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary. fn run_return(&mut self, event_handler: F) where - F: FnMut(Event, &EventLoopWindowTarget, &mut ControlFlow); + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ); } impl EventLoopExtDesktop for EventLoop { @@ -38,7 +42,11 @@ impl EventLoopExtDesktop for EventLoop { fn run_return(&mut self, event_handler: F) where - F: FnMut(Event, &EventLoopWindowTarget, &mut ControlFlow), + F: FnMut( + Event<'_, Self::UserEvent>, + &EventLoopWindowTarget, + &mut ControlFlow, + ), { self.event_loop.run_return(event_handler) } diff --git a/src/platform_impl/windows/dpi.rs b/src/platform_impl/windows/dpi.rs index 05af503e25..d76c434461 100644 --- a/src/platform_impl/windows/dpi.rs +++ b/src/platform_impl/windows/dpi.rs @@ -141,7 +141,3 @@ pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { } } } - -pub fn hwnd_scale_factor(hwnd: HWND) -> f64 { - dpi_to_scale_factor(unsafe { hwnd_dpi(hwnd) }) -} diff --git a/src/platform_impl/windows/drop_handler.rs b/src/platform_impl/windows/drop_handler.rs index feec263964..f6c7a044d4 100644 --- a/src/platform_impl/windows/drop_handler.rs +++ b/src/platform_impl/windows/drop_handler.rs @@ -31,7 +31,7 @@ pub struct FileDropHandlerData { pub interface: IDropTarget, refcount: AtomicUsize, window: HWND, - send_event: Box)>, + send_event: Box)>, cursor_effect: DWORD, hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted */ } @@ -42,7 +42,7 @@ pub struct FileDropHandler { #[allow(non_snake_case)] impl FileDropHandler { - pub fn new(window: HWND, send_event: Box)>) -> FileDropHandler { + pub fn new(window: HWND, send_event: Box)>) -> FileDropHandler { let data = Box::new(FileDropHandlerData { interface: IDropTarget { lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl, @@ -227,7 +227,7 @@ impl FileDropHandler { } impl FileDropHandlerData { - fn send_event(&self, event: Event<()>) { + fn send_event(&self, event: Event<'static, ()>) { (self.send_event)(event); } } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 88de492ca2..387b0fd40f 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -29,7 +29,6 @@ use std::{ use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR}; use winapi::{ - ctypes::c_int, shared::{ minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WPARAM}, windef::{HWND, POINT, RECT}, @@ -37,20 +36,20 @@ use winapi::{ }, um::{ commctrl, libloaderapi, ole2, processthreadsapi, winbase, - winnt::{HANDLE, LONG, LPCSTR, SHORT}, + winnt::{HANDLE, LPCSTR, SHORT}, winuser, }, }; use self::runner::{ELRShared, EventLoopRunnerShared}; use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalSize}, + dpi::{PhysicalPosition, PhysicalSize}, event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, platform_impl::platform::{ dark_mode::try_dark_mode, dpi::{ - become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_scale_factor, + become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling, }, drop_handler::FileDropHandler, event::{ @@ -97,26 +96,30 @@ lazy_static! { get_function!("user32.dll", GetPointerPenInfo); } -pub(crate) struct SubclassInput { +pub(crate) struct SubclassInput { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, pub file_drop_handler: FileDropHandler, } impl SubclassInput { - unsafe fn send_event(&self, event: Event) { + unsafe fn send_event(&self, event: Event<'static, T>) { self.event_loop_runner.send_event(event); } + + unsafe fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> { + self.event_loop_runner.send_event_unbuffered(event) + } } -struct ThreadMsgTargetSubclassInput { +struct ThreadMsgTargetSubclassInput { event_loop_runner: EventLoopRunnerShared, user_event_receiver: Receiver, modifiers_state: ModifiersStateSide, } impl ThreadMsgTargetSubclassInput { - unsafe fn send_event(&self, event: Event) { + unsafe fn send_event(&self, event: Event<'static, T>) { self.event_loop_runner.send_event(event); } } @@ -126,7 +129,7 @@ pub struct EventLoop { window_target: RootELW, } -pub struct EventLoopWindowTarget { +pub struct EventLoopWindowTarget { thread_id: DWORD, thread_msg_target: HWND, pub(crate) runner_shared: EventLoopRunnerShared, @@ -191,7 +194,7 @@ impl EventLoop { pub fn run(mut self, event_handler: F) -> ! where - F: 'static + FnMut(Event, &RootELW, &mut ControlFlow), + F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { self.run_return(event_handler); ::std::process::exit(0); @@ -199,7 +202,7 @@ impl EventLoop { pub fn run_return(&mut self, mut event_handler: F) where - F: FnMut(Event, &RootELW, &mut ControlFlow), + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { let event_loop_windows_ref = &self.window_target; @@ -465,13 +468,6 @@ lazy_static! { winuser::RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr() as LPCSTR) } }; - // Message sent by a `Window` after creation if it has a DPI != 96. - // WPARAM is the the DPI (u32). LOWORD of LPARAM is width, and HIWORD is height. - pub static ref INITIAL_DPI_MSG_ID: u32 = { - unsafe { - winuser::RegisterWindowMessageA("Winit::InitialDpiMsg\0".as_ptr() as LPCSTR) - } - }; // WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the // documentation in the `window_state` module for more information. pub static ref SET_RETAIN_STATE_ON_SIZE_MSG_ID: u32 = unsafe { @@ -597,7 +593,7 @@ fn normalize_pointer_pressure(pressure: u32) -> Option { // // Returning 0 tells the Win32 API that the message has been processed. // FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary -unsafe extern "system" fn public_window_callback( +unsafe extern "system" fn public_window_callback( window: HWND, msg: UINT, wparam: WPARAM, @@ -713,12 +709,11 @@ unsafe extern "system" fn public_window_callback( let windowpos = lparam as *const winuser::WINDOWPOS; if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE { - let dpi_factor = hwnd_scale_factor(window); - let logical_position = - LogicalPosition::from_physical(((*windowpos).x, (*windowpos).y), dpi_factor); + let physical_position = + PhysicalPosition::new((*windowpos).x as f64, (*windowpos).y as f64); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: Moved(logical_position), + event: Moved(physical_position), }); } @@ -731,11 +726,10 @@ unsafe extern "system" fn public_window_callback( let w = LOWORD(lparam as DWORD) as u32; let h = HIWORD(lparam as DWORD) as u32; - let dpi_factor = hwnd_scale_factor(window); - let logical_size = LogicalSize::from_physical((w, h), dpi_factor); + let physical_size = PhysicalSize::new(w, h); let event = Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: Resized(logical_size), + event: Resized(physical_size), }; { @@ -840,8 +834,7 @@ unsafe extern "system" fn public_window_callback( let x = windowsx::GET_X_LPARAM(lparam) as f64; let y = windowsx::GET_Y_LPARAM(lparam) as f64; - let dpi_factor = hwnd_scale_factor(window); - let position = LogicalPosition::from_physical((x, y), dpi_factor); + let position = PhysicalPosition::new(x, y); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1130,7 +1123,6 @@ unsafe extern "system" fn public_window_callback( mem::size_of::() as INT, ) > 0 { - let dpi_factor = hwnd_scale_factor(window); for input in &inputs { let mut location = POINT { x: input.x / 100, @@ -1143,7 +1135,7 @@ unsafe extern "system" fn public_window_callback( let x = location.x as f64 + (input.x % 100) as f64 / 100f64; let y = location.y as f64 + (input.y % 100) as f64 / 100f64; - let location = LogicalPosition::from_physical((x, y), dpi_factor); + let location = PhysicalPosition::new(x, y); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Touch(Touch { @@ -1204,7 +1196,6 @@ unsafe extern "system" fn public_window_callback( return 0; } - let dpi_factor = hwnd_scale_factor(window); // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getpointerframeinfohistory // The information retrieved appears in reverse chronological order, with the most recent entry in the first // row of the returned array @@ -1282,7 +1273,7 @@ unsafe extern "system" fn public_window_callback( let x = location.x as f64 + x.fract(); let y = location.y as f64 + y.fract(); - let location = LogicalPosition::from_physical((x, y), dpi_factor); + let location = PhysicalPosition::new(x, y); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::Touch(Touch { @@ -1446,27 +1437,66 @@ unsafe extern "system" fn public_window_callback( new_dpi_factor != old_dpi_factor && window_state.fullscreen.is_none() }; - // This prevents us from re-applying DPI adjustment to the restored size after exiting - // fullscreen (the restored size is already DPI adjusted). - if allow_resize { - // Resize window to the size suggested by Windows. - let rect = &*(lparam as *const RECT); + let style = winuser::GetWindowLongW(window, winuser::GWL_STYLE) as _; + let style_ex = winuser::GetWindowLongW(window, winuser::GWL_EXSTYLE) as _; + let b_menu = !winuser::GetMenu(window).is_null() as BOOL; + + // New size as suggested by Windows. + let rect = *(lparam as *const RECT); + + // The window rect provided is the window's outer size, not it's inner size. However, + // win32 doesn't provide an `UnadjustWindowRectEx` function to get the client rect from + // the outer rect, so we instead adjust the window rect to get the decoration margins + // and remove them from the outer size. + let margins_horizontal: u32; + let margins_vertical: u32; + { + let mut adjusted_rect = rect; + winuser::AdjustWindowRectExForDpi( + &mut adjusted_rect, + style, + b_menu, + style_ex, + new_dpi_x, + ); + let margin_left = rect.left - adjusted_rect.left; + let margin_right = adjusted_rect.right - rect.right; + let margin_top = rect.top - adjusted_rect.top; + let margin_bottom = adjusted_rect.bottom - rect.bottom; + + margins_horizontal = (margin_left + margin_right) as u32; + margins_vertical = (margin_bottom + margin_top) as u32; + } + + let physical_inner_rect = PhysicalSize::new( + (rect.right - rect.left) as u32 - margins_horizontal, + (rect.bottom - rect.top) as u32 - margins_vertical, + ); + + // `allow_resize` prevents us from re-applying DPI adjustment to the restored size after + // exiting fullscreen (the restored size is already DPI adjusted). + let mut new_inner_rect_opt = Some(physical_inner_rect).filter(|_| allow_resize); + + let _ = subclass_input.send_event_unbuffered(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: HiDpiFactorChanged { + hidpi_factor: new_dpi_factor, + new_inner_size: &mut new_inner_rect_opt, + }, + }); + + if let Some(new_inner_rect) = new_inner_rect_opt { winuser::SetWindowPos( window, ptr::null_mut(), rect.left, rect.top, - rect.right - rect.left, - rect.bottom - rect.top, + (new_inner_rect.width + margins_horizontal) as _, + (new_inner_rect.height + margins_vertical) as _, winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE, ); } - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: HiDpiFactorChanged(new_dpi_factor), - }); - 0 } @@ -1502,44 +1532,6 @@ unsafe extern "system" fn public_window_callback( f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam != 0) }); 0 - } else if msg == *INITIAL_DPI_MSG_ID { - use crate::event::WindowEvent::HiDpiFactorChanged; - let scale_factor = dpi_to_scale_factor(wparam as u32); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: HiDpiFactorChanged(scale_factor), - }); - // Automatically resize for actual DPI - let width = LOWORD(lparam as DWORD) as u32; - let height = HIWORD(lparam as DWORD) as u32; - let (adjusted_width, adjusted_height): (u32, u32) = - PhysicalSize::from_logical((width, height), scale_factor).into(); - // We're not done yet! `SetWindowPos` needs the window size, not the client area size. - let mut rect = RECT { - top: 0, - left: 0, - bottom: adjusted_height as LONG, - right: adjusted_width as LONG, - }; - let dw_style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD; - let b_menu = !winuser::GetMenu(window).is_null() as BOOL; - let dw_style_ex = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD; - winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); - let outer_x = (rect.right - rect.left).abs() as c_int; - let outer_y = (rect.top - rect.bottom).abs() as c_int; - winuser::SetWindowPos( - window, - ptr::null_mut(), - 0, - 0, - outer_x, - outer_y, - winuser::SWP_NOMOVE - | winuser::SWP_NOREPOSITION - | winuser::SWP_NOZORDER - | winuser::SWP_NOACTIVATE, - ); - 0 } else { commctrl::DefSubclassProc(window, msg, wparam, lparam) } @@ -1547,7 +1539,7 @@ unsafe extern "system" fn public_window_callback( } } -unsafe extern "system" fn thread_event_target_callback( +unsafe extern "system" fn thread_event_target_callback( window: HWND, msg: UINT, wparam: WPARAM, diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index c9b05fb695..db807caca2 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -10,17 +10,17 @@ use crate::{ }; pub(crate) type EventLoopRunnerShared = Rc>; -pub(crate) struct ELRShared { +pub(crate) struct ELRShared { runner: RefCell>>, - buffer: RefCell>>, + buffer: RefCell>>, redraw_buffer: Rc>>, } -struct EventLoopRunner { +struct EventLoopRunner { control_flow: ControlFlow, runner_state: RunnerState, modal_redraw_window: HWND, in_modal_loop: bool, - event_handler: Box, &mut ControlFlow)>, + event_handler: Box, &mut ControlFlow)>, panic_error: Option, redraw_buffer: Rc>>, } @@ -37,7 +37,7 @@ impl ELRShared { pub(crate) unsafe fn set_runner(&self, event_loop: &EventLoop, f: F) where - F: FnMut(Event, &mut ControlFlow), + F: FnMut(Event<'_, T>, &mut ControlFlow), { let mut runner = EventLoopRunner::new(event_loop, self.redraw_buffer.clone(), f); { @@ -66,7 +66,18 @@ impl ELRShared { } } - pub(crate) unsafe fn send_event(&self, event: Event) { + pub(crate) unsafe fn send_event(&self, event: Event<'static, T>) { + if let Err(event) = self.send_event_unbuffered(event) { + // If the runner is already borrowed, we're in the middle of an event loop invocation. Add + // the event to a buffer to be processed later. + self.buffer_event(event); + } + } + + pub(crate) unsafe fn send_event_unbuffered<'e>( + &self, + event: Event<'e, T>, + ) -> Result<(), Event<'e, T>> { if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { if let Some(ref mut runner) = *runner_ref { runner.process_event(event); @@ -84,16 +95,14 @@ impl ELRShared { } } - return; + return Ok(()); } } - // If the runner is already borrowed, we're in the middle of an event loop invocation. Add - // the event to a buffer to be processed later. - self.buffer_event(event); + Err(event) } - pub(crate) unsafe fn call_event_handler(&self, event: Event) { + pub(crate) unsafe fn call_event_handler(&self, event: Event<'static, T>) { if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { if let Some(ref mut runner) = *runner_ref { runner.call_event_handler(event); @@ -143,7 +152,7 @@ impl ELRShared { } } - fn buffer_event(&self, event: Event) { + fn buffer_event(&self, event: Event<'static, T>) { match event { Event::RedrawRequested(window_id) => { self.redraw_buffer.borrow_mut().push_back(window_id) @@ -176,7 +185,7 @@ impl EventLoopRunner { f: F, ) -> EventLoopRunner where - F: FnMut(Event, &mut ControlFlow), + F: FnMut(Event<'_, T>, &mut ControlFlow), { EventLoopRunner { control_flow: ControlFlow::default(), @@ -184,8 +193,8 @@ impl EventLoopRunner { in_modal_loop: false, modal_redraw_window: event_loop.window_target.p.thread_msg_target, event_handler: mem::transmute::< - Box, &mut ControlFlow)>, - Box, &mut ControlFlow)>, + Box, &mut ControlFlow)>, + Box, &mut ControlFlow)>, >(Box::new(f)), panic_error: None, redraw_buffer, @@ -251,7 +260,7 @@ impl EventLoopRunner { }; } - fn process_event(&mut self, event: Event) { + fn process_event(&mut self, event: Event<'_, T>) { // If we're in the modal loop, we need to have some mechanism for finding when the event // queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities // for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have @@ -390,7 +399,7 @@ impl EventLoopRunner { } } - fn call_event_handler(&mut self, event: Event) { + fn call_event_handler(&mut self, event: Event<'_, T>) { if self.panic_error.is_none() { let EventLoopRunner { ref mut panic_error, diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 6705f334fe..e87a48c600 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -168,14 +168,6 @@ impl MonitorHandle { MonitorHandle(hmonitor) } - pub(crate) fn contains_point(&self, point: &POINT) -> bool { - let monitor_info = get_monitor_info(self.0).unwrap(); - point.x >= monitor_info.rcMonitor.left - && point.x <= monitor_info.rcMonitor.right - && point.y >= monitor_info.rcMonitor.top - && point.y <= monitor_info.rcMonitor.bottom - } - #[inline] pub fn name(&self) -> Option { let monitor_info = get_monitor_info(self.0).unwrap(); @@ -196,8 +188,8 @@ impl MonitorHandle { pub fn size(&self) -> PhysicalSize { let monitor_info = get_monitor_info(self.0).unwrap(); PhysicalSize { - width: (monitor_info.rcMonitor.right - monitor_info.rcMonitor.left) as f64, - height: (monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top) as f64, + width: (monitor_info.rcMonitor.right - monitor_info.rcMonitor.left) as u32, + height: (monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top) as u32, } } diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 96347849cf..996aaf8d1d 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -11,7 +11,7 @@ use winapi::{ ctypes::wchar_t, shared::{ minwindef::{BOOL, DWORD}, - windef::{HWND, POINT, RECT}, + windef::{HWND, RECT}, }, um::{ libloaderapi::{GetProcAddress, LoadLibraryA}, @@ -85,10 +85,6 @@ fn win_to_err BOOL>(f: F) -> Result<(), io::Error> { } } -pub fn get_cursor_pos() -> Option { - unsafe { status_map(|cursor_pos| winuser::GetCursorPos(cursor_pos)) } -} - pub fn get_window_rect(hwnd: HWND) -> Option { unsafe { status_map(|rect| winuser::GetWindowRect(hwnd, rect)) } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 012b840209..3faf02935b 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -14,7 +14,7 @@ use std::{ use winapi::{ ctypes::c_int, shared::{ - minwindef::{DWORD, HINSTANCE, LPARAM, UINT, WORD, WPARAM}, + minwindef::{DWORD, HINSTANCE, UINT}, windef::{HWND, POINT, RECT}, }, um::{ @@ -30,14 +30,16 @@ use winapi::{ }; use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalSize}, + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ dark_mode::try_dark_mode, dpi::{dpi_to_scale_factor, hwnd_dpi}, drop_handler::FileDropHandler, - event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID}, + event_loop::{ + self, EventLoopWindowTarget, DESTROY_MSG_ID, + }, icon::{self, IconType, WinIcon}, monitor, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, @@ -125,8 +127,8 @@ impl Window { #[inline] pub fn set_visible(&self, visible: bool) { - let window_state = Arc::clone(&self.window_state); let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set(WindowFlags::VISIBLE, visible) @@ -146,41 +148,34 @@ impl Window { } } - pub(crate) fn outer_position_physical(&self) -> (i32, i32) { + #[inline] + pub fn outer_position(&self) -> Result { util::get_window_rect(self.window.0) - .map(|rect| (rect.left as i32, rect.top as i32)) - .unwrap() + .map(|rect| Ok(PhysicalPosition::new(rect.left as f64, rect.top as f64))) + .expect("Unexpected GetWindowRect failure; please report this error to https://github.com/rust-windowing/winit") } #[inline] - pub fn outer_position(&self) -> Result { - let physical_position = self.outer_position_physical(); - let dpi_factor = self.hidpi_factor(); - Ok(LogicalPosition::from_physical( - physical_position, - dpi_factor, - )) - } - - pub(crate) fn inner_position_physical(&self) -> (i32, i32) { + pub fn inner_position(&self) -> Result { let mut position: POINT = unsafe { mem::zeroed() }; if unsafe { winuser::ClientToScreen(self.window.0, &mut position) } == 0 { panic!("Unexpected ClientToScreen failure: please report this error to https://github.com/rust-windowing/winit") } - (position.x, position.y) + Ok(PhysicalPosition::new(position.x as f64, position.y as f64)) } #[inline] - pub fn inner_position(&self) -> Result { - let physical_position = self.inner_position_physical(); - let dpi_factor = self.hidpi_factor(); - Ok(LogicalPosition::from_physical( - physical_position, - dpi_factor, - )) - } + pub fn set_outer_position(&self, position: Position) { + let (x, y): (i32, i32) = position.to_physical(self.hidpi_factor()).into(); + + let window_state = Arc::clone(&self.window_state); + let window = self.window.clone(); + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { + f.set(WindowFlags::MAXIMIZED, false) + }); + }); - pub(crate) fn set_position_physical(&self, x: i32, y: i32) { unsafe { winuser::SetWindowPos( self.window.0, @@ -199,43 +194,22 @@ impl Window { } #[inline] - pub fn set_outer_position(&self, logical_position: LogicalPosition) { - let dpi_factor = self.hidpi_factor(); - let (x, y) = logical_position.to_physical(dpi_factor).into(); - - let window_state = Arc::clone(&self.window_state); - let window = self.window.clone(); - self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), window.0, |f| { - f.set(WindowFlags::MAXIMIZED, false) - }); - }); - - self.set_position_physical(x, y); - } - - pub(crate) fn inner_size_physical(&self) -> (u32, u32) { + pub fn inner_size(&self) -> PhysicalSize { let mut rect: RECT = unsafe { mem::zeroed() }; if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 { panic!("Unexpected GetClientRect failure: please report this error to https://github.com/rust-windowing/winit") } - ( + PhysicalSize::new( (rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32, ) } #[inline] - pub fn inner_size(&self) -> LogicalSize { - let physical_size = self.inner_size_physical(); - let dpi_factor = self.hidpi_factor(); - LogicalSize::from_physical(physical_size, dpi_factor) - } - - pub(crate) fn outer_size_physical(&self) -> (u32, u32) { + pub fn outer_size(&self) -> PhysicalSize { util::get_window_rect(self.window.0) .map(|rect| { - ( + PhysicalSize::new( (rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32, ) @@ -243,13 +217,6 @@ impl Window { .unwrap() } - #[inline] - pub fn outer_size(&self) -> LogicalSize { - let physical_size = self.outer_size_physical(); - let dpi_factor = self.hidpi_factor(); - LogicalSize::from_physical(physical_size, dpi_factor) - } - pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) { unsafe { let rect = util::adjust_window_rect( @@ -283,9 +250,9 @@ impl Window { } #[inline] - pub fn set_inner_size(&self, logical_size: LogicalSize) { + pub fn set_inner_size(&self, size: Size) { let dpi_factor = self.hidpi_factor(); - let (width, height) = logical_size.to_physical(dpi_factor).into(); + let (width, height) = size.to_physical(dpi_factor).into(); let window_state = Arc::clone(&self.window_state); let window = self.window.clone(); @@ -298,36 +265,20 @@ impl Window { self.set_inner_size_physical(width, height); } - pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { - self.window_state.lock().min_size = dimensions.map(Into::into); - // Make windows re-check the window size bounds. - let (width, height) = self.inner_size_physical(); - self.set_inner_size_physical(width, height); - } - #[inline] - pub fn set_min_inner_size(&self, logical_size: Option) { - let physical_size = logical_size.map(|logical_size| { - let dpi_factor = self.hidpi_factor(); - logical_size.to_physical(dpi_factor).into() - }); - self.set_min_inner_size_physical(physical_size); - } - - pub fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { - self.window_state.lock().max_size = dimensions.map(Into::into); + pub fn set_min_inner_size(&self, size: Option) { + self.window_state.lock().min_size = size; // Make windows re-check the window size bounds. - let (width, height) = self.inner_size_physical(); - self.set_inner_size_physical(width, height); + let size = self.inner_size(); + self.set_inner_size(size.into()); } #[inline] - pub fn set_max_inner_size(&self, logical_size: Option) { - let physical_size = logical_size.map(|logical_size| { - let dpi_factor = self.hidpi_factor(); - logical_size.to_physical(dpi_factor).into() - }); - self.set_max_inner_size_physical(physical_size); + pub fn set_max_inner_size(&self, size: Option) { + self.window_state.lock().max_size = size; + // Make windows re-check the window size bounds. + let size = self.inner_size(); + self.set_inner_size(size.into()); } #[inline] @@ -411,7 +362,11 @@ impl Window { self.window_state.lock().dpi_factor } - fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> { + #[inline] + pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { + let dpi_factor = self.hidpi_factor(); + let (x, y) = position.to_physical(dpi_factor).into(); + let mut point = POINT { x, y }; unsafe { if winuser::ClientToScreen(self.window.0, &mut point) == 0 { @@ -424,16 +379,6 @@ impl Window { Ok(()) } - #[inline] - pub fn set_cursor_position( - &self, - logical_position: LogicalPosition, - ) -> Result<(), ExternalError> { - let dpi_factor = self.hidpi_factor(); - let (x, y) = logical_position.to_physical(dpi_factor).into(); - self.set_cursor_position_physical(x, y) - } - #[inline] pub fn id(&self) -> WindowId { WindowId(self.window.0) @@ -691,7 +636,7 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, _logical_spot: LogicalPosition) { + pub fn set_ime_position(&self, _position: Position) { unimplemented!(); } @@ -770,41 +715,6 @@ unsafe fn init( // registering the window class let class_name = register_window_class(&window_icon, &taskbar_icon); - let guessed_dpi_factor = { - let monitors = monitor::available_monitors(); - let dpi_factor = if !monitors.is_empty() { - let mut dpi_factor = Some(monitors[0].hidpi_factor()); - for monitor in &monitors { - if Some(monitor.hidpi_factor()) != dpi_factor { - dpi_factor = None; - } - } - dpi_factor - } else { - return Err(os_error!(io::Error::new( - io::ErrorKind::NotFound, - "No monitors were detected." - ))); - }; - dpi_factor.unwrap_or_else(|| { - util::get_cursor_pos() - .and_then(|cursor_pos| { - let mut dpi_factor = None; - for monitor in &monitors { - if monitor.contains_point(&cursor_pos) { - dpi_factor = Some(monitor.hidpi_factor()); - break; - } - } - dpi_factor - }) - .unwrap_or(1.0) - }) - }; - info!("Guessed window DPI factor: {}", guessed_dpi_factor); - - let dimensions = attributes.inner_size.unwrap_or_else(|| (1024, 768).into()); - let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::DECORATIONS, attributes.decorations); window_flags.set(WindowFlags::ALWAYS_ON_TOP, attributes.always_on_top); @@ -853,20 +763,6 @@ unsafe fn init( let dpi = hwnd_dpi(real_window.0); let dpi_factor = dpi_to_scale_factor(dpi); - if dpi_factor != guessed_dpi_factor { - let (width, height): (u32, u32) = dimensions.into(); - let mut packed_dimensions = 0; - // MAKELPARAM isn't provided by winapi yet. - let ptr = &mut packed_dimensions as *mut LPARAM as *mut WORD; - *ptr.offset(0) = width as WORD; - *ptr.offset(1) = height as WORD; - winuser::PostMessageW( - real_window.0, - *INITIAL_DPI_MSG_ID, - dpi as WPARAM, - packed_dimensions, - ); - } // making the window transparent if attributes.transparent && !pl_attribs.no_redirection_bitmap { @@ -900,7 +796,6 @@ unsafe fn init( } } - window_flags.set(WindowFlags::VISIBLE, attributes.visible); window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized); // If the system theme is dark, we need to set the window theme now @@ -927,15 +822,17 @@ unsafe fn init( thread_executor: event_loop.create_thread_executor(), }; + let dimensions = attributes + .inner_size + .unwrap_or_else(|| PhysicalSize::new(1024, 768).into()); + win.set_inner_size(dimensions); + win.set_visible(attributes.visible); + if let Some(_) = attributes.fullscreen { win.set_fullscreen(attributes.fullscreen); force_window_active(win.window.0); } - if let Some(dimensions) = attributes.inner_size { - win.set_inner_size(dimensions); - } - Ok(win) } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 97874e5935..42cfeb7d56 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,5 +1,5 @@ use crate::{ - dpi::LogicalSize, + dpi::Size, platform_impl::platform::{event_loop, icon::WinIcon, util}, window::{CursorIcon, Fullscreen, WindowAttributes}, }; @@ -19,8 +19,8 @@ pub struct WindowState { pub mouse: MouseProperties, /// Used by `WM_GETMINMAXINFO`. - pub min_size: Option, - pub max_size: Option, + pub min_size: Option, + pub max_size: Option, pub window_icon: Option, pub taskbar_icon: Option, diff --git a/src/window.rs b/src/window.rs index 4388524077..b0ab30a367 100644 --- a/src/window.rs +++ b/src/window.rs @@ -2,7 +2,7 @@ use std::fmt; use crate::{ - dpi::{LogicalPosition, LogicalSize}, + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError}, event_loop::EventLoopWindowTarget, monitor::{MonitorHandle, VideoMode}, @@ -102,17 +102,17 @@ pub struct WindowAttributes { /// used. /// /// The default is `None`. - pub inner_size: Option, + pub inner_size: Option, /// The minimum dimensions a window can be, If this is `None`, the window will have no minimum dimensions (aside from reserved). /// /// The default is `None`. - pub min_inner_size: Option, + pub min_inner_size: Option, /// The maximum dimensions a window can be, If this is `None`, the maximum will have no maximum or will be set to the primary monitor's dimensions by the platform. /// /// The default is `None`. - pub max_inner_size: Option, + pub max_inner_size: Option, /// Whether the window is resizable or not. /// @@ -197,8 +197,8 @@ impl WindowBuilder { /// /// [`Window::set_inner_size`]: crate::window::Window::set_inner_size #[inline] - pub fn with_inner_size(mut self, size: LogicalSize) -> Self { - self.window.inner_size = Some(size); + pub fn with_inner_size>(mut self, size: S) -> Self { + self.window.inner_size = Some(size.into()); self } @@ -208,8 +208,8 @@ impl WindowBuilder { /// /// [`Window::set_min_inner_size`]: crate::window::Window::set_min_inner_size #[inline] - pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> Self { - self.window.min_inner_size = Some(min_size); + pub fn with_min_inner_size>(mut self, min_size: S) -> Self { + self.window.min_inner_size = Some(min_size.into()); self } @@ -219,8 +219,8 @@ impl WindowBuilder { /// /// [`Window::set_max_inner_size`]: crate::window::Window::set_max_inner_size #[inline] - pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> Self { - self.window.max_inner_size = Some(max_size); + pub fn with_max_inner_size>(mut self, max_size: S) -> Self { + self.window.max_inner_size = Some(max_size.into()); self } @@ -422,7 +422,7 @@ impl Window { /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result { self.window.inner_position() } @@ -441,7 +441,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window in the screen space coordinate system. #[inline] - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result { self.window.outer_position() } @@ -455,24 +455,22 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the /// window in the screen space coordinate system. #[inline] - pub fn set_outer_position(&self, position: LogicalPosition) { - self.window.set_outer_position(position) + pub fn set_outer_position>(&self, position: P) { + self.window.set_outer_position(position.into()) } /// Returns the logical size of the window's client area. /// /// The client area is the content of the window, excluding the title bar and borders. /// - /// Converting the returned `LogicalSize` to `PhysicalSize` produces the size your framebuffer should be. - /// /// ## Platform-specific /// - /// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window's + /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window's /// [safe area] in screen space coordinates. /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] - pub fn inner_size(&self) -> LogicalSize { + pub fn inner_size(&self) -> PhysicalSize { self.window.inner_size() } @@ -486,8 +484,8 @@ impl Window { /// - **iOS:** Unimplemented. Currently this panics, as it's not clear what `set_inner_size` /// would mean for iOS. #[inline] - pub fn set_inner_size(&self, size: LogicalSize) { - self.window.set_inner_size(size) + pub fn set_inner_size>(&self, size: S) { + self.window.set_inner_size(size.into()) } /// Returns the logical size of the entire window. @@ -497,10 +495,10 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Can only be called on the main thread. Returns the `LogicalSize` of the window in + /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window in /// screen space coordinates. #[inline] - pub fn outer_size(&self) -> LogicalSize { + pub fn outer_size(&self) -> PhysicalSize { self.window.outer_size() } @@ -511,8 +509,8 @@ impl Window { /// - **iOS:** Has no effect. /// - **Web:** Has no effect. #[inline] - pub fn set_min_inner_size(&self, dimensions: Option) { - self.window.set_min_inner_size(dimensions) + pub fn set_min_inner_size>(&self, min_size: Option) { + self.window.set_min_inner_size(min_size.map(|s| s.into())) } /// Sets a maximum dimension size for the window. @@ -522,8 +520,8 @@ impl Window { /// - **iOS:** Has no effect. /// - **Web:** Has no effect. #[inline] - pub fn set_max_inner_size(&self, dimensions: Option) { - self.window.set_max_inner_size(dimensions) + pub fn set_max_inner_size>(&self, max_size: Option) { + self.window.set_max_inner_size(max_size.map(|s| s.into())) } } @@ -675,8 +673,8 @@ impl Window { /// **iOS:** Has no effect. /// - **Web:** Has no effect. #[inline] - pub fn set_ime_position(&self, position: LogicalPosition) { - self.window.set_ime_position(position) + pub fn set_ime_position>(&self, position: P) { + self.window.set_ime_position(position.into()) } } @@ -700,8 +698,8 @@ impl Window { /// - **iOS:** Always returns an `Err`. /// - **Web:** Has no effect. #[inline] - pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { - self.window.set_cursor_position(position) + pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { + self.window.set_cursor_position(position.into()) } /// Grabs the cursor, preventing it from leaving the window. From 6ffd78767fc96162369374d6c5eacf2abd1bc9e9 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 4 Jan 2020 01:29:40 -0500 Subject: [PATCH 047/239] On Windows, make AdjustRect calls DPI-aware when possible (#1015) * Use AdjustWidowRectExForDPI when available * Prioritize presevering logical size when handling WM_DPICHANGED * Format * Add changelog entry --- CHANGELOG.md | 3 + examples/multithreaded.rs | 2 +- src/event.rs | 10 +- src/platform_impl/windows/dpi.rs | 45 ++------- src/platform_impl/windows/event_loop.rs | 40 +++++--- src/platform_impl/windows/util.rs | 128 +++++++++++++++++------- src/platform_impl/windows/window.rs | 22 +--- 7 files changed, 143 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55033c9c89..43484cd6d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,6 +144,9 @@ and `WindowEvent::HoveredFile`. - On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`. # 0.20.0 Alpha 1 (2019-06-21) +- On Windows, fix window rectangle not getting set correctly on high-DPI systems. + +# 0.20.0 Alpha 1 - Changes below are considered **breaking**. - Change all occurrences of `EventsLoop` to `EventLoop`. diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 8ac3bbaad8..955cdcc97c 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -5,7 +5,7 @@ fn main() { use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use winit::{ - dpi::{PhysicalPosition, PhysicalSize}, + dpi::{PhysicalPosition, PhysicalSize}, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::{CursorIcon, Fullscreen, WindowBuilder}, diff --git a/src/event.rs b/src/event.rs index 85899d3e03..a139f41533 100644 --- a/src/event.rs +++ b/src/event.rs @@ -333,7 +333,15 @@ impl<'a> WindowEvent<'a> { HoveredFileCancelled => Some(HoveredFileCancelled), ReceivedCharacter(c) => Some(ReceivedCharacter(c)), Focused(focused) => Some(Focused(focused)), - KeyboardInput { device_id, input, is_synthetic } => Some(KeyboardInput { device_id, input, is_synthetic }), + KeyboardInput { + device_id, + input, + is_synthetic, + } => Some(KeyboardInput { + device_id, + input, + is_synthetic, + }), CursorMoved { device_id, position, diff --git a/src/platform_impl/windows/dpi.rs b/src/platform_impl/windows/dpi.rs index d76c434461..f7ec439135 100644 --- a/src/platform_impl/windows/dpi.rs +++ b/src/platform_impl/windows/dpi.rs @@ -2,54 +2,30 @@ use std::sync::Once; +use crate::platform_impl::platform::util::{ + ENABLE_NON_CLIENT_DPI_SCALING, GET_DPI_FOR_MONITOR, GET_DPI_FOR_WINDOW, SET_PROCESS_DPI_AWARE, + SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT, +}; use winapi::{ shared::{ - minwindef::{BOOL, FALSE, UINT}, + minwindef::FALSE, windef::{DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, HMONITOR, HWND}, winerror::S_OK, }, um::{ - shellscalingapi::{ - MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS, - PROCESS_PER_MONITOR_DPI_AWARE, - }, + shellscalingapi::{MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE}, wingdi::{GetDeviceCaps, LOGPIXELSX}, - winnt::HRESULT, winuser::{self, MONITOR_DEFAULTTONEAREST}, }, }; const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4isize as _; -type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL; -type SetProcessDpiAwareness = unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT; -type SetProcessDpiAwarenessContext = - unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL; -type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> UINT; -type GetDpiForMonitor = unsafe extern "system" fn( - hmonitor: HMONITOR, - dpi_type: MONITOR_DPI_TYPE, - dpi_x: *mut UINT, - dpi_y: *mut UINT, -) -> HRESULT; -type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL; - -lazy_static! { - static ref GET_DPI_FOR_WINDOW: Option = - get_function!("user32.dll", GetDpiForWindow); - static ref GET_DPI_FOR_MONITOR: Option = - get_function!("shcore.dll", GetDpiForMonitor); - static ref ENABLE_NON_CLIENT_DPI_SCALING: Option = - get_function!("user32.dll", EnableNonClientDpiScaling); -} - pub fn become_dpi_aware() { static ENABLE_DPI_AWARENESS: Once = Once::new(); ENABLE_DPI_AWARENESS.call_once(|| { unsafe { - if let Some(SetProcessDpiAwarenessContext) = - get_function!("user32.dll", SetProcessDpiAwarenessContext) - { + if let Some(SetProcessDpiAwarenessContext) = *SET_PROCESS_DPI_AWARENESS_CONTEXT { // We are on Windows 10 Anniversary Update (1607) or later. if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) == FALSE @@ -58,13 +34,10 @@ pub fn become_dpi_aware() { // V1 if we can't set V2. SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); } - } else if let Some(SetProcessDpiAwareness) = - get_function!("shcore.dll", SetProcessDpiAwareness) - { + } else if let Some(SetProcessDpiAwareness) = *SET_PROCESS_DPI_AWARENESS { // We are on Windows 8.1 or later. SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); - } else if let Some(SetProcessDPIAware) = get_function!("user32.dll", SetProcessDPIAware) - { + } else if let Some(SetProcessDPIAware) = *SET_PROCESS_DPI_AWARE { // We are on Vista or later. SetProcessDPIAware(); } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 387b0fd40f..32876a2cb4 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -36,7 +36,7 @@ use winapi::{ }, um::{ commctrl, libloaderapi, ole2, processthreadsapi, winbase, - winnt::{HANDLE, LPCSTR, SHORT}, + winnt::{HANDLE, LPCSTR, SHORT}, winuser, }, }; @@ -48,15 +48,12 @@ use crate::{ event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, platform_impl::platform::{ dark_mode::try_dark_mode, - dpi::{ - become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling, - }, + dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, drop_handler::FileDropHandler, event::{ self, handle_extended_keys, process_key_params, vkey_to_winit_vkey, ModifiersStateSide, }, monitor, raw_input, util, - window::adjust_size, window_state::{CursorFlags, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, }, @@ -1394,11 +1391,9 @@ unsafe extern "system" fn public_window_callback( let window_state = subclass_input.window_state.lock(); if window_state.min_size.is_some() || window_state.max_size.is_some() { - let style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD; - let ex_style = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD; if let Some(min_size) = window_state.min_size { let min_size = min_size.to_physical(window_state.dpi_factor); - let (width, height) = adjust_size(min_size, style, ex_style); + let (width, height): (u32, u32) = util::adjust_size(window, min_size).into(); (*mmi).ptMinTrackSize = POINT { x: width as i32, y: height as i32, @@ -1406,7 +1401,7 @@ unsafe extern "system" fn public_window_callback( } if let Some(max_size) = window_state.max_size { let max_size = max_size.to_physical(window_state.dpi_factor); - let (width, height) = adjust_size(max_size, style, ex_style); + let (width, height): (u32, u32) = util::adjust_size(window, max_size).into(); (*mmi).ptMaxTrackSize = POINT { x: width as i32, y: height as i32, @@ -1428,10 +1423,11 @@ unsafe extern "system" fn public_window_callback( // https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx let new_dpi_x = u32::from(LOWORD(wparam as DWORD)); let new_dpi_factor = dpi_to_scale_factor(new_dpi_x); + let old_dpi_factor: f64; let allow_resize = { let mut window_state = subclass_input.window_state.lock(); - let old_dpi_factor = window_state.dpi_factor; + old_dpi_factor = window_state.dpi_factor; window_state.dpi_factor = new_dpi_factor; new_dpi_factor != old_dpi_factor && window_state.fullscreen.is_none() @@ -1468,10 +1464,26 @@ unsafe extern "system" fn public_window_callback( margins_vertical = (margin_bottom + margin_top) as u32; } - let physical_inner_rect = PhysicalSize::new( - (rect.right - rect.left) as u32 - margins_horizontal, - (rect.bottom - rect.top) as u32 - margins_vertical, - ); + // Use the rect suggested by Windows + // let physical_inner_rect = PhysicalSize::new( + // (rect.right - rect.left) as u32 - margins_horizontal, + // (rect.bottom - rect.top) as u32 - margins_vertical, + // ); + + // We calculate our own rect because the default suggested rect doesn't do a great job + // of preserving the window's logical size. + let physical_inner_rect = { + let mut current_rect = mem::zeroed(); + winuser::GetClientRect(window, &mut current_rect); + + let client_rect = PhysicalSize::new( + (current_rect.right - current_rect.left) as u32, + (current_rect.bottom - current_rect.top) as u32, + ); + client_rect + .to_logical(old_dpi_factor) + .to_physical(new_dpi_factor) + }; // `allow_resize` prevents us from re-applying DPI adjustment to the restored size after // exiting fullscreen (the restored size is already DPI adjusted). diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 996aaf8d1d..36fc225368 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -6,51 +6,22 @@ use std::{ sync::atomic::{AtomicBool, Ordering}, }; -use crate::window::CursorIcon; +use crate::{dpi::PhysicalSize, window::CursorIcon}; use winapi::{ ctypes::wchar_t, shared::{ - minwindef::{BOOL, DWORD}, - windef::{HWND, RECT}, + minwindef::{BOOL, DWORD, UINT}, + windef::{DPI_AWARENESS_CONTEXT, HMONITOR, HWND, LPRECT, RECT}, }, um::{ libloaderapi::{GetProcAddress, LoadLibraryA}, + shellscalingapi::{MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS}, winbase::lstrlenW, - winnt::LPCSTR, + winnt::{HRESULT, LONG, LPCSTR}, winuser, }, }; -// Helper function to dynamically load function pointer. -// `library` and `function` must be zero-terminated. -pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> { - assert_eq!(library.chars().last(), Some('\0')); - assert_eq!(function.chars().last(), Some('\0')); - - // Library names we will use are ASCII so we can use the A version to avoid string conversion. - let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) }; - if module.is_null() { - return None; - } - - let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) }; - if function_ptr.is_null() { - return None; - } - - Some(function_ptr as _) -} - -macro_rules! get_function { - ($lib:expr, $func:ident) => { - crate::platform_impl::platform::util::get_function_impl( - concat!($lib, '\0'), - concat!(stringify!($func), '\0'), - ) - .map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) }) - }; -} - pub fn has_flag(bitset: T, flag: T) -> bool where T: Copy + PartialEq + BitAnd, @@ -105,6 +76,18 @@ pub fn get_client_rect(hwnd: HWND) -> Result { } } +pub fn adjust_size(hwnd: HWND, size: PhysicalSize) -> PhysicalSize { + let (width, height): (u32, u32) = size.into(); + let rect = RECT { + left: 0, + right: width as LONG, + top: 0, + bottom: height as LONG, + }; + let rect = adjust_window_rect(hwnd, rect).unwrap_or(rect); + PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _) +} + pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option { unsafe { let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE); @@ -124,7 +107,14 @@ pub fn adjust_window_rect_with_styles( *r = rect; let b_menu = !winuser::GetMenu(hwnd).is_null() as BOOL; - winuser::AdjustWindowRectEx(r, style as _, b_menu, style_ex as _) + if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) = + (*GET_DPI_FOR_WINDOW, *ADJUST_WINDOW_RECT_EX_FOR_DPI) + { + let dpi = get_dpi_for_window(hwnd); + adjust_window_rect_ex_for_dpi(r, style as _, b_menu, style_ex as _, dpi) + } else { + winuser::AdjustWindowRectEx(r, style as _, b_menu, style_ex as _) + } }) } } @@ -206,3 +196,71 @@ impl CursorIcon { } } } + +// Helper function to dynamically load function pointer. +// `library` and `function` must be zero-terminated. +pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> { + assert_eq!(library.chars().last(), Some('\0')); + assert_eq!(function.chars().last(), Some('\0')); + + // Library names we will use are ASCII so we can use the A version to avoid string conversion. + let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) }; + if module.is_null() { + return None; + } + + let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) }; + if function_ptr.is_null() { + return None; + } + + Some(function_ptr as _) +} + +macro_rules! get_function { + ($lib:expr, $func:ident) => { + crate::platform_impl::platform::util::get_function_impl( + concat!($lib, '\0'), + concat!(stringify!($func), '\0'), + ) + .map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) }) + }; +} + +pub type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL; +pub type SetProcessDpiAwareness = + unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT; +pub type SetProcessDpiAwarenessContext = + unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL; +pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> UINT; +pub type GetDpiForMonitor = unsafe extern "system" fn( + hmonitor: HMONITOR, + dpi_type: MONITOR_DPI_TYPE, + dpi_x: *mut UINT, + dpi_y: *mut UINT, +) -> HRESULT; +pub type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL; +pub type AdjustWindowRectExForDpi = unsafe extern "system" fn( + rect: LPRECT, + dwStyle: DWORD, + bMenu: BOOL, + dwExStyle: DWORD, + dpi: UINT, +) -> BOOL; + +lazy_static! { + pub static ref GET_DPI_FOR_WINDOW: Option = + get_function!("user32.dll", GetDpiForWindow); + pub static ref ADJUST_WINDOW_RECT_EX_FOR_DPI: Option = + get_function!("user32.dll", AdjustWindowRectExForDpi); + pub static ref GET_DPI_FOR_MONITOR: Option = + get_function!("shcore.dll", GetDpiForMonitor); + pub static ref ENABLE_NON_CLIENT_DPI_SCALING: Option = + get_function!("user32.dll", EnableNonClientDpiScaling); + pub static ref SET_PROCESS_DPI_AWARENESS_CONTEXT: Option = + get_function!("user32.dll", SetProcessDpiAwarenessContext); + pub static ref SET_PROCESS_DPI_AWARENESS: Option = + get_function!("shcore.dll", SetProcessDpiAwareness); + pub static ref SET_PROCESS_DPI_AWARE: Option = + get_function!("user32.dll", SetProcessDPIAware); +} diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 3faf02935b..481ccdccb8 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -14,7 +14,7 @@ use std::{ use winapi::{ ctypes::c_int, shared::{ - minwindef::{DWORD, HINSTANCE, UINT}, + minwindef::{HINSTANCE, UINT}, windef::{HWND, POINT, RECT}, }, um::{ @@ -37,9 +37,7 @@ use crate::{ dark_mode::try_dark_mode, dpi::{dpi_to_scale_factor, hwnd_dpi}, drop_handler::FileDropHandler, - event_loop::{ - self, EventLoopWindowTarget, DESTROY_MSG_ID, - }, + event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, icon::{self, IconType, WinIcon}, monitor, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, @@ -669,22 +667,6 @@ pub struct WindowWrapper(HWND); unsafe impl Sync for WindowWrapper {} unsafe impl Send for WindowWrapper {} -pub unsafe fn adjust_size( - physical_size: PhysicalSize, - style: DWORD, - ex_style: DWORD, -) -> (LONG, LONG) { - let (width, height): (u32, u32) = physical_size.into(); - let mut rect = RECT { - left: 0, - right: width as LONG, - top: 0, - bottom: height as LONG, - }; - winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style); - (rect.right - rect.left, rect.bottom - rect.top) -} - unsafe fn init( mut attributes: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, From 6bb7db7c11465687450709d26fb9bc9e5800975a Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 4 Jan 2020 01:28:34 -0500 Subject: [PATCH 048/239] On Windows, fix new DPI API not setting window size properly (#1130) * First attempt * Second attempt * Maintain cursor horizontal ratio * Fix DPI change handling when maximized * Revert window example * Make new DPI code more understandable * Format --- src/platform_impl/windows/event_loop.rs | 200 +++++++++++++++++++----- 1 file changed, 159 insertions(+), 41 deletions(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 32876a2cb4..0568aa8c94 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -36,7 +36,7 @@ use winapi::{ }, um::{ commctrl, libloaderapi, ole2, processthreadsapi, winbase, - winnt::{HANDLE, LPCSTR, SHORT}, + winnt::{HANDLE, LONG, LPCSTR, SHORT}, winuser, }, }; @@ -1430,7 +1430,12 @@ unsafe extern "system" fn public_window_callback( old_dpi_factor = window_state.dpi_factor; window_state.dpi_factor = new_dpi_factor; - new_dpi_factor != old_dpi_factor && window_state.fullscreen.is_none() + if new_dpi_factor == old_dpi_factor { + return 0; + } + + window_state.fullscreen.is_none() + && !window_state.window_flags().contains(WindowFlags::MAXIMIZED) }; let style = winuser::GetWindowLongW(window, winuser::GWL_STYLE) as _; @@ -1438,16 +1443,18 @@ unsafe extern "system" fn public_window_callback( let b_menu = !winuser::GetMenu(window).is_null() as BOOL; // New size as suggested by Windows. - let rect = *(lparam as *const RECT); + let suggested_rect = *(lparam as *const RECT); // The window rect provided is the window's outer size, not it's inner size. However, // win32 doesn't provide an `UnadjustWindowRectEx` function to get the client rect from // the outer rect, so we instead adjust the window rect to get the decoration margins // and remove them from the outer size. - let margins_horizontal: u32; - let margins_vertical: u32; + let margin_left: i32; + let margin_top: i32; + // let margin_right: i32; + // let margin_bottom: i32; { - let mut adjusted_rect = rect; + let mut adjusted_rect = suggested_rect; winuser::AdjustWindowRectExForDpi( &mut adjusted_rect, style, @@ -1455,60 +1462,171 @@ unsafe extern "system" fn public_window_callback( style_ex, new_dpi_x, ); - let margin_left = rect.left - adjusted_rect.left; - let margin_right = adjusted_rect.right - rect.right; - let margin_top = rect.top - adjusted_rect.top; - let margin_bottom = adjusted_rect.bottom - rect.bottom; - - margins_horizontal = (margin_left + margin_right) as u32; - margins_vertical = (margin_bottom + margin_top) as u32; + margin_left = suggested_rect.left - adjusted_rect.left; + margin_top = suggested_rect.top - adjusted_rect.top; + // margin_right = adjusted_rect.right - suggested_rect.right; + // margin_bottom = adjusted_rect.bottom - suggested_rect.bottom; } - // Use the rect suggested by Windows - // let physical_inner_rect = PhysicalSize::new( - // (rect.right - rect.left) as u32 - margins_horizontal, - // (rect.bottom - rect.top) as u32 - margins_vertical, - // ); + let old_physical_inner_rect = { + let mut old_physical_inner_rect = mem::zeroed(); + winuser::GetClientRect(window, &mut old_physical_inner_rect); + let mut origin = mem::zeroed(); + winuser::ClientToScreen(window, &mut origin); - // We calculate our own rect because the default suggested rect doesn't do a great job - // of preserving the window's logical size. - let physical_inner_rect = { - let mut current_rect = mem::zeroed(); - winuser::GetClientRect(window, &mut current_rect); + old_physical_inner_rect.left += origin.x; + old_physical_inner_rect.right += origin.x; + old_physical_inner_rect.top += origin.y; + old_physical_inner_rect.bottom += origin.y; - let client_rect = PhysicalSize::new( - (current_rect.right - current_rect.left) as u32, - (current_rect.bottom - current_rect.top) as u32, - ); - client_rect - .to_logical(old_dpi_factor) - .to_physical(new_dpi_factor) + old_physical_inner_rect }; + let old_physical_inner_size = PhysicalSize::new( + (old_physical_inner_rect.right - old_physical_inner_rect.left) as u32, + (old_physical_inner_rect.bottom - old_physical_inner_rect.top) as u32, + ); + + // We calculate our own size because the default suggested rect doesn't do a great job + // of preserving the window's logical size. + let suggested_physical_inner_size = old_physical_inner_size + .to_logical(old_dpi_factor) + .to_physical(new_dpi_factor); // `allow_resize` prevents us from re-applying DPI adjustment to the restored size after // exiting fullscreen (the restored size is already DPI adjusted). - let mut new_inner_rect_opt = Some(physical_inner_rect).filter(|_| allow_resize); + let mut new_inner_size_opt = + Some(suggested_physical_inner_size).filter(|_| allow_resize); let _ = subclass_input.send_event_unbuffered(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: HiDpiFactorChanged { hidpi_factor: new_dpi_factor, - new_inner_size: &mut new_inner_rect_opt, + new_inner_size: &mut new_inner_size_opt, }, }); - if let Some(new_inner_rect) = new_inner_rect_opt { - winuser::SetWindowPos( - window, - ptr::null_mut(), - rect.left, - rect.top, - (new_inner_rect.width + margins_horizontal) as _, - (new_inner_rect.height + margins_vertical) as _, - winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE, + let new_physical_inner_size = new_inner_size_opt.unwrap_or(old_physical_inner_size); + + // Unset maximized if we're changing the window's size. + if new_physical_inner_size != old_physical_inner_size { + WindowState::set_window_flags(subclass_input.window_state.lock(), window, |f| { + f.set(WindowFlags::MAXIMIZED, false) + }); + } + + let new_outer_rect: RECT; + { + let suggested_ul = ( + suggested_rect.left + margin_left, + suggested_rect.top + margin_top, + ); + + let mut conservative_rect = RECT { + left: suggested_ul.0, + top: suggested_ul.1, + right: suggested_ul.0 + new_physical_inner_size.width as LONG, + bottom: suggested_ul.1 + new_physical_inner_size.height as LONG, + }; + + winuser::AdjustWindowRectExForDpi( + &mut conservative_rect, + style, + b_menu, + style_ex, + new_dpi_x, ); + + // If we're not dragging the window, offset the window so that the cursor's + // relative horizontal position in the title bar is preserved. + let dragging_window = subclass_input.event_loop_runner.in_modal_loop(); + if dragging_window { + let bias = { + let cursor_pos = { + let mut pos = mem::zeroed(); + winuser::GetCursorPos(&mut pos); + pos + }; + let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left) + as f64 + / (suggested_rect.right - suggested_rect.left) as f64; + + (cursor_pos.x + - (suggested_cursor_horizontal_ratio + * (conservative_rect.right - conservative_rect.left) as f64) + as LONG) + - conservative_rect.left + }; + conservative_rect.left += bias; + conservative_rect.right += bias; + } + + // Check to see if the new window rect is on the monitor with the new DPI factor. + // If it isn't, offset the window so that it is. + let new_dpi_monitor = winuser::MonitorFromWindow(window, 0); + let conservative_rect_monitor = winuser::MonitorFromRect(&conservative_rect, 0); + new_outer_rect = if conservative_rect_monitor == new_dpi_monitor { + conservative_rect + } else { + let get_monitor_rect = |monitor| { + let mut monitor_info = winuser::MONITORINFO { + cbSize: mem::size_of::() as _, + ..mem::zeroed() + }; + winuser::GetMonitorInfoW(monitor, &mut monitor_info); + monitor_info.rcMonitor + }; + let wrong_monitor = conservative_rect_monitor; + let wrong_monitor_rect = get_monitor_rect(wrong_monitor); + let new_monitor_rect = get_monitor_rect(new_dpi_monitor); + + // The direction to nudge the window in to get the window onto the monitor with + // the new DPI factor. We calculate this by seeing which monitor edges are + // shared and nudging away from the wrong monitor based on those. + let delta_nudge_to_dpi_monitor = ( + if wrong_monitor_rect.left == new_monitor_rect.right { + -1 + } else if wrong_monitor_rect.right == new_monitor_rect.left { + 1 + } else { + 0 + }, + if wrong_monitor_rect.bottom == new_monitor_rect.top { + 1 + } else if wrong_monitor_rect.top == new_monitor_rect.bottom { + -1 + } else { + 0 + }, + ); + + let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left + + new_monitor_rect.bottom + - new_monitor_rect.top; + for _ in 0..abort_after_iterations { + conservative_rect.left += delta_nudge_to_dpi_monitor.0; + conservative_rect.right += delta_nudge_to_dpi_monitor.0; + conservative_rect.top += delta_nudge_to_dpi_monitor.1; + conservative_rect.bottom += delta_nudge_to_dpi_monitor.1; + + if winuser::MonitorFromRect(&conservative_rect, 0) == new_dpi_monitor { + break; + } + } + + conservative_rect + }; } + winuser::SetWindowPos( + window, + ptr::null_mut(), + new_outer_rect.left, + new_outer_rect.top, + new_outer_rect.right - new_outer_rect.left, + new_outer_rect.bottom - new_outer_rect.top, + winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE, + ); + 0 } From 7b43b0bc940e6b997f7ce13ff87c223d8f3dd06e Mon Sep 17 00:00:00 2001 From: Murarth Date: Sat, 4 Jan 2020 01:31:23 -0500 Subject: [PATCH 049/239] Implement DPI Usability Upgrades for X11 and Wayland (#1098) * Fix compile errors * Use `mio` for the X11 event loop * Removes `calloop` from the X11 event loop, as the method of draining a source using a closure provided to the `calloop::EventLoop` instance conflicts with the need to deliver events directly to the callback provided to `EventLoop::run`, in order to respond to the value provided by `WindowEvent::HiDpiFactorChanged`. * Implement interactive `HiDpiFactorChanged` event for X11 * Implement interactive `HiDpiFactorChanged` event for Wayland * Run cargo fmt * Fix Wayland not processing events from EventQueue * Backport #981 --- Cargo.toml | 3 +- src/platform_impl/linux/mod.rs | 40 +- src/platform_impl/linux/wayland/event_loop.rs | 363 +++++++++--------- src/platform_impl/linux/wayland/keyboard.rs | 147 +++---- src/platform_impl/linux/wayland/mod.rs | 5 +- src/platform_impl/linux/wayland/pointer.rs | 21 +- src/platform_impl/linux/wayland/touch.rs | 7 +- src/platform_impl/linux/wayland/window.rs | 69 ++-- .../linux/x11/event_processor.rs | 208 +++++----- src/platform_impl/linux/x11/mod.rs | 214 +++++------ src/platform_impl/linux/x11/window.rs | 109 +++--- src/window.rs | 4 +- 12 files changed, 563 insertions(+), 627 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1869b1c809..f46f61a9bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,8 @@ features = [ [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] } -calloop = "0.4.2" +mio = "0.6" +mio-extras = "2.0" smithay-client-toolkit = "0.6" x11-dl = "2.18.3" percent-encoding = "2.0" diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 616c02fe61..350e05f06e 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -7,11 +7,9 @@ use raw_window_handle::RawWindowHandle; use smithay_client_toolkit::reexports::client::ConnectError; pub use self::x11::XNotSupported; -use self::x11::{ - ffi::XVisualInfo, get_xtarget, util::WindowType as XWindowType, XConnection, XError, -}; +use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError}; use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, @@ -248,7 +246,7 @@ impl Window { } #[inline] - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result { match self { &Window::X(ref w) => w.outer_position(), &Window::Wayland(ref w) => w.outer_position(), @@ -256,7 +254,7 @@ impl Window { } #[inline] - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result { match self { &Window::X(ref m) => m.inner_position(), &Window::Wayland(ref m) => m.inner_position(), @@ -264,7 +262,7 @@ impl Window { } #[inline] - pub fn set_outer_position(&self, position: LogicalPosition) { + pub fn set_outer_position(&self, position: Position) { match self { &Window::X(ref w) => w.set_outer_position(position), &Window::Wayland(ref w) => w.set_outer_position(position), @@ -272,7 +270,7 @@ impl Window { } #[inline] - pub fn inner_size(&self) -> LogicalSize { + pub fn inner_size(&self) -> PhysicalSize { match self { &Window::X(ref w) => w.inner_size(), &Window::Wayland(ref w) => w.inner_size(), @@ -280,7 +278,7 @@ impl Window { } #[inline] - pub fn outer_size(&self) -> LogicalSize { + pub fn outer_size(&self) -> PhysicalSize { match self { &Window::X(ref w) => w.outer_size(), &Window::Wayland(ref w) => w.outer_size(), @@ -288,7 +286,7 @@ impl Window { } #[inline] - pub fn set_inner_size(&self, size: LogicalSize) { + pub fn set_inner_size(&self, size: Size) { match self { &Window::X(ref w) => w.set_inner_size(size), &Window::Wayland(ref w) => w.set_inner_size(size), @@ -296,7 +294,7 @@ impl Window { } #[inline] - pub fn set_min_inner_size(&self, dimensions: Option) { + pub fn set_min_inner_size(&self, dimensions: Option) { match self { &Window::X(ref w) => w.set_min_inner_size(dimensions), &Window::Wayland(ref w) => w.set_min_inner_size(dimensions), @@ -304,7 +302,7 @@ impl Window { } #[inline] - pub fn set_max_inner_size(&self, dimensions: Option) { + pub fn set_max_inner_size(&self, dimensions: Option) { match self { &Window::X(ref w) => w.set_max_inner_size(dimensions), &Window::Wayland(ref w) => w.set_max_inner_size(dimensions), @@ -352,7 +350,7 @@ impl Window { } #[inline] - pub fn set_cursor_position(&self, position: LogicalPosition) -> Result<(), ExternalError> { + pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { match self { &Window::X(ref w) => w.set_cursor_position(position), &Window::Wayland(ref w) => w.set_cursor_position(position), @@ -416,7 +414,7 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, position: LogicalPosition) { + pub fn set_ime_position(&self, position: Position) { match self { &Window::X(ref w) => w.set_ime_position(position), &Window::Wayland(_) => (), @@ -603,7 +601,7 @@ impl EventLoop { .into_iter() .map(MonitorHandle::Wayland) .collect(), - EventLoop::X(ref evlp) => get_xtarget(&evlp.target) + EventLoop::X(ref evlp) => evlp .x_connection() .available_monitors() .into_iter() @@ -616,9 +614,7 @@ impl EventLoop { pub fn primary_monitor(&self) -> MonitorHandle { match *self { EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()), - EventLoop::X(ref evlp) => { - MonitorHandle::X(get_xtarget(&evlp.target).x_connection().primary_monitor()) - } + EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()), } } @@ -631,7 +627,7 @@ impl EventLoop { pub fn run_return(&mut self, callback: F) where - F: FnMut(crate::event::Event, &RootELW, &mut ControlFlow), + F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { match *self { EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback), @@ -641,7 +637,7 @@ impl EventLoop { pub fn run(self, callback: F) -> ! where - F: 'static + FnMut(crate::event::Event, &RootELW, &mut ControlFlow), + F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { match self { EventLoop::Wayland(evlp) => evlp.run(callback), @@ -682,12 +678,12 @@ impl EventLoopWindowTarget { } fn sticky_exit_callback( - evt: Event, + evt: Event<'_, T>, target: &RootELW, control_flow: &mut ControlFlow, callback: &mut F, ) where - F: FnMut(Event, &RootELW, &mut ControlFlow), + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { // make ControlFlow::Exit sticky by providing a dummy // control flow reference if it is already Exit. diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 7ba2c59a67..c538e77f8b 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -2,11 +2,16 @@ use std::{ cell::RefCell, collections::VecDeque, fmt, + io::ErrorKind, rc::Rc, sync::{Arc, Mutex}, - time::Instant, + time::{Duration, Instant}, }; +use mio::{Events, Poll, PollOpt, Ready, Token}; + +use mio_extras::channel::{channel, Receiver, SendError, Sender}; + use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{ zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::ZwpPointerConstraintsV1, }; @@ -21,15 +26,17 @@ use smithay_client_toolkit::reexports::client::protocol::{ }; use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - event::ModifiersState, + dpi::{LogicalSize, PhysicalPosition, PhysicalSize}, + event::{ + DeviceEvent, DeviceId as RootDeviceId, Event, ModifiersState, StartCause, WindowEvent, + }, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::platform::{ - sticky_exit_callback, MonitorHandle as PlatformMonitorHandle, - VideoMode as PlatformVideoMode, + sticky_exit_callback, DeviceId as PlatformDeviceId, MonitorHandle as PlatformMonitorHandle, + VideoMode as PlatformVideoMode, WindowId as PlatformWindowId, }, - window::CursorIcon, + window::{CursorIcon, WindowId as RootWindowId}, }; use super::{window::WindowStore, DeviceId, WindowId}; @@ -43,43 +50,37 @@ use smithay_client_toolkit::{ Environment, }; -pub struct WindowEventsSink { - buffer: VecDeque>, +const KBD_TOKEN: Token = Token(0); +const USER_TOKEN: Token = Token(1); +const EVQ_TOKEN: Token = Token(2); + +#[derive(Clone)] +pub struct EventsSink { + sender: Sender>, } -impl WindowEventsSink { - pub fn new() -> WindowEventsSink { - WindowEventsSink { - buffer: VecDeque::new(), - } +impl EventsSink { + pub fn new(sender: Sender>) -> EventsSink { + EventsSink { sender } } - pub fn send_event(&mut self, evt: crate::event::Event) { - self.buffer.push_back(evt); + pub fn send_event(&self, event: Event<'static, ()>) { + self.sender.send(event).unwrap() } - pub fn send_window_event(&mut self, evt: crate::event::WindowEvent, wid: WindowId) { - self.buffer.push_back(crate::event::Event::WindowEvent { - event: evt, - window_id: crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid)), + pub fn send_device_event(&self, event: DeviceEvent, device_id: DeviceId) { + self.send_event(Event::DeviceEvent { + event, + device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)), }); } - pub fn send_device_event(&mut self, evt: crate::event::DeviceEvent, dev_id: DeviceId) { - self.buffer.push_back(crate::event::Event::DeviceEvent { - event: evt, - device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(dev_id)), + pub fn send_window_event(&self, event: WindowEvent<'static>, window_id: WindowId) { + self.send_event(Event::WindowEvent { + event, + window_id: RootWindowId(PlatformWindowId::Wayland(window_id)), }); } - - fn empty_with(&mut self, mut callback: F) - where - F: FnMut(crate::event::Event), - { - for evt in self.buffer.drain(..) { - callback(evt) - } - } } pub struct CursorManager { @@ -230,21 +231,17 @@ impl CursorManager { } pub struct EventLoop { - // The loop - inner_loop: ::calloop::EventLoop<()>, + // Poll instance + poll: Poll, // The wayland display pub display: Arc, // The output manager pub outputs: OutputMgr, - // Our sink, shared with some handlers, buffering the events - sink: Arc>>, - pending_user_events: Rc>>, // The cursor manager cursor_manager: Arc>, - // Utility for grabbing the cursor and changing visibility - _user_source: ::calloop::Source<::calloop::channel::Channel>, - user_sender: ::calloop::channel::Sender, - _kbd_source: ::calloop::Source<::calloop::channel::Channel>>, + kbd_channel: Receiver>, + user_channel: Receiver, + user_sender: Sender, window_target: RootELW, } @@ -252,12 +249,12 @@ pub struct EventLoop { // // We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs. pub struct EventLoopProxy { - user_sender: calloop::channel::Sender, + user_sender: Sender, } pub struct EventLoopWindowTarget { - // The event queue - pub evq: RefCell<::calloop::Source>, + // the event queue + pub evq: RefCell, // The window store pub store: Arc>, // The cursor manager @@ -284,7 +281,7 @@ impl Clone for EventLoopProxy { impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.user_sender.send(event).map_err(|e| { - EventLoopClosed(if let ::calloop::channel::SendError::Disconnected(x) = e { + EventLoopClosed(if let SendError::Disconnected(x) = e { x } else { unreachable!() @@ -298,33 +295,26 @@ impl EventLoop { let (display, mut event_queue) = Display::connect_to_env()?; let display = Arc::new(display); - let sink = Arc::new(Mutex::new(WindowEventsSink::new())); let store = Arc::new(Mutex::new(WindowStore::new())); let seats = Arc::new(Mutex::new(Vec::new())); - let inner_loop = ::calloop::EventLoop::new().unwrap(); + let poll = Poll::new().unwrap(); - let (kbd_sender, kbd_channel) = ::calloop::channel::channel::>(); - let kbd_sink = sink.clone(); - let kbd_source = inner_loop - .handle() - .insert_source(kbd_channel, move |evt, &mut ()| { - if let ::calloop::channel::Event::Msg(evt) = evt { - let evt = evt.map_nonuser_event().ok().unwrap(); - kbd_sink.lock().unwrap().send_event(evt); - } - }) + let (kbd_sender, kbd_channel) = channel(); + + let sink = EventsSink::new(kbd_sender); + + poll.register(&kbd_channel, KBD_TOKEN, Ready::readable(), PollOpt::level()) .unwrap(); let pointer_constraints_proxy = Arc::new(Mutex::new(None)); let mut seat_manager = SeatManager { - sink: sink.clone(), - relative_pointer_manager_proxy: Rc::new(RefCell::new(None)), - pointer_constraints_proxy: pointer_constraints_proxy.clone(), + sink, store: store.clone(), seats: seats.clone(), - kbd_sender, + relative_pointer_manager_proxy: Rc::new(RefCell::new(None)), + pointer_constraints_proxy: pointer_constraints_proxy.clone(), cursor_manager: Arc::new(Mutex::new(CursorManager::new(pointer_constraints_proxy))), }; @@ -404,39 +394,31 @@ impl EventLoop { ) .unwrap(); - let source = inner_loop - .handle() - .insert_source(event_queue, |(), &mut ()| {}) + poll.register(&event_queue, EVQ_TOKEN, Ready::readable(), PollOpt::level()) .unwrap(); - let pending_user_events = Rc::new(RefCell::new(VecDeque::new())); - let pending_user_events2 = pending_user_events.clone(); - - let (user_sender, user_channel) = ::calloop::channel::channel(); + let (user_sender, user_channel) = channel(); - let user_source = inner_loop - .handle() - .insert_source(user_channel, move |evt, &mut ()| { - if let ::calloop::channel::Event::Msg(msg) = evt { - pending_user_events2.borrow_mut().push_back(msg); - } - }) - .unwrap(); + poll.register( + &user_channel, + USER_TOKEN, + Ready::readable(), + PollOpt::level(), + ) + .unwrap(); let cursor_manager_clone = cursor_manager.clone(); Ok(EventLoop { - inner_loop, - sink, - pending_user_events, + poll, display: display.clone(), outputs: env.outputs.clone(), - _user_source: user_source, user_sender, + user_channel, + kbd_channel, cursor_manager, - _kbd_source: kbd_source, window_target: RootELW { p: crate::platform_impl::EventLoopWindowTarget::Wayland(EventLoopWindowTarget { - evq: RefCell::new(source), + evq: RefCell::new(event_queue), store, env, cursor_manager: cursor_manager_clone, @@ -458,7 +440,7 @@ impl EventLoop { pub fn run(mut self, callback: F) -> ! where - F: 'static + FnMut(crate::event::Event, &RootELW, &mut ControlFlow), + F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { self.run_return(callback); std::process::exit(0); @@ -466,53 +448,60 @@ impl EventLoop { pub fn run_return(&mut self, mut callback: F) where - F: FnMut(crate::event::Event, &RootELW, &mut ControlFlow), + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { // send pending events to the server self.display.flush().expect("Wayland connection lost."); let mut control_flow = ControlFlow::default(); - - let sink = self.sink.clone(); - let user_events = self.pending_user_events.clone(); + let mut events = Events::with_capacity(8); callback( - crate::event::Event::NewEvents(crate::event::StartCause::Init), + Event::NewEvents(StartCause::Init), &self.window_target, &mut control_flow, ); loop { - self.post_dispatch_triggers(); - - // empty buffer of events - { - let mut guard = sink.lock().unwrap(); - guard.empty_with(|evt| { - sticky_exit_callback( - evt, - &self.window_target, - &mut control_flow, - &mut callback, - ); - }); - } - // empty user events + // Read events from the event queue { - let mut guard = user_events.borrow_mut(); - for evt in guard.drain(..) { - sticky_exit_callback( - crate::event::Event::UserEvent(evt), - &self.window_target, - &mut control_flow, - &mut callback, - ); + let mut evq = get_target(&self.window_target).evq.borrow_mut(); + + evq.dispatch_pending() + .expect("failed to dispatch wayland events"); + + if let Some(read) = evq.prepare_read() { + if let Err(e) = read.read_events() { + if e.kind() != ErrorKind::WouldBlock { + panic!("failed to read wayland events: {}", e); + } + } + + evq.dispatch_pending() + .expect("failed to dispatch wayland events"); } } + + self.post_dispatch_triggers(&mut callback, &mut control_flow); + + while let Ok(event) = self.kbd_channel.try_recv() { + let event = event.map_nonuser_event().unwrap(); + sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + } + + while let Ok(event) = self.user_channel.try_recv() { + sticky_exit_callback( + Event::UserEvent(event), + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + // send Events cleared { sticky_exit_callback( - crate::event::Event::MainEventsCleared, + Event::MainEventsCleared, &self.window_target, &mut control_flow, &mut callback, @@ -523,7 +512,7 @@ impl EventLoop { { self.redraw_triggers(|wid, window_target| { sticky_exit_callback( - crate::event::Event::RedrawRequested(crate::window::WindowId( + Event::RedrawRequested(crate::window::WindowId( crate::platform_impl::WindowId::Wayland(wid), )), window_target, @@ -536,7 +525,7 @@ impl EventLoop { // send RedrawEventsCleared { sticky_exit_callback( - crate::event::Event::RedrawEventsCleared, + Event::RedrawEventsCleared, &self.window_target, &mut control_flow, &mut callback, @@ -569,24 +558,25 @@ impl EventLoop { ControlFlow::Exit => break, ControlFlow::Poll => { // non-blocking dispatch - self.inner_loop - .dispatch(Some(::std::time::Duration::from_millis(0)), &mut ()) + self.poll + .poll(&mut events, Some(Duration::from_millis(0))) .unwrap(); + events.clear(); + callback( - crate::event::Event::NewEvents(crate::event::StartCause::Poll), + Event::NewEvents(StartCause::Poll), &self.window_target, &mut control_flow, ); } ControlFlow::Wait => { - let timeout = if instant_wakeup { - Some(::std::time::Duration::from_millis(0)) - } else { - None - }; - self.inner_loop.dispatch(timeout, &mut ()).unwrap(); + if !instant_wakeup { + self.poll.poll(&mut events, None).unwrap(); + events.clear(); + } + callback( - crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled { + Event::NewEvents(StartCause::WaitCancelled { start: Instant::now(), requested_resume: None, }), @@ -600,29 +590,27 @@ impl EventLoop { let duration = if deadline > start && !instant_wakeup { deadline - start } else { - ::std::time::Duration::from_millis(0) + Duration::from_millis(0) }; - self.inner_loop.dispatch(Some(duration), &mut ()).unwrap(); + self.poll.poll(&mut events, Some(duration)).unwrap(); + events.clear(); + let now = Instant::now(); if now < deadline { callback( - crate::event::Event::NewEvents( - crate::event::StartCause::WaitCancelled { - start, - requested_resume: Some(deadline), - }, - ), + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: Some(deadline), + }), &self.window_target, &mut control_flow, ); } else { callback( - crate::event::Event::NewEvents( - crate::event::StartCause::ResumeTimeReached { - start, - requested_resume: deadline, - }, - ), + Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume: deadline, + }), &self.window_target, &mut control_flow, ); @@ -631,11 +619,7 @@ impl EventLoop { } } - callback( - crate::event::Event::LoopDestroyed, - &self.window_target, - &mut control_flow, - ); + callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); } pub fn primary_monitor(&self) -> MonitorHandle { @@ -687,12 +671,19 @@ impl EventLoop { ) } - fn post_dispatch_triggers(&mut self) { - let mut sink = self.sink.lock().unwrap(); + fn post_dispatch_triggers(&mut self, mut callback: F, control_flow: &mut ControlFlow) + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { let window_target = match self.window_target.p { crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, _ => unreachable!(), }; + + let mut callback = |event: Event<'_, T>| { + sticky_exit_callback(event, &self.window_target, control_flow, &mut callback); + }; + // prune possible dead windows { let mut cleanup_needed = window_target.cleanup_needed.lock().unwrap(); @@ -700,41 +691,67 @@ impl EventLoop { let pruned = window_target.store.lock().unwrap().cleanup(); *cleanup_needed = false; for wid in pruned { - sink.send_window_event(crate::event::WindowEvent::Destroyed, wid); + callback(Event::WindowEvent { + window_id: crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(wid), + ), + event: WindowEvent::Destroyed, + }); } } } // process pending resize/refresh window_target.store.lock().unwrap().for_each(|window| { + let window_id = + crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid)); if let Some(frame) = window.frame { - if let Some(newsize) = window.newsize { - let (w, h) = newsize; + if let Some((w, h)) = window.newsize { // mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case // it overlaps mutter's `bounding box`, so we can't avoid this resize call, // which calls `set_geometry` under the hood, for now. frame.resize(w, h); frame.refresh(); + // Don't send resize event downstream if the new size is identical to the // current one. - if newsize != *window.size { + if (w, h) != *window.size { let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64); - sink.send_window_event( - crate::event::WindowEvent::Resized(logical_size), - window.wid, - ); + let physical_size = logical_size + .to_physical(window.new_dpi.unwrap_or(window.prev_dpi) as f64); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Resized(physical_size), + }); + *window.size = (w, h); + } + } + + if let Some(dpi) = window.new_dpi { + let dpi = dpi as f64; + let logical_size = LogicalSize::from(*window.size); + let mut new_inner_size = Some(logical_size.to_physical(dpi)); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::HiDpiFactorChanged { + hidpi_factor: dpi, + new_inner_size: &mut new_inner_size, + }, + }); + + if let Some(new_size) = new_inner_size { + let (w, h) = new_size.to_logical(dpi).into(); + frame.resize(w, h); *window.size = (w, h); } } - } - if let Some(dpi) = window.new_dpi { - sink.send_window_event( - crate::event::WindowEvent::HiDpiFactorChanged(dpi as f64), - window.wid, - ); } if window.closed { - sink.send_window_event(crate::event::WindowEvent::CloseRequested, window.wid); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::CloseRequested, + }); } if let Some(grab_cursor) = window.grab_cursor { @@ -749,21 +766,27 @@ impl EventLoop { } } +fn get_target(target: &RootELW) -> &EventLoopWindowTarget { + match target.p { + crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + _ => unreachable!(), + } +} + /* * Wayland protocol implementations */ -struct SeatManager { - sink: Arc>>, +struct SeatManager { + sink: EventsSink, store: Arc>, seats: Arc>>, - kbd_sender: ::calloop::channel::Sender>, relative_pointer_manager_proxy: Rc>>, pointer_constraints_proxy: Arc>>, cursor_manager: Arc>, } -impl SeatManager { +impl SeatManager { fn add_seat(&mut self, id: u32, version: u32, registry: wl_registry::WlRegistry) { use std::cmp::min; @@ -775,7 +798,6 @@ impl SeatManager { relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.clone(), keyboard: None, touch: None, - kbd_sender: self.kbd_sender.clone(), modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())), cursor_manager: self.cursor_manager.clone(), }; @@ -799,10 +821,9 @@ impl SeatManager { } } -struct SeatData { - sink: Arc>>, +struct SeatData { + sink: EventsSink, store: Arc>, - kbd_sender: ::calloop::channel::Sender>, pointer: Option, relative_pointer: Option, relative_pointer_manager_proxy: Rc>>, @@ -812,7 +833,7 @@ struct SeatData { cursor_manager: Arc>, } -impl SeatData { +impl SeatData { fn receive(&mut self, evt: wl_seat::Event, seat: wl_seat::WlSeat) { match evt { wl_seat::Event::Name { .. } => (), @@ -858,7 +879,7 @@ impl SeatData { if capabilities.contains(wl_seat::Capability::Keyboard) && self.keyboard.is_none() { self.keyboard = Some(super::keyboard::init_keyboard( &seat, - self.kbd_sender.clone(), + self.sink.clone(), self.modifiers_tracker.clone(), )) } @@ -892,7 +913,7 @@ impl SeatData { } } -impl Drop for SeatData { +impl Drop for SeatData { fn drop(&mut self) { if let Some(pointer) = self.pointer.take() { if pointer.as_ref().version() >= 3 { diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index 7e0d2e3eec..ff61b9e377 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, Mutex}; -use super::{make_wid, DeviceId}; +use super::{event_loop::EventsSink, make_wid, DeviceId}; use smithay_client_toolkit::{ keyboard::{ self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind, @@ -9,12 +9,12 @@ use smithay_client_toolkit::{ }; use crate::event::{ - DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent, + DeviceEvent, ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent, }; pub fn init_keyboard( seat: &wl_seat::WlSeat, - sink: ::calloop::channel::Sender>, + sink: EventsSink, modifiers_tracker: Arc>, ) -> wl_keyboard::WlKeyboard { // { variables to be captured by the closures @@ -31,22 +31,12 @@ pub fn init_keyboard( match evt { KbEvent::Enter { surface, .. } => { let wid = make_wid(&surface); - my_sink - .send(Event::WindowEvent { - window_id: mk_root_wid(wid), - event: WindowEvent::Focused(true), - }) - .unwrap(); + my_sink.send_window_event(WindowEvent::Focused(true), wid); *target.lock().unwrap() = Some(wid); } KbEvent::Leave { surface, .. } => { let wid = make_wid(&surface); - my_sink - .send(Event::WindowEvent { - window_id: mk_root_wid(wid), - event: WindowEvent::Focused(false), - }) - .unwrap(); + my_sink.send_window_event(WindowEvent::Focused(false), wid); *target.lock().unwrap() = None; } KbEvent::Key { @@ -63,33 +53,28 @@ pub fn init_keyboard( _ => unreachable!(), }; let vkcode = key_to_vkey(rawkey, keysym); - my_sink - .send(Event::WindowEvent { - window_id: mk_root_wid(wid), - event: WindowEvent::KeyboardInput { - device_id: device_id(), - input: KeyboardInput { - state, - scancode: rawkey, - virtual_keycode: vkcode, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - is_synthetic: false, + my_sink.send_window_event( + WindowEvent::KeyboardInput { + device_id: crate::event::DeviceId( + crate::platform_impl::DeviceId::Wayland(DeviceId), + ), + input: KeyboardInput { + state, + scancode: rawkey, + virtual_keycode: vkcode, + modifiers: modifiers_tracker.lock().unwrap().clone(), }, - }) - .unwrap(); + is_synthetic: false, + }, + wid, + ); // send char event only on key press, not release if let ElementState::Released = state { return; } if let Some(txt) = utf8 { for chr in txt.chars() { - my_sink - .send(Event::WindowEvent { - window_id: mk_root_wid(wid), - event: WindowEvent::ReceivedCharacter(chr), - }) - .unwrap(); + my_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid); } } } @@ -102,12 +87,7 @@ pub fn init_keyboard( *modifiers_tracker.lock().unwrap() = modifiers; - my_sink - .send(Event::DeviceEvent { - device_id: device_id(), - event: DeviceEvent::ModifiersChanged(modifiers), - }) - .unwrap(); + my_sink.send_device_event(DeviceEvent::ModifiersChanged(modifiers), DeviceId); } } }, @@ -115,29 +95,24 @@ pub fn init_keyboard( if let Some(wid) = *repeat_target.lock().unwrap() { let state = ElementState::Pressed; let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym); - repeat_sink - .send(Event::WindowEvent { - window_id: mk_root_wid(wid), - event: WindowEvent::KeyboardInput { - device_id: device_id(), - input: KeyboardInput { - state, - scancode: repeat_event.rawkey, - virtual_keycode: vkcode, - modifiers: my_modifiers.lock().unwrap().clone(), - }, - is_synthetic: false, + repeat_sink.send_window_event( + WindowEvent::KeyboardInput { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + input: KeyboardInput { + state, + scancode: repeat_event.rawkey, + virtual_keycode: vkcode, + modifiers: my_modifiers.lock().unwrap().clone(), }, - }) - .unwrap(); + is_synthetic: false, + }, + wid, + ); if let Some(txt) = repeat_event.utf8 { for chr in txt.chars() { - repeat_sink - .send(Event::WindowEvent { - window_id: mk_root_wid(wid), - event: WindowEvent::ReceivedCharacter(chr), - }) - .unwrap(); + repeat_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid); } } } @@ -164,22 +139,12 @@ pub fn init_keyboard( move |evt, _| match evt { wl_keyboard::Event::Enter { surface, .. } => { let wid = make_wid(&surface); - my_sink - .send(Event::WindowEvent { - window_id: mk_root_wid(wid), - event: WindowEvent::Focused(true), - }) - .unwrap(); + my_sink.send_window_event(WindowEvent::Focused(true), wid); target = Some(wid); } wl_keyboard::Event::Leave { surface, .. } => { let wid = make_wid(&surface); - my_sink - .send(Event::WindowEvent { - window_id: mk_root_wid(wid), - event: WindowEvent::Focused(false), - }) - .unwrap(); + my_sink.send_window_event(WindowEvent::Focused(false), wid); target = None; } wl_keyboard::Event::Key { key, state, .. } => { @@ -189,21 +154,21 @@ pub fn init_keyboard( wl_keyboard::KeyState::Released => ElementState::Released, _ => unreachable!(), }; - my_sink - .send(Event::WindowEvent { - window_id: mk_root_wid(wid), - event: WindowEvent::KeyboardInput { - device_id: device_id(), - input: KeyboardInput { - state, - scancode: key, - virtual_keycode: None, - modifiers: ModifiersState::default(), - }, - is_synthetic: false, + my_sink.send_window_event( + WindowEvent::KeyboardInput { + device_id: crate::event::DeviceId( + crate::platform_impl::DeviceId::Wayland(DeviceId), + ), + input: KeyboardInput { + state, + scancode: key, + virtual_keycode: None, + modifiers: ModifiersState::default(), }, - }) - .unwrap(); + is_synthetic: false, + }, + wid, + ); } } _ => (), @@ -412,11 +377,3 @@ impl ModifiersState { m } } - -fn device_id() -> crate::event::DeviceId { - crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)) -} - -fn mk_root_wid(wid: crate::platform_impl::wayland::WindowId) -> crate::window::WindowId { - crate::window::WindowId(crate::platform_impl::WindowId::Wayland(wid)) -} diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index 09cd66d15a..7fdb3fd1ed 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -2,10 +2,7 @@ target_os = "netbsd", target_os = "openbsd"))] pub use self::{ - event_loop::{ - EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode, - WindowEventsSink, - }, + event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode}, window::Window, }; diff --git a/src/platform_impl/linux/wayland/pointer.rs b/src/platform_impl/linux/wayland/pointer.rs index d2785bfe98..f84264c384 100644 --- a/src/platform_impl/linux/wayland/pointer.rs +++ b/src/platform_impl/linux/wayland/pointer.rs @@ -6,7 +6,7 @@ use crate::event::{ }; use super::{ - event_loop::{CursorManager, WindowEventsSink}, + event_loop::{CursorManager, EventsSink}, window::WindowStore, DeviceId, }; @@ -28,9 +28,9 @@ use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints: use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface; -pub fn implement_pointer( +pub fn implement_pointer( seat: &wl_seat::WlSeat, - sink: Arc>>, + sink: EventsSink, store: Arc>, modifiers_tracker: Arc>, cursor_manager: Arc>, @@ -43,7 +43,6 @@ pub fn implement_pointer( pointer.implement_closure( move |evt, pointer| { - let mut sink = sink.lock().unwrap(); let store = store.lock().unwrap(); let mut cursor_manager = cursor_manager.lock().unwrap(); match evt { @@ -242,20 +241,18 @@ pub fn implement_pointer( .unwrap() } -pub fn implement_relative_pointer( - sink: Arc>>, +pub fn implement_relative_pointer( + sink: EventsSink, pointer: &WlPointer, manager: &ZwpRelativePointerManagerV1, ) -> Result { manager.get_relative_pointer(pointer, |rel_pointer| { rel_pointer.implement_closure( - move |evt, _rel_pointer| { - let mut sink = sink.lock().unwrap(); - match evt { - Event::RelativeMotion { dx, dy, .. } => sink - .send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId), - _ => unreachable!(), + move |evt, _rel_pointer| match evt { + Event::RelativeMotion { dx, dy, .. } => { + sink.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId) } + _ => unreachable!(), }, (), ) diff --git a/src/platform_impl/linux/wayland/touch.rs b/src/platform_impl/linux/wayland/touch.rs index 33644a863e..803ea62492 100644 --- a/src/platform_impl/linux/wayland/touch.rs +++ b/src/platform_impl/linux/wayland/touch.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex}; use crate::event::{TouchPhase, WindowEvent}; -use super::{event_loop::WindowEventsSink, window::WindowStore, DeviceId, WindowId}; +use super::{event_loop::EventsSink, window::WindowStore, DeviceId, WindowId}; use smithay_client_toolkit::reexports::client::protocol::{ wl_seat, @@ -15,16 +15,15 @@ struct TouchPoint { id: i32, } -pub(crate) fn implement_touch( +pub(crate) fn implement_touch( seat: &wl_seat::WlSeat, - sink: Arc>>, + sink: EventsSink, store: Arc>, ) -> WlTouch { let mut pending_ids = Vec::new(); seat.get_touch(|touch| { touch.implement_closure( move |evt, _| { - let mut sink = sink.lock().unwrap(); let store = store.lock().unwrap(); match evt { TouchEvent::Down { diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index f6ed396410..2c509bbfa7 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::{ - dpi::{LogicalPosition, LogicalSize}, + dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, monitor::MonitorHandle as RootMonitorHandle, platform_impl::{ @@ -49,11 +49,7 @@ impl Window { attributes: WindowAttributes, pl_attribs: PlAttributes, ) -> Result { - let (width, height) = attributes.inner_size.map(Into::into).unwrap_or((800, 600)); - // Create the window - let size = Arc::new(Mutex::new((width, height))); - let fullscreen = Arc::new(Mutex::new(false)); - + // Create the surface first to get initial DPI let window_store = evlp.store.clone(); let cursor_manager = evlp.cursor_manager.clone(); let surface = evlp.env.create_surface(move |dpi, surface| { @@ -61,7 +57,18 @@ impl Window { surface.set_buffer_scale(dpi); }); + let dpi = get_dpi_factor(&surface) as f64; + let (width, height) = attributes + .inner_size + .map(|size| size.to_logical(dpi).into()) + .unwrap_or((800, 600)); + + // Create the window + let size = Arc::new(Mutex::new((width, height))); + let fullscreen = Arc::new(Mutex::new(false)); + let window_store = evlp.store.clone(); + let my_surface = surface.clone(); let mut frame = SWindow::::init_from_env( &evlp.env, @@ -137,8 +144,16 @@ impl Window { frame.set_title(attributes.title); // min-max dimensions - frame.set_min_size(attributes.min_inner_size.map(Into::into)); - frame.set_max_size(attributes.max_inner_size.map(Into::into)); + frame.set_min_size( + attributes + .min_inner_size + .map(|size| size.to_logical(dpi).into()), + ); + frame.set_max_size( + attributes + .max_inner_size + .map(|size| size.to_logical(dpi).into()), + ); let kill_switch = Arc::new(Mutex::new(false)); let need_frame_refresh = Arc::new(Mutex::new(true)); @@ -191,22 +206,24 @@ impl Window { } #[inline] - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result { Err(NotSupportedError::new()) } #[inline] - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result { Err(NotSupportedError::new()) } #[inline] - pub fn set_outer_position(&self, _pos: LogicalPosition) { + pub fn set_outer_position(&self, _pos: Position) { // Not possible with wayland } - pub fn inner_size(&self) -> LogicalSize { - self.size.lock().unwrap().clone().into() + pub fn inner_size(&self) -> PhysicalSize { + let dpi = self.hidpi_factor() as f64; + let size = LogicalSize::from(*self.size.lock().unwrap()); + size.to_physical(dpi) } pub fn request_redraw(&self) { @@ -214,34 +231,39 @@ impl Window { } #[inline] - pub fn outer_size(&self) -> LogicalSize { + pub fn outer_size(&self) -> PhysicalSize { + let dpi = self.hidpi_factor() as f64; let (w, h) = self.size.lock().unwrap().clone(); // let (w, h) = super::wayland_window::add_borders(w as i32, h as i32); - (w, h).into() + let size = LogicalSize::from((w, h)); + size.to_physical(dpi) } #[inline] // NOTE: This will only resize the borders, the contents must be updated by the user - pub fn set_inner_size(&self, size: LogicalSize) { - let (w, h) = size.into(); + pub fn set_inner_size(&self, size: Size) { + let dpi = self.hidpi_factor() as f64; + let (w, h) = size.to_logical(dpi).into(); self.frame.lock().unwrap().resize(w, h); *(self.size.lock().unwrap()) = (w, h); } #[inline] - pub fn set_min_inner_size(&self, dimensions: Option) { + pub fn set_min_inner_size(&self, dimensions: Option) { + let dpi = self.hidpi_factor() as f64; self.frame .lock() .unwrap() - .set_min_size(dimensions.map(Into::into)); + .set_min_size(dimensions.map(|dim| dim.to_logical(dpi).into())); } #[inline] - pub fn set_max_inner_size(&self, dimensions: Option) { + pub fn set_max_inner_size(&self, dimensions: Option) { + let dpi = self.hidpi_factor() as f64; self.frame .lock() .unwrap() - .set_max_size(dimensions.map(Into::into)); + .set_max_size(dimensions.map(|dim| dim.to_logical(dpi).into())); } #[inline] @@ -325,7 +347,7 @@ impl Window { } #[inline] - pub fn set_cursor_position(&self, _pos: LogicalPosition) -> Result<(), ExternalError> { + pub fn set_cursor_position(&self, _pos: Position) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } @@ -375,6 +397,7 @@ impl Drop for Window { struct InternalWindow { surface: wl_surface::WlSurface, + // TODO: CONVERT TO LogicalSizes newsize: Option<(u32, u32)>, size: Arc>, need_refresh: Arc>, @@ -395,6 +418,7 @@ pub struct WindowStore { pub struct WindowStoreForEach<'a> { pub newsize: Option<(u32, u32)>, pub size: &'a mut (u32, u32), + pub prev_dpi: i32, pub new_dpi: Option, pub closed: bool, pub grab_cursor: Option, @@ -460,6 +484,7 @@ impl WindowStore { f(WindowStoreForEach { newsize: window.newsize.take(), size: &mut *(window.size.lock().unwrap()), + prev_dpi: window.current_dpi, new_dpi: window.new_dpi, closed: window.closed, grab_cursor: window.cursor_grab_changed.lock().unwrap().take(), diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 162b09df27..884a7ea149 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc, slice}; +use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc}; use libc::{c_char, c_int, c_long, c_uint, c_ulong}; @@ -11,7 +11,7 @@ use super::{ use util::modifiers::{ModifierKeyState, ModifierKeymap}; use crate::{ - dpi::{LogicalPosition, LogicalSize}, + dpi::{PhysicalPosition, PhysicalSize}, event::{ DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent, }, @@ -45,7 +45,7 @@ impl EventProcessor { fn with_window(&self, window_id: ffi::Window, callback: F) -> Option where - F: Fn(&UnownedWindow) -> Ret, + F: Fn(&Arc) -> Ret, { let mut deleted = false; let window_id = WindowId(window_id); @@ -59,7 +59,7 @@ impl EventProcessor { deleted = arc.is_none(); arc }) - .map(|window| callback(&*window)); + .map(|window| callback(&window)); if deleted { // Garbage collection wt.windows.borrow_mut().remove(&window_id); @@ -107,7 +107,7 @@ impl EventProcessor { pub(super) fn process_event(&mut self, xev: &mut ffi::XEvent, mut callback: F) where - F: FnMut(Event), + F: FnMut(Event<'_, T>), { let wt = get_xtarget(&self.target); // XFilterEvent tells us when an event has been discarded by the input method. @@ -321,16 +321,11 @@ impl EventProcessor { } ffi::ConfigureNotify => { - #[derive(Debug, Default)] - struct Events { - resized: Option, - moved: Option, - dpi_changed: Option, - } - let xev: &ffi::XConfigureEvent = xev.as_ref(); let xwindow = xev.window; - let events = self.with_window(xwindow, |window| { + let window_id = mkwid(xwindow); + + if let Some(window) = self.with_window(xwindow, Arc::clone) { // So apparently... // `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root // `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent @@ -344,7 +339,6 @@ impl EventProcessor { let new_inner_size = (xev.width as u32, xev.height as u32); let new_inner_position = (xev.x as i32, xev.y as i32); - let mut monitor = window.current_monitor(); // This must be done *before* locking! let mut shared_state_lock = window.shared_state.lock(); let (mut resized, moved) = { @@ -374,8 +368,6 @@ impl EventProcessor { (resized, moved) }; - let mut events = Events::default(); - let new_outer_position = if moved || shared_state_lock.position.is_none() { // We need to convert client area position to window position. let frame_extents = shared_state_lock @@ -392,9 +384,10 @@ impl EventProcessor { .inner_pos_to_outer(new_inner_position.0, new_inner_position.1); shared_state_lock.position = Some(outer); if moved { - let logical_position = - LogicalPosition::from_physical(outer, monitor.hidpi_factor); - events.moved = Some(WindowEvent::Moved(logical_position)); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Moved(outer.into()), + }); } outer } else { @@ -406,36 +399,46 @@ impl EventProcessor { // resizing by dragging across monitors *without* dropping the window. let (width, height) = shared_state_lock .dpi_adjusted - .unwrap_or_else(|| (xev.width as f64, xev.height as f64)); + .unwrap_or_else(|| (xev.width as u32, xev.height as u32)); let last_hidpi_factor = shared_state_lock.last_monitor.hidpi_factor; let new_hidpi_factor = { let window_rect = util::AaRect::new(new_outer_position, new_inner_size); - let new_monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); + let monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); - if new_monitor.is_dummy() { + if monitor.is_dummy() { // Avoid updating monitor using a dummy monitor handle last_hidpi_factor } else { - monitor = new_monitor; shared_state_lock.last_monitor = monitor.clone(); monitor.hidpi_factor } }; if last_hidpi_factor != new_hidpi_factor { - events.dpi_changed = - Some(WindowEvent::HiDpiFactorChanged(new_hidpi_factor)); - let (new_width, new_height, flusher) = window.adjust_for_dpi( + let (new_width, new_height) = window.adjust_for_dpi( last_hidpi_factor, new_hidpi_factor, width, height, ); - flusher.queue(); - shared_state_lock.dpi_adjusted = Some((new_width, new_height)); - // if the DPI factor changed, force a resize event to ensure the logical - // size is computed with the right DPI factor - resized = true; + + let mut new_inner_size = Some(PhysicalSize::new(new_width, new_height)); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::HiDpiFactorChanged { + hidpi_factor: new_hidpi_factor, + new_inner_size: &mut new_inner_size, + }, + }); + + if let Some(new_size) = new_inner_size { + window.set_inner_size_physical(new_size.width, new_size.height); + shared_state_lock.dpi_adjusted = Some(new_size.into()); + // if the DPI factor changed, force a resize event to ensure the logical + // size is computed with the right DPI factor + resized = true; + } } } @@ -444,44 +447,19 @@ impl EventProcessor { // WMs constrain the window size, making the resize fail. This would cause an endless stream of // XResizeWindow requests, making Xorg, the winit client, and the WM consume 100% of CPU. if let Some(adjusted_size) = shared_state_lock.dpi_adjusted { - let rounded_size = ( - adjusted_size.0.round() as u32, - adjusted_size.1.round() as u32, - ); - if new_inner_size == rounded_size || !util::wm_name_is_one_of(&["Xfwm4"]) { + if new_inner_size == adjusted_size || !util::wm_name_is_one_of(&["Xfwm4"]) { // When this finally happens, the event will not be synthetic. shared_state_lock.dpi_adjusted = None; } else { - unsafe { - (wt.xconn.xlib.XResizeWindow)( - wt.xconn.display, - xwindow, - rounded_size.0 as c_uint, - rounded_size.1 as c_uint, - ); - } + window.set_inner_size_physical(adjusted_size.0, adjusted_size.1); } } if resized { - let logical_size = - LogicalSize::from_physical(new_inner_size, monitor.hidpi_factor); - events.resized = Some(WindowEvent::Resized(logical_size)); - } - - events - }); - - if let Some(events) = events { - let window_id = mkwid(xwindow); - if let Some(event) = events.dpi_changed { - callback(Event::WindowEvent { window_id, event }); - } - if let Some(event) = events.resized { - callback(Event::WindowEvent { window_id, event }); - } - if let Some(event) = events.moved { - callback(Event::WindowEvent { window_id, event }); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Resized(new_inner_size.into()), + }); } } } @@ -728,24 +706,17 @@ impl EventProcessor { util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) }); if cursor_moved == Some(true) { - let dpi_factor = - self.with_window(xev.event, |window| window.hidpi_factor()); - if let Some(dpi_factor) = dpi_factor { - let position = LogicalPosition::from_physical( - (xev.event_x as f64, xev.event_y as f64), - dpi_factor, - ); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id, - position, - modifiers, - }, - }); - } else { - return; - } + let position = + PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); + + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position, + modifiers, + }, + }); } else if cursor_moved.is_none() { return; } @@ -836,18 +807,14 @@ impl EventProcessor { } } - if let Some(dpi_factor) = - self.with_window(xev.event, |window| window.hidpi_factor()) - { + if self.window_exists(xev.event) { callback(Event::WindowEvent { window_id, event: CursorEntered { device_id }, }); - let position = LogicalPosition::from_physical( - (xev.event_x as f64, xev.event_y as f64), - dpi_factor, - ); + let position = + PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); // The mods field on this event isn't actually populated, so query the // pointer device. In the future, we can likely remove this round-trip by @@ -890,11 +857,6 @@ impl EventProcessor { ffi::XI_FocusIn => { let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; - let dpi_factor = - match self.with_window(xev.event, |window| window.hidpi_factor()) { - Some(dpi_factor) => dpi_factor, - None => return, - }; let window_id = mkwid(xev.event); wt.ime @@ -920,10 +882,9 @@ impl EventProcessor { .map(|device| device.attachment) .unwrap_or(2); - let position = LogicalPosition::from_physical( - (xev.event_x as f64, xev.event_y as f64), - dpi_factor, - ); + let position = + PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); + callback(Event::WindowEvent { window_id, event: CursorMoved { @@ -966,15 +927,11 @@ impl EventProcessor { ffi::XI_TouchEnd => TouchPhase::Ended, _ => unreachable!(), }; - let dpi_factor = - self.with_window(xev.event, |window| window.hidpi_factor()); - if let Some(dpi_factor) = dpi_factor { + if self.window_exists(xev.event) { let id = xev.detail as u64; let modifiers = self.device_mod_state.modifiers(); - let location = LogicalPosition::from_physical( - (xev.event_x as f64, xev.event_y as f64), - dpi_factor, - ); + let location = + PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); // Mouse cursor position changes when touch events are received. // Only the first concurrently active touch ID moves the mouse cursor. @@ -1163,21 +1120,40 @@ impl EventProcessor { // Check if the window is on this monitor let monitor = window.current_monitor(); if monitor.name == new_monitor.name { - callback(Event::WindowEvent { - window_id: mkwid(window_id.0), - event: WindowEvent::HiDpiFactorChanged( - new_monitor.hidpi_factor, - ), - }); let (width, height) = window.inner_size_physical(); - let (_, _, flusher) = window.adjust_for_dpi( - prev_monitor.hidpi_factor, - new_monitor.hidpi_factor, - width as f64, - height as f64, + let (new_width, new_height) = window + .adjust_for_dpi( + prev_monitor.hidpi_factor, + new_monitor.hidpi_factor, + width, + height, + ); + + let window_id = crate::window::WindowId( + crate::platform_impl::platform::WindowId::X( + *window_id, + ), + ); + let mut new_inner_size = Some( + PhysicalSize::new(new_width, new_height), ); - flusher.queue(); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::HiDpiFactorChanged { + hidpi_factor: new_monitor.hidpi_factor, + new_inner_size: &mut new_inner_size, + }, + }); + + if let Some(new_size) = new_inner_size { + let (new_width, new_height) = + new_size.into(); + window.set_inner_size_physical( + new_width, new_height, + ); + } } } } @@ -1203,7 +1179,7 @@ impl EventProcessor { state: ElementState, callback: &mut F, ) where - F: FnMut(Event), + F: FnMut(Event<'_, T>), { let wt = get_xtarget(&self.target); diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index a3bdea9c6b..f0275bf93b 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -24,7 +24,7 @@ pub use self::{ use std::{ cell::RefCell, - collections::{HashMap, HashSet, VecDeque}, + collections::{HashMap, HashSet}, ffi::CStr, mem::{self, MaybeUninit}, ops::Deref, @@ -37,6 +37,10 @@ use std::{ use libc::{self, setlocale, LC_CTYPE}; +use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token}; + +use mio_extras::channel::{channel, Receiver, SendError, Sender}; + use self::{ dnd::{Dnd, DndState}, event_processor::EventProcessor, @@ -51,6 +55,9 @@ use crate::{ window::WindowAttributes, }; +const X_TOKEN: Token = Token(0); +const USER_TOKEN: Token = Token(1); + pub struct EventLoopWindowTarget { xconn: Arc, wm_delete_window: ffi::Atom, @@ -64,18 +71,15 @@ pub struct EventLoopWindowTarget { } pub struct EventLoop { - inner_loop: ::calloop::EventLoop<()>, - _x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>, - _user_source: ::calloop::Source<::calloop::channel::Channel>, - pending_user_events: Rc>>, - event_processor: Rc>>, - user_sender: ::calloop::channel::Sender, - pending_events: Rc>>>, - pub(crate) target: Rc>, + poll: Poll, + event_processor: EventProcessor, + user_channel: Receiver, + user_sender: Sender, + target: Rc>, } pub struct EventLoopProxy { - user_sender: ::calloop::channel::Sender, + user_sender: Sender, } impl Clone for EventLoopProxy { @@ -171,28 +175,27 @@ impl EventLoop { _marker: ::std::marker::PhantomData, }); - // A calloop event loop to drive us - let inner_loop = ::calloop::EventLoop::new().unwrap(); + let poll = Poll::new().unwrap(); - // Handle user events - let pending_user_events = Rc::new(RefCell::new(VecDeque::new())); - let pending_user_events2 = pending_user_events.clone(); + let (user_sender, user_channel) = channel(); - let (user_sender, user_channel) = ::calloop::channel::channel(); - - let _user_source = inner_loop - .handle() - .insert_source(user_channel, move |evt, &mut ()| { - if let ::calloop::channel::Event::Msg(msg) = evt { - pending_user_events2.borrow_mut().push_back(msg); - } - }) - .unwrap(); + poll.register( + &EventedFd(&get_xtarget(&target).xconn.x11_fd), + X_TOKEN, + Ready::readable(), + PollOpt::level(), + ) + .unwrap(); - // Handle X11 events - let pending_events: Rc>> = Default::default(); + poll.register( + &user_channel, + USER_TOKEN, + Ready::readable(), + PollOpt::level(), + ) + .unwrap(); - let processor = EventProcessor { + let event_processor = EventProcessor { target: target.clone(), dnd, devices: Default::default(), @@ -212,38 +215,12 @@ impl EventLoop { .select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask) .queue(); - processor.init_device(ffi::XIAllDevices); - - let processor = Rc::new(RefCell::new(processor)); - let event_processor = processor.clone(); - - // Setup the X11 event source - let mut x11_events = - ::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd); - x11_events.set_interest(::calloop::mio::Ready::readable()); - let _x11_source = inner_loop - .handle() - .insert_source(x11_events, { - let pending_events = pending_events.clone(); - move |evt, &mut ()| { - if evt.readiness.is_readable() { - let mut processor = processor.borrow_mut(); - let mut pending_events = pending_events.borrow_mut(); - let mut pending_redraws = pending_redraws.lock().unwrap(); - - drain_events(&mut processor, &mut pending_events, &mut pending_redraws); - } - } - }) - .unwrap(); + event_processor.init_device(ffi::XIAllDevices); let result = EventLoop { - inner_loop, - pending_events, - _x11_source, - _user_source, + poll, + user_channel, user_sender, - pending_user_events, event_processor, target, }; @@ -261,12 +238,16 @@ impl EventLoop { &self.target } + pub(crate) fn x_connection(&self) -> &Arc { + get_xtarget(&self.target).x_connection() + } + pub fn run_return(&mut self, mut callback: F) where - F: FnMut(Event, &RootELW, &mut ControlFlow), + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { let mut control_flow = ControlFlow::default(); - let wt = get_xtarget(&self.target); + let mut events = Events::with_capacity(8); callback( crate::event::Event::NewEvents(crate::event::StartCause::Init), @@ -275,22 +256,16 @@ impl EventLoop { ); loop { - self.drain_events(); + // Process all pending events + self.drain_events(&mut callback, &mut control_flow); - // Empty the event buffer - { - let mut guard = self.pending_events.borrow_mut(); - for evt in guard.drain(..) { - sticky_exit_callback(evt, &self.target, &mut control_flow, &mut callback); - } - } + let wt = get_xtarget(&self.target); // Empty the user event buffer { - let mut guard = self.pending_user_events.borrow_mut(); - for evt in guard.drain(..) { + while let Ok(event) = self.user_channel.try_recv() { sticky_exit_callback( - crate::event::Event::UserEvent(evt), + crate::event::Event::UserEvent(event), &self.target, &mut control_flow, &mut callback, @@ -331,7 +306,7 @@ impl EventLoop { } let start = Instant::now(); - let (mut cause, deadline, mut timeout); + let (mut cause, deadline, timeout); match control_flow { ControlFlow::Exit => break, @@ -362,26 +337,39 @@ impl EventLoop { } } - if self.events_waiting() { - timeout = Some(Duration::from_millis(0)); - } + if self.event_processor.poll() { + // If the XConnection already contains buffered events, we don't + // need to wait for data on the socket. + // However, we still need to check for user events. + self.poll + .poll(&mut events, Some(Duration::from_millis(0))) + .unwrap(); + events.clear(); + + callback( + crate::event::Event::NewEvents(cause), + &self.target, + &mut control_flow, + ); + } else { + self.poll.poll(&mut events, timeout).unwrap(); + events.clear(); - self.inner_loop.dispatch(timeout, &mut ()).unwrap(); + let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline); - if let Some(deadline) = deadline { - if deadline > Instant::now() { + if wait_cancelled { cause = StartCause::WaitCancelled { start, - requested_resume: Some(deadline), + requested_resume: deadline, }; } - } - callback( - crate::event::Event::NewEvents(cause), - &self.target, - &mut control_flow, - ); + callback( + crate::event::Event::NewEvents(cause), + &self.target, + &mut control_flow, + ); + } } callback( @@ -393,44 +381,42 @@ impl EventLoop { pub fn run(mut self, callback: F) -> ! where - F: 'static + FnMut(Event, &RootELW, &mut ControlFlow), + F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { self.run_return(callback); ::std::process::exit(0); } - fn drain_events(&self) { - let mut processor = self.event_processor.borrow_mut(); - let mut pending_events = self.pending_events.borrow_mut(); + fn drain_events(&mut self, callback: &mut F, control_flow: &mut ControlFlow) + where + F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), + { + let target = &self.target; + let mut xev = MaybeUninit::uninit(); + let wt = get_xtarget(&self.target); let mut pending_redraws = wt.pending_redraws.lock().unwrap(); - drain_events(&mut processor, &mut pending_events, &mut pending_redraws); - } - - fn events_waiting(&self) -> bool { - !self.pending_events.borrow().is_empty() || self.event_processor.borrow().poll() - } -} - -fn drain_events( - processor: &mut EventProcessor, - pending_events: &mut VecDeque>, - pending_redraws: &mut HashSet, -) { - let mut callback = |event| { - if let Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))) = event { - pending_redraws.insert(wid); - } else { - pending_events.push_back(event); + while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } { + let mut xev = unsafe { xev.assume_init() }; + self.event_processor.process_event(&mut xev, |event| { + sticky_exit_callback( + event, + target, + control_flow, + &mut |event, window_target, control_flow| { + if let Event::RedrawRequested(crate::window::WindowId( + super::WindowId::X(wid), + )) = event + { + pending_redraws.insert(wid); + } else { + callback(event, window_target, control_flow); + } + }, + ); + }); } - }; - - // process all pending events - let mut xev = MaybeUninit::uninit(); - while unsafe { processor.poll_one_event(xev.as_mut_ptr()) } { - let mut xev = unsafe { xev.assume_init() }; - processor.process_event(&mut xev, &mut callback); } } @@ -452,7 +438,7 @@ impl EventLoopWindowTarget { impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { self.user_sender.send(event).map_err(|e| { - EventLoopClosed(if let ::calloop::channel::SendError::Disconnected(x) = e { + EventLoopClosed(if let SendError::Disconnected(x) = e { x } else { unreachable!() diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index fc12ba0afa..0a72875b09 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -15,7 +15,7 @@ use libc; use parking_lot::Mutex; use crate::{ - dpi::{LogicalPosition, LogicalSize}, + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::{ @@ -36,7 +36,7 @@ pub struct SharedState { pub inner_position: Option<(i32, i32)>, pub inner_position_rel_parent: Option<(i32, i32)>, pub last_monitor: X11MonitorHandle, - pub dpi_adjusted: Option<(f64, f64)>, + pub dpi_adjusted: Option<(u32, u32)>, pub fullscreen: Option, // Set when application calls `set_fullscreen` when window is not visible pub desired_fullscreen: Option>, @@ -45,8 +45,8 @@ pub struct SharedState { // Used to restore video mode after exiting fullscreen pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>, pub frame_extents: Option, - pub min_inner_size: Option, - pub max_inner_size: Option, + pub min_inner_size: Option, + pub max_inner_size: Option, pub visibility: Visibility, } @@ -148,8 +148,8 @@ impl UnownedWindow { // by the user, so we have to manually apply the initial constraints let mut dimensions: (u32, u32) = window_attrs .inner_size - .or_else(|| Some((800, 600).into())) .map(|size| size.to_physical(dpi_factor)) + .or_else(|| Some((800, 600).into())) .map(Into::into) .unwrap(); if let Some(max) = max_inner_size { @@ -440,16 +440,6 @@ impl UnownedWindow { .map_err(|x_err| os_error!(OsError::XError(x_err))) } - fn logicalize_coords(&self, (x, y): (i32, i32)) -> LogicalPosition { - let dpi = self.hidpi_factor(); - LogicalPosition::from_physical((x, y), dpi) - } - - fn logicalize_size(&self, (width, height): (u32, u32)) -> LogicalSize { - let dpi = self.hidpi_factor(); - LogicalSize::from_physical((width, height), dpi) - } - fn set_pid(&self) -> Option> { let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") }; let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") }; @@ -951,11 +941,11 @@ impl UnownedWindow { } #[inline] - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result { let extents = (*self.shared_state.lock()).frame_extents.clone(); if let Some(extents) = extents { - let logical = self.inner_position().unwrap(); - Ok(extents.inner_pos_to_outer_logical(logical, self.hidpi_factor())) + let (x, y) = self.inner_position_physical(); + Ok(extents.inner_pos_to_outer(x, y).into()) } else { self.update_cached_frame_extents(); self.outer_position() @@ -972,8 +962,8 @@ impl UnownedWindow { } #[inline] - pub fn inner_position(&self) -> Result { - Ok(self.logicalize_coords(self.inner_position_physical())) + pub fn inner_position(&self) -> Result { + Ok(self.inner_position_physical().into()) } pub(crate) fn set_position_inner(&self, mut x: i32, mut y: i32) -> util::Flusher<'_> { @@ -1002,8 +992,8 @@ impl UnownedWindow { } #[inline] - pub fn set_outer_position(&self, logical_position: LogicalPosition) { - let (x, y) = logical_position.to_physical(self.hidpi_factor()).into(); + pub fn set_outer_position(&self, position: Position) { + let (x, y) = position.to_physical(self.hidpi_factor()).into(); self.set_position_physical(x, y); } @@ -1017,16 +1007,16 @@ impl UnownedWindow { } #[inline] - pub fn inner_size(&self) -> LogicalSize { - self.logicalize_size(self.inner_size_physical()) + pub fn inner_size(&self) -> PhysicalSize { + self.inner_size_physical().into() } #[inline] - pub fn outer_size(&self) -> LogicalSize { + pub fn outer_size(&self) -> PhysicalSize { let extents = self.shared_state.lock().frame_extents.clone(); if let Some(extents) = extents { - let logical = self.inner_size(); - extents.inner_size_to_outer_logical(logical, self.hidpi_factor()) + let (width, height) = self.inner_size_physical(); + extents.inner_size_to_outer(width, height).into() } else { self.update_cached_frame_extents(); self.outer_size() @@ -1047,9 +1037,9 @@ impl UnownedWindow { } #[inline] - pub fn set_inner_size(&self, logical_size: LogicalSize) { + pub fn set_inner_size(&self, size: Size) { let dpi_factor = self.hidpi_factor(); - let (width, height) = logical_size.to_physical(dpi_factor).into(); + let (width, height) = size.to_physical(dpi_factor).into(); self.set_inner_size_physical(width, height); } @@ -1070,10 +1060,10 @@ impl UnownedWindow { } #[inline] - pub fn set_min_inner_size(&self, logical_dimensions: Option) { - self.shared_state.lock().min_inner_size = logical_dimensions; - let physical_dimensions = logical_dimensions - .map(|logical_dimensions| logical_dimensions.to_physical(self.hidpi_factor()).into()); + pub fn set_min_inner_size(&self, dimensions: Option) { + self.shared_state.lock().min_inner_size = dimensions; + let physical_dimensions = + dimensions.map(|dimensions| dimensions.to_physical(self.hidpi_factor()).into()); self.set_min_inner_size_physical(physical_dimensions); } @@ -1083,10 +1073,10 @@ impl UnownedWindow { } #[inline] - pub fn set_max_inner_size(&self, logical_dimensions: Option) { - self.shared_state.lock().max_inner_size = logical_dimensions; - let physical_dimensions = logical_dimensions - .map(|logical_dimensions| logical_dimensions.to_physical(self.hidpi_factor()).into()); + pub fn set_max_inner_size(&self, dimensions: Option) { + self.shared_state.lock().max_inner_size = dimensions; + let physical_dimensions = + dimensions.map(|dimensions| dimensions.to_physical(self.hidpi_factor()).into()); self.set_max_inner_size_physical(physical_dimensions); } @@ -1094,12 +1084,10 @@ impl UnownedWindow { &self, old_dpi_factor: f64, new_dpi_factor: f64, - width: f64, - height: f64, - ) -> (f64, f64, util::Flusher<'_>) { + width: u32, + height: u32, + ) -> (u32, u32) { let scale_factor = new_dpi_factor / old_dpi_factor; - let new_width = width * scale_factor; - let new_height = height * scale_factor; self.update_normal_hints(|normal_hints| { let dpi_adjuster = |(width, height): (u32, u32)| -> (u32, u32) { let new_width = width as f64 * scale_factor; @@ -1116,15 +1104,11 @@ impl UnownedWindow { normal_hints.set_base_size(base_size); }) .expect("Failed to update normal hints"); - unsafe { - (self.xconn.xlib.XResizeWindow)( - self.xconn.display, - self.xwindow, - new_width.round() as c_uint, - new_height.round() as c_uint, - ); - } - (new_width, new_height, util::Flusher::new(&self.xconn)) + + let new_width = (width as f64 * scale_factor).round() as u32; + let new_height = (height as f64 * scale_factor).round() as u32; + + (new_width, new_height) } pub fn set_resizable(&self, resizable: bool) { @@ -1136,25 +1120,25 @@ impl UnownedWindow { return; } - let (logical_min, logical_max) = if resizable { + let (min_size, max_size) = if resizable { let shared_state_lock = self.shared_state.lock(); ( shared_state_lock.min_inner_size, shared_state_lock.max_inner_size, ) } else { - let window_size = Some(self.inner_size()); + let window_size = Some(Size::from(self.inner_size())); (window_size.clone(), window_size) }; self.set_maximizable_inner(resizable).queue(); let dpi_factor = self.hidpi_factor(); - let min_inner_size = logical_min - .map(|logical_size| logical_size.to_physical(dpi_factor)) + let min_inner_size = min_size + .map(|size| size.to_physical(dpi_factor)) .map(Into::into); - let max_inner_size = logical_max - .map(|logical_size| logical_size.to_physical(dpi_factor)) + let max_inner_size = max_size + .map(|size| size.to_physical(dpi_factor)) .map(Into::into); self.update_normal_hints(|normal_hints| { normal_hints.set_min_size(min_inner_size); @@ -1289,11 +1273,8 @@ impl UnownedWindow { } #[inline] - pub fn set_cursor_position( - &self, - logical_position: LogicalPosition, - ) -> Result<(), ExternalError> { - let (x, y) = logical_position.to_physical(self.hidpi_factor()).into(); + pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { + let (x, y) = position.to_physical(self.hidpi_factor()).into(); self.set_cursor_position_physical(x, y) } @@ -1305,8 +1286,8 @@ impl UnownedWindow { } #[inline] - pub fn set_ime_position(&self, logical_spot: LogicalPosition) { - let (x, y) = logical_spot.to_physical(self.hidpi_factor()).into(); + pub fn set_ime_position(&self, spot: Position) { + let (x, y) = spot.to_physical(self.hidpi_factor()).into(); self.set_ime_position_physical(x, y); } diff --git a/src/window.rs b/src/window.rs index b0ab30a367..b97b9c6a58 100644 --- a/src/window.rs +++ b/src/window.rs @@ -459,7 +459,7 @@ impl Window { self.window.set_outer_position(position.into()) } - /// Returns the logical size of the window's client area. + /// Returns the physical size of the window's client area. /// /// The client area is the content of the window, excluding the title bar and borders. /// @@ -488,7 +488,7 @@ impl Window { self.window.set_inner_size(size.into()) } - /// Returns the logical size of the entire window. + /// Returns the physical size of the entire window. /// /// These dimensions include the title bar and borders. If you don't want that (and you usually don't), /// use `inner_size` instead. From 077ee4d85184448e443dbddf089eaa6d3f74175d Mon Sep 17 00:00:00 2001 From: Vladimir Bogaevsky Date: Sat, 4 Jan 2020 01:32:34 -0500 Subject: [PATCH 050/239] macOS: Dpi overhaul (#997) (and rebase changes) * WIP - Make EL2 DPI changes and implement on Windows (#895) * Modify DPI API publicly and on Windows * Add generic Position and make dpi creation functions const * Make examples work * Fix fullscreen windows not appearing * Replace Logical coordinates in window events with Physical coordinates * Update HiDpiFactorChanged * Document to_static * fix app_state errors * fixes hidpi related errors in window_delegate * fix bad merge * dpi_factor edits in window_delegate * fixes type and lifetime errors in window and window_delegate * applies fmt * complies with @aleksijuvani requested changes * modifies Handler lifetimes * fixes lifetime isues, adds propper handling for HiDpiChanged * applies fmt * restore original lifetimes * solution is somewhere out there * applies fmt * pass as references * resolves issue with HANDLER * crate visible type error * fixes visibility issues * applies fmt * deals with warnings * simplifies new_inner_size setting algorthm * moves proxy instead of referencing it and removes double deref from proxy.ns_window * makes @Osspial tests (https://github.com/rust-windowing/winit/pull/997\#discussion_r301852354) pass * complies with @aleksijuvani suggested changes * makes max window size std::f32::MAX Changes from rebasing: * fixes compile errors * applies fmt * reimplements HiDpiFactorChanged after #1173 merge * uses EventWrappers --- Cargo.toml | 2 +- examples/multithreaded.rs | 20 ++-- src/platform_impl/macos/app.rs | 22 ++-- src/platform_impl/macos/app_state.rs | 116 ++++++++++++++------ src/platform_impl/macos/event.rs | 25 ++++- src/platform_impl/macos/event_loop.rs | 4 +- src/platform_impl/macos/util/mod.rs | 1 + src/platform_impl/macos/view.rs | 48 ++++----- src/platform_impl/macos/window.rs | 120 +++++++++++++-------- src/platform_impl/macos/window_delegate.rs | 68 ++++++------ 10 files changed, 273 insertions(+), 153 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f46f61a9bf..c2f5c9a1ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ cocoa = "0.19.1" core-foundation = "0.6" core-graphics = "0.17.3" dispatch = "0.1.4" -objc = "0.2.3" +objc = "0.2.6" [target.'cfg(target_os = "macos")'.dependencies.core-video-sys] version = "0.1.3" diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 955cdcc97c..388a4af5dd 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -5,7 +5,7 @@ fn main() { use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use winit::{ - dpi::{PhysicalPosition, PhysicalSize}, + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::{CursorIcon, Fullscreen, WindowBuilder}, @@ -122,12 +122,18 @@ fn main() { ), false => WINDOW_SIZE, }), - W => window - .set_cursor_position(PhysicalPosition::new( - WINDOW_SIZE.width as f64 / 2.0, - WINDOW_SIZE.height as f64 / 2.0, - )) - .unwrap(), + W => { + if let Size::Physical(size) = WINDOW_SIZE.into() { + window + .set_cursor_position(Position::Physical( + PhysicalPosition::new( + size.width as f64 / 2.0, + size.height as f64 / 2.0, + ), + )) + .unwrap() + } + } Z => { window.set_visible(false); thread::sleep(Duration::from_secs(1)); diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index 3278cb13a8..fba48ae5ca 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -11,7 +11,7 @@ use objc::{ use crate::{ event::{DeviceEvent, ElementState, Event}, - platform_impl::platform::{app_state::AppState, util, DEVICE_ID}, + platform_impl::platform::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}, }; pub struct AppClass(pub *const Class); @@ -71,32 +71,32 @@ unsafe fn maybe_dispatch_device_event(event: id) { let delta_y = event.deltaY() as f64; if delta_x != 0.0 { - events.push_back(Event::DeviceEvent { + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { device_id: DEVICE_ID, event: DeviceEvent::Motion { axis: 0, value: delta_x, }, - }); + })); } if delta_y != 0.0 { - events.push_back(Event::DeviceEvent { + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { device_id: DEVICE_ID, event: DeviceEvent::Motion { axis: 1, value: delta_y, }, - }); + })); } if delta_x != 0.0 || delta_y != 0.0 { - events.push_back(Event::DeviceEvent { + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { device_id: DEVICE_ID, event: DeviceEvent::MouseMotion { delta: (delta_x, delta_y), }, - }); + })); } AppState::queue_events(events); @@ -104,26 +104,26 @@ unsafe fn maybe_dispatch_device_event(event: id) { appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => { let mut events = VecDeque::with_capacity(1); - events.push_back(Event::DeviceEvent { + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { device_id: DEVICE_ID, event: DeviceEvent::Button { button: event.buttonNumber() as u32, state: ElementState::Pressed, }, - }); + })); AppState::queue_events(events); } appkit::NSLeftMouseUp | appkit::NSRightMouseUp | appkit::NSOtherMouseUp => { let mut events = VecDeque::with_capacity(1); - events.push_back(Event::DeviceEvent { + events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent { device_id: DEVICE_ID, event: DeviceEvent::Button { button: event.buttonNumber() as u32, state: ElementState::Released, }, - }); + })); AppState::queue_events(events); } diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 626b95fc56..618be00233 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -12,15 +12,21 @@ use std::{ }; use cocoa::{ - appkit::NSApp, + appkit::{NSApp, NSWindow}, base::nil, - foundation::{NSAutoreleasePool, NSString}, + foundation::{NSAutoreleasePool, NSSize, NSString}, }; use crate::{ - event::{Event, StartCause}, + dpi::LogicalSize, + event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, - platform_impl::platform::{observer::EventLoopWaker, util::Never}, + platform_impl::platform::{ + event::{EventProxy, EventWrapper}, + observer::EventLoopWaker, + util::{IdRef, Never}, + window::get_window_id, + }, window::WindowId, }; use objc::runtime::Object; @@ -29,8 +35,8 @@ lazy_static! { static ref HANDLER: Handler = Default::default(); } -impl Event { - fn userify(self) -> Event { +impl<'a, Never> Event<'a, Never> { + fn userify(self) -> Event<'a, T> { self.map_nonuser_event() // `Never` can't be constructed, so the `UserEvent` variant can't // be present here. @@ -39,12 +45,13 @@ impl Event { } pub trait EventHandler: Debug { - fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow); + // Not sure probably it should accept Event<'static, Never> + fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow); fn handle_user_events(&mut self, control_flow: &mut ControlFlow); } struct EventLoopHandler { - callback: Box, &RootWindowTarget, &mut ControlFlow)>, + callback: Box, &RootWindowTarget, &mut ControlFlow)>, will_exit: bool, window_target: Rc>, } @@ -59,7 +66,7 @@ impl Debug for EventLoopHandler { } impl EventHandler for EventLoopHandler { - fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow) { + fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) { (self.callback)(event.userify(), &self.window_target, control_flow); self.will_exit |= *control_flow == ControlFlow::Exit; if self.will_exit { @@ -88,7 +95,7 @@ struct Handler { control_flow_prev: Mutex, start_time: Mutex>, callback: Mutex>>, - pending_events: Mutex>>, + pending_events: Mutex>, pending_redraw: Mutex>, waker: Mutex, } @@ -97,7 +104,7 @@ unsafe impl Send for Handler {} unsafe impl Sync for Handler {} impl Handler { - fn events<'a>(&'a self) -> MutexGuard<'a, VecDeque>> { + fn events(&self) -> MutexGuard<'_, VecDeque> { self.pending_events.lock().unwrap() } @@ -105,7 +112,7 @@ impl Handler { self.pending_redraw.lock().unwrap() } - fn waker<'a>(&'a self) -> MutexGuard<'a, EventLoopWaker> { + fn waker(&self) -> MutexGuard<'_, EventLoopWaker> { self.waker.lock().unwrap() } @@ -141,7 +148,7 @@ impl Handler { *self.start_time.lock().unwrap() = Some(Instant::now()); } - fn take_events(&self) -> VecDeque> { + fn take_events(&self) -> VecDeque { mem::replace(&mut *self.events(), Default::default()) } @@ -157,9 +164,14 @@ impl Handler { self.in_callback.store(in_callback, Ordering::Release); } - fn handle_nonuser_event(&self, event: Event) { + fn handle_nonuser_event(&self, wrapper: EventWrapper) { if let Some(ref mut callback) = *self.callback.lock().unwrap() { - callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap()); + match wrapper { + EventWrapper::StaticEvent(event) => { + callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap()) + } + EventWrapper::EventProxy(proxy) => self.handle_proxy(proxy, callback), + } } } @@ -168,6 +180,46 @@ impl Handler { callback.handle_user_events(&mut *self.control_flow.lock().unwrap()); } } + + fn handle_hidpi_factor_changed_event( + &self, + callback: &mut Box, + ns_window: IdRef, + suggested_size: LogicalSize, + hidpi_factor: f64, + ) { + let size = suggested_size.to_physical(hidpi_factor); + let new_inner_size = &mut Some(size); + let event = Event::WindowEvent { + window_id: WindowId(get_window_id(*ns_window)), + event: WindowEvent::HiDpiFactorChanged { + hidpi_factor, + new_inner_size, + }, + }; + + callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap()); + + let physical_size = new_inner_size.unwrap_or(size); + let logical_size = physical_size.to_logical(hidpi_factor); + let size = NSSize::new(logical_size.width, logical_size.height); + unsafe { NSWindow::setContentSize_(*ns_window, size) }; + } + + fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box) { + match proxy { + EventProxy::HiDpiFactorChangedProxy { + ns_window, + suggested_size, + hidpi_factor, + } => self.handle_hidpi_factor_changed_event( + callback, + ns_window, + suggested_size, + hidpi_factor, + ), + } + } } pub enum AppState {} @@ -176,7 +228,7 @@ impl AppState { // This function extends lifetime of `callback` to 'static as its side effect pub unsafe fn set_callback(callback: F, window_target: Rc>) where - F: FnMut(Event, &RootWindowTarget, &mut ControlFlow), + F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { // This transmute is always safe, in case it was reached through `run`, since our @@ -184,8 +236,8 @@ impl AppState { // they passed to callback will actually outlive it, some apps just can't move // everything to event loop, so this is something that they should care about. callback: mem::transmute::< - Box, &RootWindowTarget, &mut ControlFlow)>, - Box, &RootWindowTarget, &mut ControlFlow)>, + Box, &RootWindowTarget, &mut ControlFlow)>, + Box, &RootWindowTarget, &mut ControlFlow)>, >(Box::new(callback)), will_exit: false, window_target, @@ -194,7 +246,7 @@ impl AppState { pub fn exit() { HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(Event::LoopDestroyed); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed)); HANDLER.set_in_callback(false); HANDLER.callback.lock().unwrap().take(); } @@ -203,7 +255,9 @@ impl AppState { HANDLER.set_ready(); HANDLER.waker().start(); HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init)); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( + StartCause::Init, + ))); HANDLER.set_in_callback(false); } @@ -234,7 +288,7 @@ impl AppState { ControlFlow::Exit => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"), }; HANDLER.set_in_callback(true); - HANDLER.handle_nonuser_event(Event::NewEvents(cause)); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause))); HANDLER.set_in_callback(false); } @@ -246,18 +300,18 @@ impl AppState { } } - pub fn queue_event(event: Event) { + pub fn queue_event(wrapper: EventWrapper) { if !unsafe { msg_send![class!(NSThread), isMainThread] } { - panic!("Event queued from different thread: {:#?}", event); + panic!("Event queued from different thread: {:#?}", wrapper); } - HANDLER.events().push_back(event); + HANDLER.events().push_back(wrapper); } - pub fn queue_events(mut events: VecDeque>) { + pub fn queue_events(mut wrappers: VecDeque) { if !unsafe { msg_send![class!(NSThread), isMainThread] } { - panic!("Events queued from different thread: {:#?}", events); + panic!("Events queued from different thread: {:#?}", wrappers); } - HANDLER.events().append(&mut events); + HANDLER.events().append(&mut wrappers); } pub fn cleared() { @@ -270,11 +324,13 @@ impl AppState { for event in HANDLER.take_events() { HANDLER.handle_nonuser_event(event); } - HANDLER.handle_nonuser_event(Event::MainEventsCleared); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared)); for window_id in HANDLER.should_redraw() { - HANDLER.handle_nonuser_event(Event::RedrawRequested(window_id)); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested( + window_id, + ))); } - HANDLER.handle_nonuser_event(Event::RedrawEventsCleared); + HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawEventsCleared)); HANDLER.set_in_callback(false); } if HANDLER.should_exit() { diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 4f8123d4fe..bbf111645b 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -6,10 +6,29 @@ use cocoa::{ }; use crate::{ - event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}, - platform_impl::platform::DEVICE_ID, + dpi::LogicalSize, + event::{ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}, + platform_impl::platform::{ + util::{IdRef, Never}, + DEVICE_ID, + }, }; +#[derive(Debug)] +pub enum EventWrapper { + StaticEvent(Event<'static, Never>), + EventProxy(EventProxy), +} + +#[derive(Debug, PartialEq)] +pub enum EventProxy { + HiDpiFactorChangedProxy { + ns_window: IdRef, + suggested_size: LogicalSize, + hidpi_factor: f64, + }, +} + pub fn char_to_keycode(c: char) -> Option { // We only translate keys that are affected by keyboard layout. // @@ -256,7 +275,7 @@ pub unsafe fn modifier_event( ns_event: id, keymask: NSEventModifierFlags, was_key_pressed: bool, -) -> Option { +) -> Option> { if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask) || was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) { diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 57e07b928f..cc3d392840 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -84,7 +84,7 @@ impl EventLoop { pub fn run(mut self, callback: F) -> ! where - F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), + F: 'static + FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { self.run_return(callback); process::exit(0); @@ -92,7 +92,7 @@ impl EventLoop { pub fn run_return(&mut self, callback: F) where - F: FnMut(Event, &RootWindowTarget, &mut ControlFlow), + F: FnMut(Event<'_, T>, &RootWindowTarget, &mut ControlFlow), { unsafe { let pool = NSAutoreleasePool::new(nil); diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 238c9e5ddc..3a454cbba4 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -31,6 +31,7 @@ pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange { length: 0, }; +#[derive(Debug, PartialEq)] pub struct IdRef(id); impl IdRef { diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 8795ea1815..2914c1926c 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -25,7 +25,7 @@ use crate::{ app_state::AppState, event::{ char_to_keycode, check_function_keys, event_mods, get_scancode, modifier_event, - scancode_to_keycode, + scancode_to_keycode, EventWrapper, }, ffi::*, util::{self, IdRef}, @@ -512,10 +512,10 @@ extern "C" fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_ran let mut events = VecDeque::with_capacity(characters.len()); for character in string.chars().filter(|c| !is_corporate_character(*c)) { - events.push_back(Event::WindowEvent { + events.push_back(EventWrapper::StaticEvent(Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::ReceivedCharacter(character), - }); + })); } AppState::queue_events(events); @@ -536,10 +536,10 @@ extern "C" fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { // The `else` condition would emit the same character, but I'm keeping this here both... // 1) as a reminder for how `doCommandBySelector` works // 2) to make our use of carriage return explicit - events.push_back(Event::WindowEvent { + events.push_back(EventWrapper::StaticEvent(Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::ReceivedCharacter('\r'), - }); + })); } else { let raw_characters = state.raw_characters.take(); if let Some(raw_characters) = raw_characters { @@ -547,10 +547,10 @@ extern "C" fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { .chars() .filter(|c| !is_corporate_character(*c)) { - events.push_back(Event::WindowEvent { + events.push_back(EventWrapper::StaticEvent(Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::ReceivedCharacter(character), - }); + })); } } }; @@ -646,14 +646,14 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { }; let pass_along = { - AppState::queue_event(window_event); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); // Emit `ReceivedCharacter` for key repeats if is_repeat && state.is_key_down { for character in characters.chars().filter(|c| !is_corporate_character(*c)) { - AppState::queue_event(Event::WindowEvent { + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id, event: WindowEvent::ReceivedCharacter(character), - }); + })); } false } else { @@ -697,7 +697,7 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) { }, }; - AppState::queue_event(window_event); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } trace!("Completed `keyUp`"); } @@ -747,16 +747,16 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) { } for event in events { - AppState::queue_event(Event::WindowEvent { + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event, - }); + })); } - AppState::queue_event(Event::DeviceEvent { + AppState::queue_event(EventWrapper::StaticEvent(Event::DeviceEvent { device_id: DEVICE_ID, event: DeviceEvent::ModifiersChanged(state.modifiers), - }); + })); } trace!("Completed `flagsChanged`"); } @@ -811,7 +811,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { }, }; - AppState::queue_event(window_event); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } trace!("Completed `cancelOperation`"); } @@ -831,7 +831,7 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem }, }; - AppState::queue_event(window_event); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } } @@ -892,7 +892,7 @@ fn mouse_motion(this: &Object, event: id) { }, }; - AppState::queue_event(window_event); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } } @@ -944,8 +944,8 @@ extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) { } }; - AppState::queue_event(enter_event); - AppState::queue_event(move_event); + AppState::queue_event(EventWrapper::StaticEvent(enter_event)); + AppState::queue_event(EventWrapper::StaticEvent(move_event)); } trace!("Completed `mouseEntered`"); } @@ -963,7 +963,7 @@ extern "C" fn mouse_exited(this: &Object, _sel: Sel, _event: id) { }, }; - AppState::queue_event(window_event); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } trace!("Completed `mouseExited`"); } @@ -1005,8 +1005,8 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { }, }; - AppState::queue_event(device_event); - AppState::queue_event(window_event); + AppState::queue_event(EventWrapper::StaticEvent(device_event)); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } trace!("Completed `scrollWheel`"); } @@ -1029,7 +1029,7 @@ extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { }, }; - AppState::queue_event(window_event); + AppState::queue_event(EventWrapper::StaticEvent(window_event)); } trace!("Completed `pressureChangeWithEvent`"); } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index ff66f82ac5..4ed62756be 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -10,7 +10,9 @@ use std::{ }; use crate::{ - dpi::{LogicalPosition, LogicalSize}, + dpi::{ + LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size, Size::Logical, + }, error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, @@ -130,12 +132,17 @@ fn create_window( None => None, }; let frame = match screen { - Some(screen) => appkit::NSScreen::frame(screen), + Some(screen) => NSScreen::frame(screen), None => { - let (width, height) = attrs - .inner_size - .map(|logical| (logical.width, logical.height)) - .unwrap_or_else(|| (800.0, 600.0)); + let screen = NSScreen::mainScreen(nil); + let hidpi_factor = NSScreen::backingScaleFactor(screen) as f64; + let (width, height) = match attrs.inner_size { + Some(size) => { + let logical = size.to_logical(hidpi_factor); + (logical.width, logical.height) + } + None => (800.0, 600.0), + }; NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height)) } }; @@ -292,6 +299,7 @@ pub struct UnownedWindow { pub shared_state: Arc>, decorations: AtomicBool, cursor_state: Weak>, + pub inner_rect: Option, } unsafe impl Send for UnownedWindow {} @@ -328,6 +336,8 @@ impl UnownedWindow { let input_context = unsafe { util::create_input_context(*ns_view) }; + let dpi_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 }; + unsafe { if win_attribs.transparent { ns_window.setOpaque_(NO); @@ -335,13 +345,14 @@ impl UnownedWindow { } ns_app.activateIgnoringOtherApps_(YES); - - win_attribs - .min_inner_size - .map(|dim| set_min_inner_size(*ns_window, dim)); - win_attribs - .max_inner_size - .map(|dim| set_max_inner_size(*ns_window, dim)); + win_attribs.min_inner_size.map(|dim| { + let logical_dim = dim.to_logical(dpi_factor); + set_min_inner_size(*ns_window, logical_dim) + }); + win_attribs.max_inner_size.map(|dim| { + let logical_dim = dim.to_logical(dpi_factor); + set_max_inner_size(*ns_window, logical_dim) + }); use cocoa::foundation::NSArray; // register for drag and drop operations. @@ -361,6 +372,9 @@ impl UnownedWindow { let maximized = win_attribs.maximized; let visible = win_attribs.visible; let decorations = win_attribs.decorations; + let inner_rect = win_attribs + .inner_size + .map(|size| size.to_physical(dpi_factor)); let window = Arc::new(UnownedWindow { ns_view, @@ -369,6 +383,7 @@ impl UnownedWindow { shared_state: Arc::new(Mutex::new(win_attribs.into())), decorations: AtomicBool::new(decorations), cursor_state, + inner_rect, }); let delegate = new_delegate(&window, fullscreen.is_some()); @@ -425,27 +440,31 @@ impl UnownedWindow { AppState::queue_redraw(RootWindowId(self.id())); } - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result { let frame_rect = unsafe { NSWindow::frame(*self.ns_window) }; - Ok(( + let position = LogicalPosition::new( frame_rect.origin.x as f64, util::bottom_left_to_top_left(frame_rect), - ) - .into()) + ); + let dpi_factor = self.hidpi_factor(); + Ok(position.to_physical(dpi_factor)) } - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result { let content_rect = unsafe { NSWindow::contentRectForFrameRect_(*self.ns_window, NSWindow::frame(*self.ns_window)) }; - Ok(( + let position = LogicalPosition::new( content_rect.origin.x as f64, util::bottom_left_to_top_left(content_rect), - ) - .into()) + ); + let dpi_factor = self.hidpi_factor(); + Ok(position.to_physical(dpi_factor)) } - pub fn set_outer_position(&self, position: LogicalPosition) { + pub fn set_outer_position(&self, position: Position) { + let dpi_factor = self.hidpi_factor(); + let position = position.to_logical(dpi_factor); let dummy = NSRect::new( NSPoint::new( position.x, @@ -461,35 +480,50 @@ impl UnownedWindow { } #[inline] - pub fn inner_size(&self) -> LogicalSize { + pub fn inner_size(&self) -> PhysicalSize { let view_frame = unsafe { NSView::frame(*self.ns_view) }; - (view_frame.size.width as f64, view_frame.size.height as f64).into() + let logical: LogicalSize = + (view_frame.size.width as f64, view_frame.size.height as f64).into(); + let dpi_factor = self.hidpi_factor(); + logical.to_physical(dpi_factor) } #[inline] - pub fn outer_size(&self) -> LogicalSize { + pub fn outer_size(&self) -> PhysicalSize { let view_frame = unsafe { NSWindow::frame(*self.ns_window) }; - (view_frame.size.width as f64, view_frame.size.height as f64).into() + let logical: LogicalSize = + (view_frame.size.width as f64, view_frame.size.height as f64).into(); + let dpi_factor = self.hidpi_factor(); + logical.to_physical(dpi_factor) } #[inline] - pub fn set_inner_size(&self, size: LogicalSize) { + pub fn set_inner_size(&self, size: Size) { unsafe { - util::set_content_size_async(*self.ns_window, size); + let dpi_factor = self.hidpi_factor(); + util::set_content_size_async(*self.ns_window, size.to_logical(dpi_factor)); } } - pub fn set_min_inner_size(&self, dimensions: Option) { + pub fn set_min_inner_size(&self, dimensions: Option) { unsafe { - let dimensions = dimensions.unwrap_or_else(|| (0, 0).into()); - set_min_inner_size(*self.ns_window, dimensions); + let dimensions = dimensions.unwrap_or(Logical(LogicalSize { + width: 0.0, + height: 0.0, + })); + let dpi_factor = self.hidpi_factor(); + set_min_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor)); } } - pub fn set_max_inner_size(&self, dimensions: Option) { + pub fn set_max_inner_size(&self, dimensions: Option) { unsafe { - let dimensions = dimensions.unwrap_or_else(|| (!0, !0).into()); - set_max_inner_size(*self.ns_window, dimensions); + let dimensions = dimensions.unwrap_or(Logical(LogicalSize { + width: std::f32::MAX as f64, + height: std::f32::MAX as f64, + })); + let dpi_factor = self.hidpi_factor(); + set_max_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor)); } } @@ -554,14 +588,14 @@ impl UnownedWindow { } #[inline] - pub fn set_cursor_position( - &self, - cursor_position: LogicalPosition, - ) -> Result<(), ExternalError> { - let window_position = self.inner_position().unwrap(); + pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> { + let physical_window_position = self.inner_position().unwrap(); + let dpi_factor = self.hidpi_factor(); + let window_position = physical_window_position.to_logical(dpi_factor); + let logical_cursor_position = cursor_position.to_logical(dpi_factor); let point = appkit::CGPoint { - x: (cursor_position.x + window_position.x) as CGFloat, - y: (cursor_position.y + window_position.y) as CGFloat, + x: (logical_cursor_position.x + window_position.x) as CGFloat, + y: (logical_cursor_position.y + window_position.y) as CGFloat, }; CGDisplay::warp_mouse_cursor_position(point) .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; @@ -894,7 +928,9 @@ impl UnownedWindow { } #[inline] - pub fn set_ime_position(&self, logical_spot: LogicalPosition) { + pub fn set_ime_position(&self, spot: Position) { + let dpi_factor = self.hidpi_factor(); + let logical_spot = spot.to_logical(dpi_factor); unsafe { view::set_ime_position( *self.ns_view, diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index ecdd4eeff1..d4ac37b54d 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -19,6 +19,7 @@ use crate::{ event::{Event, WindowEvent}, platform_impl::platform::{ app_state::AppState, + event::{EventProxy, EventWrapper}, util::{self, IdRef}, window::{get_window_id, UnownedWindow}, }, @@ -47,20 +48,18 @@ pub struct WindowDelegateState { impl WindowDelegateState { pub fn new(window: &Arc, initial_fullscreen: bool) -> Self { - let dpi_factor = window.hidpi_factor(); - + let hidpi_factor = window.hidpi_factor(); let mut delegate_state = WindowDelegateState { ns_window: window.ns_window.clone(), ns_view: window.ns_view.clone(), window: Arc::downgrade(&window), initial_fullscreen, previous_position: None, - previous_dpi_factor: dpi_factor, + previous_dpi_factor: hidpi_factor, }; - if dpi_factor != 1.0 { - delegate_state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor)); - delegate_state.emit_resize_event(); + if hidpi_factor != 1.0 { + delegate_state.emit_static_hidpi_factor_changed_event(); } delegate_state @@ -73,17 +72,34 @@ impl WindowDelegateState { self.window.upgrade().map(|ref window| callback(window)) } - pub fn emit_event(&mut self, event: WindowEvent) { + pub fn emit_event(&mut self, event: WindowEvent<'static>) { let event = Event::WindowEvent { window_id: WindowId(get_window_id(*self.ns_window)), event, }; - AppState::queue_event(event); + AppState::queue_event(EventWrapper::StaticEvent(event)); + } + + pub fn emit_static_hidpi_factor_changed_event(&mut self) { + let hidpi_factor = self.get_hidpi_factor(); + if hidpi_factor == self.previous_dpi_factor { + return (); + }; + + self.previous_dpi_factor = hidpi_factor; + let wrapper = EventWrapper::EventProxy(EventProxy::HiDpiFactorChangedProxy { + ns_window: IdRef::retain(*self.ns_window), + suggested_size: self.view_size(), + hidpi_factor, + }); + AppState::queue_event(wrapper); } pub fn emit_resize_event(&mut self) { let rect = unsafe { NSView::frame(*self.ns_view) }; - let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); + let hidpi_factor = self.get_hidpi_factor(); + let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); + let size = logical_size.to_physical(hidpi_factor); self.emit_event(WindowEvent::Resized(size)); } @@ -97,6 +113,15 @@ impl WindowDelegateState { self.emit_event(WindowEvent::Moved((x, y).into())); } } + + fn get_hidpi_factor(&self) -> f64 { + (unsafe { NSWindow::backingScaleFactor(*self.ns_window) }) as f64 + } + + fn view_size(&self) -> LogicalSize { + let ns_size = unsafe { NSView::frame(*self.ns_view).size }; + LogicalSize::new(ns_size.width as f64, ns_size.height as f64) + } } pub fn new_delegate(window: &Arc, initial_fullscreen: bool) -> IdRef { @@ -140,10 +165,6 @@ lazy_static! { sel!(windowDidMove:), window_did_move as extern "C" fn(&Object, Sel, id), ); - decl.add_method( - sel!(windowDidChangeScreen:), - window_did_change_screen as extern "C" fn(&Object, Sel, id), - ); decl.add_method( sel!(windowDidChangeBackingProperties:), window_did_change_backing_properties as extern "C" fn(&Object, Sel, id), @@ -277,29 +298,10 @@ extern "C" fn window_did_move(this: &Object, _: Sel, _: id) { trace!("Completed `windowDidMove:`"); } -extern "C" fn window_did_change_screen(this: &Object, _: Sel, _: id) { - trace!("Triggered `windowDidChangeScreen:`"); - with_state(this, |state| { - let dpi_factor = unsafe { NSWindow::backingScaleFactor(*state.ns_window) } as f64; - if state.previous_dpi_factor != dpi_factor { - state.previous_dpi_factor = dpi_factor; - state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor)); - state.emit_resize_event(); - } - }); - trace!("Completed `windowDidChangeScreen:`"); -} - -// This will always be called before `window_did_change_screen`. extern "C" fn window_did_change_backing_properties(this: &Object, _: Sel, _: id) { trace!("Triggered `windowDidChangeBackingProperties:`"); with_state(this, |state| { - let dpi_factor = unsafe { NSWindow::backingScaleFactor(*state.ns_window) } as f64; - if state.previous_dpi_factor != dpi_factor { - state.previous_dpi_factor = dpi_factor; - state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor)); - state.emit_resize_event(); - } + state.emit_static_hidpi_factor_changed_event(); }); trace!("Completed `windowDidChangeBackingProperties:`"); } From cbf61e5cb9aef7c00160208ef7757a4f292f6024 Mon Sep 17 00:00:00 2001 From: Osspial Date: Wed, 16 Oct 2019 11:59:06 -0400 Subject: [PATCH 051/239] Fix window rectangle change being in wrong changelog entry --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43484cd6d7..048c2e75a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ reduces the potential for cross-platform compatibility gotchyas. - On Windows and Linux X11/Wayland, add platform-specific functions for creating an `EventLoop` outside the main thread. - On Wayland, drop resize events identical to the current window size. +- On Windows, fix window rectangle not getting set correctly on high-DPI systems. # 0.20.0 Alpha 3 (2019-08-14) @@ -144,9 +145,6 @@ and `WindowEvent::HoveredFile`. - On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`. # 0.20.0 Alpha 1 (2019-06-21) -- On Windows, fix window rectangle not getting set correctly on high-DPI systems. - -# 0.20.0 Alpha 1 - Changes below are considered **breaking**. - Change all occurrences of `EventsLoop` to `EventLoop`. From b16042a047a13504999567b63711dff8fffa2304 Mon Sep 17 00:00:00 2001 From: Bogaevsky Date: Fri, 18 Oct 2019 18:31:26 +0300 Subject: [PATCH 052/239] iOS: Dpi overhaul (#1223) * WIP - Make EL2 DPI changes and implement on Windows (#895) * Modify DPI API publicly and on Windows * Add generic Position and make dpi creation functions const * Make examples work * Fix fullscreen windows not appearing * Replace Logical coordinates in window events with Physical coordinates * Update HiDpiFactorChanged * Document to_static * On Windows, make AdjustRect calls DPI-aware when possible (#1015) * Use AdjustWidowRectExForDPI when available * Prioritize presevering logical size when handling WM_DPICHANGED * Format * Add changelog entry * macOS: Dpi overhaul (#997) * WIP - Make EL2 DPI changes and implement on Windows (#895) * Modify DPI API publicly and on Windows * Add generic Position and make dpi creation functions const * Make examples work * Fix fullscreen windows not appearing * Replace Logical coordinates in window events with Physical coordinates * Update HiDpiFactorChanged * Document to_static * fix app_state errors * fixes hidpi related errors in window_delegate * fix bad merge * dpi_factor edits in window_delegate * fixes type and lifetime errors in window and window_delegate * applies fmt * complies with @aleksijuvani requested changes * modifies Handler lifetimes * fixes lifetime isues, adds propper handling for HiDpiChanged * applies fmt * restore original lifetimes * solution is somewhere out there * applies fmt * pass as references * resolves issue with HANDLER * crate visible type error * fixes visibility issues * applies fmt * deals with warnings * simplifies new_inner_size setting algorthm * moves proxy instead of referencing it and removes double deref from proxy.ns_window * makes @Osspial tests (https://github.com/rust-windowing/winit/pull/997\#discussion_r301852354) pass * complies with @aleksijuvani suggested changes * makes max window size std::f32::MAX * On Windows, fix new DPI API not setting window size properly (#1130) * First attempt * Second attempt * Maintain cursor horizontal ratio * Fix DPI change handling when maximized * Revert window example * Make new DPI code more understandable * Format * Implement DPI Usability Upgrades for X11 and Wayland (#1098) * Fix compile errors * Use `mio` for the X11 event loop * Removes `calloop` from the X11 event loop, as the method of draining a source using a closure provided to the `calloop::EventLoop` instance conflicts with the need to deliver events directly to the callback provided to `EventLoop::run`, in order to respond to the value provided by `WindowEvent::HiDpiFactorChanged`. * Implement interactive `HiDpiFactorChanged` event for X11 * Implement interactive `HiDpiFactorChanged` event for Wayland * Run cargo fmt * Fix Wayland not processing events from EventQueue * Backport #981 * some lifetime tinkering * finishes lifetime tinkering * fixes all type errors * adds support ffi functions * adds wrappers for nonstatic events * replaces events with event wrappers * reimplementing hidpichanged event in app_state * implements HiDpiFactorChanged for iOS * applies formatter * complies with @aleksijuvani requested changes * resolves conflicts * applies fmt * removes merge blurp * corrects state of CHANGELOG * fix fmt check error * fixes hidpi_factor for armv7-apple-ios --- src/platform_impl/ios/app_state.rs | 175 +++++++++++++++++++++------- src/platform_impl/ios/event_loop.rs | 24 +++- src/platform_impl/ios/ffi.rs | 20 +++- src/platform_impl/ios/monitor.rs | 2 +- src/platform_impl/ios/view.rs | 70 ++++++----- src/platform_impl/ios/window.rs | 93 +++++++++------ 6 files changed, 267 insertions(+), 117 deletions(-) diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index e1b82be194..45b995e3a7 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -12,15 +12,16 @@ use std::{ use objc::runtime::{BOOL, YES}; use crate::{ - event::{Event, StartCause}, + dpi::LogicalSize, + event::{Event, StartCause, WindowEvent}, event_loop::ControlFlow, platform_impl::platform::{ - event_loop::{EventHandler, Never}, + event_loop::{EventHandler, EventProxy, EventWrapper, Never}, ffi::{ id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, - CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSInteger, NSOperatingSystemVersion, - NSUInteger, + CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger, + NSOperatingSystemVersion, NSUInteger, }, }, window::WindowId as RootWindowId, @@ -45,11 +46,11 @@ enum UserCallbackTransitionResult<'a> { processing_redraws: bool, }, ReentrancyPrevented { - queued_events: &'a mut Vec>, + queued_events: &'a mut Vec, }, } -impl Event { +impl Event<'static, Never> { fn is_redraw(&self) -> bool { if let Event::RedrawRequested(_) = self { true @@ -65,12 +66,12 @@ impl Event { enum AppStateImpl { NotLaunched { queued_windows: Vec, - queued_events: Vec>, + queued_events: Vec, queued_gpu_redraws: HashSet, }, Launching { queued_windows: Vec, - queued_events: Vec>, + queued_events: Vec, queued_event_handler: Box, queued_gpu_redraws: HashSet, }, @@ -81,7 +82,7 @@ enum AppStateImpl { }, // special state to deal with reentrancy and prevent mutable aliasing. InUserCallback { - queued_events: Vec>, + queued_events: Vec, queued_gpu_redraws: HashSet, }, ProcessingRedraws { @@ -222,7 +223,7 @@ impl AppState { }); } - fn did_finish_launching_transition(&mut self) -> (Vec, Vec>) { + fn did_finish_launching_transition(&mut self) -> (Vec, Vec) { let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() { AppStateImpl::Launching { queued_windows, @@ -245,7 +246,7 @@ impl AppState { (windows, events) } - fn wakeup_transition(&mut self) -> Option> { + fn wakeup_transition(&mut self) -> Option { // before `AppState::did_finish_launching` is called, pretend there is no running // event loop. if !self.has_launched() { @@ -258,7 +259,10 @@ impl AppState { AppStateImpl::PollFinished { waiting_event_handler, }, - ) => (waiting_event_handler, Event::NewEvents(StartCause::Poll)), + ) => ( + waiting_event_handler, + EventWrapper::StaticEvent(Event::NewEvents(StartCause::Poll)), + ), ( ControlFlow::Wait, AppStateImpl::Waiting { @@ -267,10 +271,10 @@ impl AppState { }, ) => ( waiting_event_handler, - Event::NewEvents(StartCause::WaitCancelled { + EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { start, requested_resume: None, - }), + })), ), ( ControlFlow::WaitUntil(requested_resume), @@ -280,15 +284,15 @@ impl AppState { }, ) => { let event = if Instant::now() >= requested_resume { - Event::NewEvents(StartCause::ResumeTimeReached { + EventWrapper::StaticEvent(Event::NewEvents(StartCause::ResumeTimeReached { start, requested_resume, - }) + })) } else { - Event::NewEvents(StartCause::WaitCancelled { + EventWrapper::StaticEvent(Event::NewEvents(StartCause::WaitCancelled { start, requested_resume: Some(requested_resume), - }) + })) }; (waiting_event_handler, event) } @@ -587,7 +591,10 @@ pub unsafe fn did_finish_launching() { let (windows, events) = AppState::get_mut().did_finish_launching_transition(); - let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events); + let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents( + StartCause::Init, + ))) + .chain(events); handle_nonuser_events(events); // the above window dance hack, could possibly trigger new windows to be created. @@ -616,12 +623,12 @@ pub unsafe fn handle_wakeup_transition() { } // requires main thread -pub unsafe fn handle_nonuser_event(event: Event) { +pub unsafe fn handle_nonuser_event(event: EventWrapper) { handle_nonuser_events(std::iter::once(event)) } // requires main thread -pub unsafe fn handle_nonuser_events>>(events: I) { +pub unsafe fn handle_nonuser_events>(events: I) { let mut this = AppState::get_mut(); let (mut event_handler, active_control_flow, processing_redraws) = match this.try_user_callback_transition() { @@ -638,16 +645,23 @@ pub unsafe fn handle_nonuser_events>>(events let mut control_flow = this.control_flow; drop(this); - for event in events { - if !processing_redraws && event.is_redraw() { - log::info!("processing `RedrawRequested` during the main event loop"); - } else if processing_redraws && !event.is_redraw() { - log::warn!( - "processing non `RedrawRequested` event after the main event loop: {:#?}", - event - ); + for wrapper in events { + match wrapper { + EventWrapper::StaticEvent(event) => { + if !processing_redraws && event.is_redraw() { + log::info!("processing `RedrawRequested` during the main event loop"); + } else if processing_redraws && !event.is_redraw() { + log::warn!( + "processing non `RedrawRequested` event after the main event loop: {:#?}", + event + ); + } + event_handler.handle_nonuser_event(event, &mut control_flow) + } + EventWrapper::EventProxy(proxy) => { + handle_event_proxy(&mut event_handler, control_flow, proxy) + } } - event_handler.handle_nonuser_event(event, &mut control_flow) } loop { @@ -688,16 +702,23 @@ pub unsafe fn handle_nonuser_events>>(events } drop(this); - for event in queued_events { - if !processing_redraws && event.is_redraw() { - log::info!("processing `RedrawRequested` during the main event loop"); - } else if processing_redraws && !event.is_redraw() { - log::warn!( - "processing non-`RedrawRequested` event after the main event loop: {:#?}", - event - ); + for wrapper in queued_events { + match wrapper { + EventWrapper::StaticEvent(event) => { + if !processing_redraws && event.is_redraw() { + log::info!("processing `RedrawRequested` during the main event loop"); + } else if processing_redraws && !event.is_redraw() { + log::warn!( + "processing non-`RedrawRequested` event after the main event loop: {:#?}", + event + ); + } + event_handler.handle_nonuser_event(event, &mut control_flow) + } + EventWrapper::EventProxy(proxy) => { + handle_event_proxy(&mut event_handler, control_flow, proxy) + } } - event_handler.handle_nonuser_event(event, &mut control_flow) } } } @@ -751,8 +772,15 @@ unsafe fn handle_user_events() { } drop(this); - for event in queued_events { - event_handler.handle_nonuser_event(event, &mut control_flow) + for wrapper in queued_events { + match wrapper { + EventWrapper::StaticEvent(event) => { + event_handler.handle_nonuser_event(event, &mut control_flow) + } + EventWrapper::EventProxy(proxy) => { + handle_event_proxy(&mut event_handler, control_flow, proxy) + } + } } event_handler.handle_user_events(&mut control_flow); } @@ -772,13 +800,13 @@ pub unsafe fn handle_main_events_cleared() { // User events are always sent out at the end of the "MainEventLoop" handle_user_events(); - handle_nonuser_event(Event::MainEventsCleared); + handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared)); let mut this = AppState::get_mut(); let mut redraw_events: Vec> = this .main_events_cleared_transition() .into_iter() - .map(|window| Event::RedrawRequested(RootWindowId(window.into()))) + .map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))) .collect(); if !redraw_events.is_empty() { @@ -804,6 +832,67 @@ pub unsafe fn terminated() { event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow) } +fn handle_event_proxy( + event_handler: &mut Box, + control_flow: ControlFlow, + proxy: EventProxy, +) { + match proxy { + EventProxy::HiDpiFactorChangedProxy { + suggested_size, + hidpi_factor, + window_id, + } => handle_hidpi_proxy( + event_handler, + control_flow, + suggested_size, + hidpi_factor, + window_id, + ), + } +} + +fn handle_hidpi_proxy( + event_handler: &mut Box, + mut control_flow: ControlFlow, + suggested_size: LogicalSize, + hidpi_factor: f64, + window_id: id, +) { + let size = suggested_size.to_physical(hidpi_factor); + let new_inner_size = &mut Some(size); + let event = Event::WindowEvent { + window_id: RootWindowId(window_id.into()), + event: WindowEvent::HiDpiFactorChanged { + hidpi_factor, + new_inner_size, + }, + }; + event_handler.handle_nonuser_event(event, &mut control_flow); + let (view, screen_frame) = get_view_and_screen_frame(window_id); + if let Some(physical_size) = new_inner_size { + let logical_size = physical_size.to_logical(hidpi_factor); + let size = CGSize::new(logical_size); + let new_frame: CGRect = CGRect::new(screen_frame.origin, size); + unsafe { + let () = msg_send![view, setFrame: new_frame]; + } + } +} + +fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) { + unsafe { + let view_controller: id = msg_send![window_id, rootViewController]; + let view: id = msg_send![view_controller, view]; + let bounds: CGRect = msg_send![window_id, bounds]; + let screen: id = msg_send![window_id, screen]; + let screen_space: id = msg_send![screen, coordinateSpace]; + let screen_frame: CGRect = + msg_send![window_id, convertRect:bounds toCoordinateSpace:screen_space]; + (view, screen_frame) + } +} + struct EventLoopWaker { timer: CFRunLoopTimerRef, } diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 657d29ebb4..869005191c 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -8,6 +8,7 @@ use std::{ }; use crate::{ + dpi::LogicalSize, event::Event, event_loop::{ ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget, @@ -28,6 +29,21 @@ use crate::platform_impl::platform::{ monitor, view, MonitorHandle, }; +#[derive(Debug)] +pub enum EventWrapper { + StaticEvent(Event<'static, Never>), + EventProxy(EventProxy), +} + +#[derive(Debug, PartialEq)] +pub enum EventProxy { + HiDpiFactorChangedProxy { + window_id: id, + suggested_size: LogicalSize, + hidpi_factor: f64, + }, +} + pub struct EventLoopWindowTarget { receiver: Receiver, sender_to_clone: Sender, @@ -69,7 +85,7 @@ impl EventLoop { pub fn run(self, event_handler: F) -> ! where - F: 'static + FnMut(Event, &RootEventLoopWindowTarget, &mut ControlFlow), + F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), { unsafe { let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication]; @@ -277,7 +293,7 @@ fn setup_control_flow_observers() { pub enum Never {} pub trait EventHandler: Debug { - fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow); + fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow); fn handle_user_events(&mut self, control_flow: &mut ControlFlow); } @@ -296,10 +312,10 @@ impl Debug for EventLoopHandler { impl EventHandler for EventLoopHandler where - F: 'static + FnMut(Event, &RootEventLoopWindowTarget, &mut ControlFlow), + F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), T: 'static, { - fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow) { + fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) { (self.f)( event.map_nonuser_event().unwrap(), &self.event_loop, diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 4ecd47f7b2..c7cac81622 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -4,7 +4,10 @@ use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*}; use objc::{runtime::Object, Encode, Encoding}; -use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations}; +use crate::{ + dpi::LogicalSize, + platform::ios::{Idiom, ScreenEdge, ValidOrientations}, +}; pub type id = *mut Object; pub const nil: id = 0 as id; @@ -39,6 +42,15 @@ pub struct CGSize { pub height: CGFloat, } +impl CGSize { + pub fn new(size: LogicalSize) -> CGSize { + CGSize { + width: size.width as _, + height: size.height as _, + } + } +} + #[repr(C)] #[derive(Debug, Clone)] pub struct CGRect { @@ -46,6 +58,12 @@ pub struct CGRect { pub size: CGSize, } +impl CGRect { + pub fn new(origin: CGPoint, size: CGSize) -> CGRect { + CGRect { origin, size } + } +} + unsafe impl Encode for CGRect { fn encode() -> Encoding { unsafe { diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index ee455eaef8..71ad739083 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -219,7 +219,7 @@ impl Inner { pub fn size(&self) -> PhysicalSize { unsafe { let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds]; - (bounds.size.width as f64, bounds.size.height as f64).into() + PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) } } diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 622b351bd7..e31a99ed65 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -10,7 +10,7 @@ use crate::{ platform::ios::MonitorHandleExtIOS, platform_impl::platform::{ app_state::{self, OSCapabilities}, - event_loop, + event_loop::{self, EventProxy, EventWrapper}, ffi::{ id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, UIRectEdge, UITouchPhase, UITouchType, @@ -103,8 +103,8 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { let window: id = msg_send![object, window]; assert!(!window.is_null()); app_state::handle_nonuser_events( - std::iter::once(Event::RedrawRequested(RootWindowId(window.into()))) - .chain(std::iter::once(Event::RedrawEventsCleared)), + std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))) + .chain(std::iter::once(EventWrapper::StaticEvent(Event::RedrawEventsCleared))), ); let superclass: &'static Class = msg_send![object, superclass]; let () = msg_send![super(object, superclass), drawRect: rect]; @@ -123,14 +123,16 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { let screen_space: id = msg_send![screen, coordinateSpace]; let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; + let dpi_factor: CGFloat = msg_send![screen, scale]; let size = crate::dpi::LogicalSize { width: screen_frame.size.width as _, height: screen_frame.size.height as _, - }; - app_state::handle_nonuser_event(Event::WindowEvent { + } + .to_physical(dpi_factor.into()); + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::Resized(size), - }); + })); } } @@ -156,14 +158,15 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { // `setContentScaleFactor` may be called with a value of 0, which means "reset the // content scale factor to a device-specific default value", so we can't use the // parameter here. We can query the actual factor using the getter - let hidpi_factor: CGFloat = msg_send![object, contentScaleFactor]; + let dpi_factor: CGFloat = msg_send![object, contentScaleFactor]; assert!( - !hidpi_factor.is_nan() - && hidpi_factor.is_finite() - && hidpi_factor.is_sign_positive() - && hidpi_factor > 0.0, + !dpi_factor.is_nan() + && dpi_factor.is_finite() + && dpi_factor.is_sign_positive() + && dpi_factor > 0.0, "invalid hidpi_factor set on UIView", ); + let hidpi_factor: f64 = dpi_factor.into(); let bounds: CGRect = msg_send![object, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; @@ -174,14 +177,19 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { height: screen_frame.size.height as _, }; app_state::handle_nonuser_events( - std::iter::once(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _), - }) - .chain(std::iter::once(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Resized(size), - })), + std::iter::once(EventWrapper::EventProxy( + EventProxy::HiDpiFactorChangedProxy { + window_id: window, + hidpi_factor, + suggested_size: size, + }, + )) + .chain(std::iter::once(EventWrapper::StaticEvent( + Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Resized(size.to_physical(hidpi_factor)), + }, + ))), ); } } @@ -238,7 +246,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { _ => panic!("unexpected touch phase: {:?}", phase as i32), }; - touch_events.push(Event::WindowEvent { + touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::Touch(Touch { device_id: RootDeviceId(DeviceId { uiscreen }), @@ -247,7 +255,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { force, phase, }), - }); + })); } app_state::handle_nonuser_events(touch_events); } @@ -367,20 +375,20 @@ unsafe fn get_window_class() -> &'static Class { extern "C" fn become_key_window(object: &Object, _: Sel) { unsafe { - app_state::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(object.into()), event: WindowEvent::Focused(true), - }); + })); let () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow]; } } extern "C" fn resign_key_window(object: &Object, _: Sel) { unsafe { - app_state::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(object.into()), event: WindowEvent::Focused(false), - }); + })); let () = msg_send![super(object, class!(UIWindow)), resignKeyWindow]; } } @@ -500,9 +508,7 @@ pub unsafe fn create_window( let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; msg_send![window, setScreen:video_mode.monitor().ui_screen()] } - Some(Fullscreen::Borderless(ref monitor)) => { - msg_send![window, setScreen:monitor.ui_screen()] - } + Some(Fullscreen::Borderless(ref monitor)) => msg_send![window, setScreen:monitor.ui_screen()], None => (), } @@ -518,11 +524,11 @@ pub fn create_delegate_class() { } extern "C" fn did_become_active(_: &Object, _: Sel, _: id) { - unsafe { app_state::handle_nonuser_event(Event::Resumed) } + unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) } } extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) { - unsafe { app_state::handle_nonuser_event(Event::Suspended) } + unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) } } extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {} @@ -541,10 +547,10 @@ pub fn create_delegate_class() { } let is_winit_window: BOOL = msg_send![window, isKindOfClass: class!(WinitUIWindow)]; if is_winit_window == YES { - events.push(Event::WindowEvent { + events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::Destroyed, - }); + })); } } app_state::handle_nonuser_events(events); diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index d1ad03f55f..f24e61c0d7 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -7,14 +7,15 @@ use std::{ use objc::runtime::{Class, Object, BOOL, NO, YES}; use crate::{ - dpi::{self, LogicalPosition, LogicalSize}, + dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::{Event, WindowEvent}, icon::Icon, monitor::MonitorHandle as RootMonitorHandle, platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations}, platform_impl::platform::{ - app_state, event_loop, + app_state, + event_loop::{self, EventProxy, EventWrapper}, ffi::{ id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, UIRectEdge, UIScreenOverscanCompensation, @@ -75,28 +76,34 @@ impl Inner { } } - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result { unsafe { let safe_area = self.safe_area_screen_space(); - Ok(LogicalPosition { + let position = LogicalPosition { x: safe_area.origin.x as _, y: safe_area.origin.y as _, - }) + }; + let dpi_factor = self.hidpi_factor(); + Ok(position.to_physical(dpi_factor)) } } - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result { unsafe { let screen_frame = self.screen_frame(); - Ok(LogicalPosition { + let position = LogicalPosition { x: screen_frame.origin.x as _, y: screen_frame.origin.y as _, - }) + }; + let dpi_factor = self.hidpi_factor(); + Ok(position.to_physical(dpi_factor)) } } - pub fn set_outer_position(&self, position: LogicalPosition) { + pub fn set_outer_position(&self, physical_position: Position) { unsafe { + let dpi_factor = self.hidpi_factor(); + let position = physical_position.to_logical(dpi_factor); let screen_frame = self.screen_frame(); let new_screen_frame = CGRect { origin: CGPoint { @@ -110,35 +117,39 @@ impl Inner { } } - pub fn inner_size(&self) -> LogicalSize { + pub fn inner_size(&self) -> PhysicalSize { unsafe { + let dpi_factor = self.hidpi_factor(); let safe_area = self.safe_area_screen_space(); - LogicalSize { + let size = LogicalSize { width: safe_area.size.width as _, height: safe_area.size.height as _, - } + }; + size.to_physical(dpi_factor) } } - pub fn outer_size(&self) -> LogicalSize { + pub fn outer_size(&self) -> PhysicalSize { unsafe { + let dpi_factor = self.hidpi_factor(); let screen_frame = self.screen_frame(); - LogicalSize { + let size = LogicalSize { width: screen_frame.size.width as _, height: screen_frame.size.height as _, - } + }; + size.to_physical(dpi_factor) } } - pub fn set_inner_size(&self, _size: LogicalSize) { + pub fn set_inner_size(&self, _size: Size) { unimplemented!("not clear what `Window::set_inner_size` means on iOS"); } - pub fn set_min_inner_size(&self, _dimensions: Option) { + pub fn set_min_inner_size(&self, _dimensions: Option) { warn!("`Window::set_min_inner_size` is ignored on iOS") } - pub fn set_max_inner_size(&self, _dimensions: Option) { + pub fn set_max_inner_size(&self, _dimensions: Option) { warn!("`Window::set_max_inner_size` is ignored on iOS") } @@ -157,7 +168,7 @@ impl Inner { debug!("`Window::set_cursor_icon` ignored on iOS") } - pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> { + pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } @@ -243,7 +254,7 @@ impl Inner { warn!("`Window::set_window_icon` is ignored on iOS") } - pub fn set_ime_position(&self, _position: LogicalPosition) { + pub fn set_ime_position(&self, _position: Position) { warn!("`Window::set_ime_position` is ignored on iOS") } @@ -343,13 +354,17 @@ impl Window { let screen_bounds: CGRect = msg_send![screen, bounds]; let frame = match window_attributes.inner_size { - Some(dim) => CGRect { - origin: screen_bounds.origin, - size: CGSize { - width: dim.width as _, - height: dim.height as _, - }, - }, + Some(dim) => { + let dpi_factor = msg_send![screen, scale]; + let size = dim.to_logical(dpi_factor); + CGRect { + origin: screen_bounds.origin, + size: CGSize { + width: size.width as _, + height: size.height as _, + }, + } + } None => screen_bounds, }; @@ -385,7 +400,8 @@ impl Window { // Like the Windows and macOS backends, we send a `HiDpiFactorChanged` and `Resized` // event on window creation if the DPI factor != 1.0 - let hidpi_factor: CGFloat = msg_send![view, contentScaleFactor]; + let dpi_factor: CGFloat = msg_send![view, contentScaleFactor]; + let hidpi_factor: f64 = dpi_factor.into(); if hidpi_factor != 1.0 { let bounds: CGRect = msg_send![view, bounds]; let screen: id = msg_send![window, screen]; @@ -397,14 +413,19 @@ impl Window { height: screen_frame.size.height as _, }; app_state::handle_nonuser_events( - std::iter::once(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _), - }) - .chain(std::iter::once(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Resized(size), - })), + std::iter::once(EventWrapper::EventProxy( + EventProxy::HiDpiFactorChangedProxy { + window_id: window, + hidpi_factor, + suggested_size: size, + }, + )) + .chain(std::iter::once(EventWrapper::StaticEvent( + Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Resized(size.to_physical(hidpi_factor)), + }, + ))), ); } From 3a1e694c2f8326f4baca937a5fec2e083f15a0ae Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 4 Jan 2020 01:33:07 -0500 Subject: [PATCH 053/239] Make size/position types generic over pixel type (#1277) * Begin implementing DPI generics * Fix multithreaded example * Format * Fix serde test * hopefully fix most of the errors * Fix dpi module errors * More error fixings * Format * fix macos errors * Another error pass * Replace bad type signatures * more fixins --- examples/multithreaded.rs | 13 +- src/dpi.rs | 306 ++++++++++-------- src/event.rs | 12 +- src/monitor.rs | 6 +- src/platform/macos.rs | 4 +- src/platform/unix.rs | 8 +- src/platform_impl/android/mod.rs | 31 +- src/platform_impl/ios/app_state.rs | 6 +- src/platform_impl/ios/event_loop.rs | 2 +- src/platform_impl/ios/ffi.rs | 2 +- src/platform_impl/ios/monitor.rs | 10 +- src/platform_impl/ios/view.rs | 16 +- src/platform_impl/ios/window.rs | 28 +- src/platform_impl/linux/mod.rs | 14 +- src/platform_impl/linux/wayland/event_loop.rs | 14 +- src/platform_impl/linux/wayland/window.rs | 24 +- .../linux/x11/event_processor.rs | 8 +- src/platform_impl/linux/x11/monitor.rs | 6 +- src/platform_impl/linux/x11/util/geometry.rs | 8 +- src/platform_impl/linux/x11/window.rs | 34 +- src/platform_impl/macos/app_state.rs | 2 +- src/platform_impl/macos/event.rs | 2 +- src/platform_impl/macos/monitor.rs | 14 +- src/platform_impl/macos/util/async.rs | 6 +- src/platform_impl/macos/window.rs | 28 +- src/platform_impl/macos/window_delegate.rs | 2 +- src/platform_impl/web/monitor.rs | 6 +- src/platform_impl/web/stdweb/canvas.rs | 2 +- src/platform_impl/web/stdweb/event.rs | 2 +- src/platform_impl/web/stdweb/mod.rs | 2 +- src/platform_impl/web/web_sys/canvas.rs | 2 +- src/platform_impl/web/web_sys/event.rs | 2 +- src/platform_impl/web/web_sys/mod.rs | 2 +- src/platform_impl/web/window.rs | 25 +- src/platform_impl/windows/event_loop.rs | 10 +- src/platform_impl/windows/monitor.rs | 10 +- src/platform_impl/windows/util.rs | 2 +- src/platform_impl/windows/window.rs | 18 +- src/window.rs | 8 +- tests/serde_objects.rs | 9 +- 40 files changed, 379 insertions(+), 327 deletions(-) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 388a4af5dd..c91ce5a593 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -12,7 +12,7 @@ fn main() { }; const WINDOW_COUNT: usize = 3; - const WINDOW_SIZE: PhysicalSize = PhysicalSize::new(600, 400); + const WINDOW_SIZE: PhysicalSize = PhysicalSize::new(600, 400); env_logger::init(); let event_loop = EventLoop::new(); @@ -108,9 +108,9 @@ fn main() { M => window.set_maximized(state), P => window.set_outer_position({ let mut position = window.outer_position().unwrap(); - let sign = if state { 1.0 } else { -1.0 }; - position.x += 10.0 * sign; - position.y += 10.0 * sign; + let sign = if state { 1 } else { -1 }; + position.x += 10 * sign; + position.y += 10 * sign; position }), Q => window.request_redraw(), @@ -126,10 +126,7 @@ fn main() { if let Size::Physical(size) = WINDOW_SIZE.into() { window .set_cursor_position(Position::Physical( - PhysicalPosition::new( - size.width as f64 / 2.0, - size.height as f64 / 2.0, - ), + PhysicalPosition::new(size.width / 2, size.height / 2), )) .unwrap() } diff --git a/src/dpi.rs b/src/dpi.rs index 4fc89e528d..a1a0491cbd 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -76,6 +76,54 @@ //! If you never received any [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events, //! then your window's DPI factor is 1. +pub trait Pixel: Copy + Into { + fn from_f64(f: f64) -> Self; + fn cast(self) -> P { + P::from_f64(self.into()) + } +} + +impl Pixel for u8 { + fn from_f64(f: f64) -> Self { + f.round() as u8 + } +} +impl Pixel for u16 { + fn from_f64(f: f64) -> Self { + f.round() as u16 + } +} +impl Pixel for u32 { + fn from_f64(f: f64) -> Self { + f.round() as u32 + } +} +impl Pixel for i8 { + fn from_f64(f: f64) -> Self { + f.round() as i8 + } +} +impl Pixel for i16 { + fn from_f64(f: f64) -> Self { + f.round() as i16 + } +} +impl Pixel for i32 { + fn from_f64(f: f64) -> Self { + f.round() as i32 + } +} +impl Pixel for f32 { + fn from_f64(f: f64) -> Self { + f as f32 + } +} +impl Pixel for f64 { + fn from_f64(f: f64) -> Self { + f + } +} + /// Checks that the DPI factor is a normal positive `f64`. /// /// All functions that take a DPI factor assert that this will return `true`. If you're sourcing DPI factors from @@ -93,57 +141,53 @@ pub fn validate_hidpi_factor(dpi_factor: f64) -> bool { /// does the rounding for you. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct LogicalPosition { - pub x: f64, - pub y: f64, +pub struct LogicalPosition

{ + pub x: P, + pub y: P, } -impl LogicalPosition { +impl

LogicalPosition

{ #[inline] - pub const fn new(x: f64, y: f64) -> Self { + pub const fn new(x: P, y: P) -> Self { LogicalPosition { x, y } } +} +impl LogicalPosition

{ #[inline] - pub fn from_physical>(physical: T, dpi_factor: f64) -> Self { + pub fn from_physical>, X: Pixel>( + physical: T, + dpi_factor: f64, + ) -> Self { physical.into().to_logical(dpi_factor) } #[inline] - pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition { + pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition { assert!(validate_hidpi_factor(dpi_factor)); - let x = self.x * dpi_factor; - let y = self.y * dpi_factor; - PhysicalPosition::new(x, y) + let x = self.x.into() * dpi_factor; + let y = self.y.into() * dpi_factor; + PhysicalPosition::new(x, y).cast() } -} - -impl From<(f64, f64)> for LogicalPosition { - #[inline] - fn from((x, y): (f64, f64)) -> Self { - Self::new(x, y) - } -} -impl From<(i32, i32)> for LogicalPosition { #[inline] - fn from((x, y): (i32, i32)) -> Self { - Self::new(x as f64, y as f64) + pub fn cast(&self) -> LogicalPosition { + LogicalPosition { + x: self.x.cast(), + y: self.y.cast(), + } } } -impl Into<(f64, f64)> for LogicalPosition { - #[inline] - fn into(self) -> (f64, f64) { - (self.x, self.y) +impl From<(X, X)> for LogicalPosition

{ + fn from((x, y): (X, X)) -> LogicalPosition

{ + LogicalPosition::new(x.cast(), y.cast()) } } -impl Into<(i32, i32)> for LogicalPosition { - /// Note that this rounds instead of truncating. - #[inline] - fn into(self) -> (i32, i32) { - (self.x.round() as _, self.y.round() as _) +impl Into<(X, X)> for LogicalPosition

{ + fn into(self: Self) -> (X, X) { + (self.x.cast(), self.y.cast()) } } @@ -154,57 +198,53 @@ impl Into<(i32, i32)> for LogicalPosition { /// does the rounding for you. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct PhysicalPosition { - pub x: f64, - pub y: f64, +pub struct PhysicalPosition

{ + pub x: P, + pub y: P, } -impl PhysicalPosition { +impl

PhysicalPosition

{ #[inline] - pub const fn new(x: f64, y: f64) -> Self { + pub const fn new(x: P, y: P) -> Self { PhysicalPosition { x, y } } +} +impl PhysicalPosition

{ #[inline] - pub fn from_logical>(logical: T, dpi_factor: f64) -> Self { + pub fn from_logical>, X: Pixel>( + logical: T, + dpi_factor: f64, + ) -> Self { logical.into().to_physical(dpi_factor) } #[inline] - pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition { + pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition { assert!(validate_hidpi_factor(dpi_factor)); - let x = self.x / dpi_factor; - let y = self.y / dpi_factor; - LogicalPosition::new(x, y) - } -} - -impl From<(f64, f64)> for PhysicalPosition { - #[inline] - fn from((x, y): (f64, f64)) -> Self { - Self::new(x, y) + let x = self.x.into() / dpi_factor; + let y = self.y.into() / dpi_factor; + LogicalPosition::new(x, y).cast() } -} -impl From<(i32, i32)> for PhysicalPosition { #[inline] - fn from((x, y): (i32, i32)) -> Self { - Self::new(x as f64, y as f64) + pub fn cast(&self) -> PhysicalPosition { + PhysicalPosition { + x: self.x.cast(), + y: self.y.cast(), + } } } -impl Into<(f64, f64)> for PhysicalPosition { - #[inline] - fn into(self) -> (f64, f64) { - (self.x, self.y) +impl From<(X, X)> for PhysicalPosition

{ + fn from((x, y): (X, X)) -> PhysicalPosition

{ + PhysicalPosition::new(x.cast(), y.cast()) } } -impl Into<(i32, i32)> for PhysicalPosition { - /// Note that this rounds instead of truncating. - #[inline] - fn into(self) -> (i32, i32) { - (self.x.round() as _, self.y.round() as _) +impl Into<(X, X)> for PhysicalPosition

{ + fn into(self: Self) -> (X, X) { + (self.x.cast(), self.y.cast()) } } @@ -215,108 +255,108 @@ impl Into<(i32, i32)> for PhysicalPosition { /// does the rounding for you. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct LogicalSize { - pub width: f64, - pub height: f64, +pub struct LogicalSize

{ + pub width: P, + pub height: P, } -impl LogicalSize { +impl

LogicalSize

{ #[inline] - pub const fn new(width: f64, height: f64) -> Self { + pub const fn new(width: P, height: P) -> Self { LogicalSize { width, height } } +} +impl LogicalSize

{ #[inline] - pub fn from_physical>(physical: T, dpi_factor: f64) -> Self { + pub fn from_physical>, X: Pixel>(physical: T, dpi_factor: f64) -> Self { physical.into().to_logical(dpi_factor) } #[inline] - pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize { + pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize { assert!(validate_hidpi_factor(dpi_factor)); - let width = self.width * dpi_factor; - let height = self.height * dpi_factor; - PhysicalSize::new(width.round() as _, height.round() as _) + let width = self.width.into() * dpi_factor; + let height = self.height.into() * dpi_factor; + PhysicalSize::new(width, height).cast() } -} -impl From<(f64, f64)> for LogicalSize { #[inline] - fn from((width, height): (f64, f64)) -> Self { - Self::new(width, height) - } -} - -impl From<(u32, u32)> for LogicalSize { - #[inline] - fn from((width, height): (u32, u32)) -> Self { - Self::new(width as f64, height as f64) + pub fn cast(&self) -> LogicalSize { + LogicalSize { + width: self.width.cast(), + height: self.height.cast(), + } } } -impl Into<(f64, f64)> for LogicalSize { - #[inline] - fn into(self) -> (f64, f64) { - (self.width, self.height) +impl From<(X, X)> for LogicalSize

{ + fn from((x, y): (X, X)) -> LogicalSize

{ + LogicalSize::new(x.cast(), y.cast()) } } -impl Into<(u32, u32)> for LogicalSize { - /// Note that this rounds instead of truncating. - #[inline] - fn into(self) -> (u32, u32) { - (self.width.round() as _, self.height.round() as _) +impl Into<(X, X)> for LogicalSize

{ + fn into(self: LogicalSize

) -> (X, X) { + (self.width.cast(), self.height.cast()) } } /// A size represented in physical pixels. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct PhysicalSize { - pub width: u32, - pub height: u32, +pub struct PhysicalSize

{ + pub width: P, + pub height: P, } -impl PhysicalSize { +impl

PhysicalSize

{ #[inline] - pub const fn new(width: u32, height: u32) -> Self { + pub const fn new(width: P, height: P) -> Self { PhysicalSize { width, height } } +} +impl PhysicalSize

{ #[inline] - pub fn from_logical>(logical: T, dpi_factor: f64) -> Self { + pub fn from_logical>, X: Pixel>(logical: T, dpi_factor: f64) -> Self { logical.into().to_physical(dpi_factor) } #[inline] - pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize { + pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize { assert!(validate_hidpi_factor(dpi_factor)); - let width = self.width as f64 / dpi_factor; - let height = self.height as f64 / dpi_factor; - LogicalSize::new(width, height) + let width = self.width.into() / dpi_factor; + let height = self.height.into() / dpi_factor; + LogicalSize::new(width, height).cast() } -} -impl From<(u32, u32)> for PhysicalSize { #[inline] - fn from((width, height): (u32, u32)) -> Self { - Self::new(width, height) + pub fn cast(&self) -> PhysicalSize { + PhysicalSize { + width: self.width.cast(), + height: self.height.cast(), + } } } -impl Into<(u32, u32)> for PhysicalSize { - /// Note that this rounds instead of truncating. - #[inline] - fn into(self) -> (u32, u32) { - (self.width, self.height) +impl From<(X, X)> for PhysicalSize

{ + fn from((x, y): (X, X)) -> PhysicalSize

{ + PhysicalSize::new(x.cast(), y.cast()) + } +} + +impl Into<(X, X)> for PhysicalSize

{ + fn into(self: Self) -> (X, X) { + (self.width.cast(), self.height.cast()) } } #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Size { - Physical(PhysicalSize), - Logical(LogicalSize), + Physical(PhysicalSize), + Logical(LogicalSize), } impl Size { @@ -324,40 +364,40 @@ impl Size { size.into() } - pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize { + pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize

{ match *self { Size::Physical(size) => size.to_logical(dpi_factor), - Size::Logical(size) => size, + Size::Logical(size) => size.cast(), } } - pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize { + pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize

{ match *self { - Size::Physical(size) => size, + Size::Physical(size) => size.cast(), Size::Logical(size) => size.to_physical(dpi_factor), } } } -impl From for Size { +impl From> for Size { #[inline] - fn from(size: PhysicalSize) -> Size { - Size::Physical(size) + fn from(size: PhysicalSize

) -> Size { + Size::Physical(size.cast()) } } -impl From for Size { +impl From> for Size { #[inline] - fn from(size: LogicalSize) -> Size { - Size::Logical(size) + fn from(size: LogicalSize

) -> Size { + Size::Logical(size.cast()) } } #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Position { - Physical(PhysicalPosition), - Logical(LogicalPosition), + Physical(PhysicalPosition), + Logical(LogicalPosition), } impl Position { @@ -365,31 +405,31 @@ impl Position { position.into() } - pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition { + pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition

{ match *self { Position::Physical(position) => position.to_logical(dpi_factor), - Position::Logical(position) => position, + Position::Logical(position) => position.cast(), } } - pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition { + pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition

{ match *self { - Position::Physical(position) => position, + Position::Physical(position) => position.cast(), Position::Logical(position) => position.to_physical(dpi_factor), } } } -impl From for Position { +impl From> for Position { #[inline] - fn from(position: PhysicalPosition) -> Position { - Position::Physical(position) + fn from(position: PhysicalPosition

) -> Position { + Position::Physical(position.cast()) } } -impl From for Position { +impl From> for Position { #[inline] - fn from(position: LogicalPosition) -> Position { - Position::Logical(position) + fn from(position: LogicalPosition

) -> Position { + Position::Logical(position.cast()) } } diff --git a/src/event.rs b/src/event.rs index a139f41533..87f6943c38 100644 --- a/src/event.rs +++ b/src/event.rs @@ -182,10 +182,10 @@ pub enum StartCause { #[derive(Debug, PartialEq)] pub enum WindowEvent<'a> { /// The size of the window has changed. Contains the client area's new dimensions. - Resized(PhysicalSize), + Resized(PhysicalSize), /// The position of the window has changed. Contains the window's new position. - Moved(PhysicalPosition), + Moved(PhysicalPosition), /// The window has been requested to close. CloseRequested, @@ -242,7 +242,7 @@ pub enum WindowEvent<'a> { /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. - position: PhysicalPosition, + position: PhysicalPosition, #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -308,7 +308,7 @@ pub enum WindowEvent<'a> { /// For more information about DPI in general, see the [`dpi`](dpi/index.html) module. HiDpiFactorChanged { hidpi_factor: f64, - new_inner_size: &'a mut Option, + new_inner_size: &'a mut Option>, }, /// The system window theme has changed. @@ -534,7 +534,7 @@ pub enum TouchPhase { pub struct Touch { pub device_id: DeviceId, pub phase: TouchPhase, - pub location: PhysicalPosition, + pub location: PhysicalPosition, /// Describes how hard the screen was pressed. May be `None` if the platform /// does not support pressure sensitivity. /// @@ -645,7 +645,7 @@ pub enum MouseScrollDelta { /// Scroll events are expressed as a PixelDelta if /// supported by the device (eg. a touchpad) and /// platform. - PixelDelta(LogicalPosition), + PixelDelta(LogicalPosition), } /// Symbolic name for a keyboard key. diff --git a/src/monitor.rs b/src/monitor.rs index 417596c379..c9d6e061ce 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -58,7 +58,7 @@ impl Ord for VideoMode { impl VideoMode { /// Returns the resolution of this video mode. #[inline] - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { self.video_mode.size() } @@ -133,7 +133,7 @@ impl MonitorHandle { /// /// - **Web:** Always returns (0,0) #[inline] - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { self.inner.size() } @@ -144,7 +144,7 @@ impl MonitorHandle { /// /// - **Web:** Always returns (0,0) #[inline] - pub fn position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { self.inner.position() } diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 8101f0f7c3..cfedaab23f 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -128,7 +128,7 @@ pub trait WindowBuilderExtMacOS { /// Makes the window content appear behind the titlebar. fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder; /// Build window with `resizeIncrements` property. Values must not be 0. - fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; + fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder; } @@ -179,7 +179,7 @@ impl WindowBuilderExtMacOS for WindowBuilder { } #[inline] - fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder { + fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder { self.platform_specific.resize_increments = Some(increments.into()); self } diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 61921e1d82..fbf8a920a7 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -380,9 +380,9 @@ pub trait WindowBuilderExtUnix { /// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11. fn with_gtk_theme_variant(self, variant: String) -> Self; /// Build window with resize increment hint. Only implemented on X11. - fn with_resize_increments(self, increments: LogicalSize) -> Self; + fn with_resize_increments(self, increments: LogicalSize) -> Self; /// Build window with base size hint. Only implemented on X11. - fn with_base_size(self, base_size: LogicalSize) -> Self; + fn with_base_size(self, base_size: LogicalSize) -> Self; /// Build window with a given application ID. It should match the `.desktop` file distributed with /// your program. Only relevant on Wayland. @@ -431,13 +431,13 @@ impl WindowBuilderExtUnix for WindowBuilder { } #[inline] - fn with_resize_increments(mut self, increments: LogicalSize) -> Self { + fn with_resize_increments(mut self, increments: LogicalSize) -> Self { self.platform_specific.resize_increments = Some(increments.into()); self } #[inline] - fn with_base_size(mut self, base_size: LogicalSize) -> Self { + fn with_base_size(mut self, base_size: LogicalSize) -> Self { self.platform_specific.base_size = Some(base_size.into()); self } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 4304e25482..11a140de6b 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -193,8 +193,8 @@ impl fmt::Debug for MonitorHandle { #[derive(Debug)] struct MonitorHandle { name: Option, - dimensions: PhysicalSize, - position: PhysicalPosition, + dimensions: PhysicalSize, + position: PhysicalPosition, hidpi_factor: f64, } @@ -216,7 +216,7 @@ impl MonitorHandle { } #[inline] - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { unsafe { let window = android_glue::native_window(); ( @@ -228,7 +228,7 @@ impl MonitorHandle { } #[inline] - pub fn outer_position(&self) -> PhysicalPosition { + pub fn outer_position(&self) -> PhysicalPosition { // Android assumes single screen (0, 0).into() } @@ -283,29 +283,29 @@ impl Window { } #[inline] - pub fn outer_position(&self) -> Option { + pub fn outer_position(&self) -> Option> { // N/A None } #[inline] - pub fn inner_position(&self) -> Option { + pub fn inner_position(&self) -> Option> { // N/A None } #[inline] - pub fn set_outer_position(&self, _position: LogicalPosition) { + pub fn set_outer_position(&self, _position: LogicalPosition) { // N/A } #[inline] - pub fn set_min_inner_size(&self, _dimensions: Option) { + pub fn set_min_inner_size(&self, _dimensions: Option>) { // N/A } #[inline] - pub fn set_max_inner_size(&self, _dimensions: Option) { + pub fn set_max_inner_size(&self, _dimensions: Option>) { // N/A } @@ -315,7 +315,7 @@ impl Window { } #[inline] - pub fn inner_size(&self) -> Option { + pub fn inner_size(&self) -> Option> { if self.native_window.is_null() { None } else { @@ -326,12 +326,12 @@ impl Window { } #[inline] - pub fn outer_size(&self) -> Option { + pub fn outer_size(&self) -> Option> { self.inner_size() } #[inline] - pub fn set_inner_size(&self, _size: LogicalSize) { + pub fn set_inner_size(&self, _size: LogicalSize) { // N/A } @@ -356,7 +356,10 @@ impl Window { } #[inline] - pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> { + pub fn set_cursor_position( + &self, + _position: LogicalPosition, + ) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } @@ -400,7 +403,7 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, _spot: LogicalPosition) { + pub fn set_ime_position(&self, _spot: LogicalPosition) { // N/A } diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 45b995e3a7..e295091640 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -806,7 +806,9 @@ pub unsafe fn handle_main_events_cleared() { let mut redraw_events: Vec> = this .main_events_cleared_transition() .into_iter() - .map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))) + .map(|window| { + EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into()))) + }) .collect(); if !redraw_events.is_empty() { @@ -855,7 +857,7 @@ fn handle_event_proxy( fn handle_hidpi_proxy( event_handler: &mut Box, mut control_flow: ControlFlow, - suggested_size: LogicalSize, + suggested_size: LogicalSize, hidpi_factor: f64, window_id: id, ) { diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 869005191c..b2987298d0 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -39,7 +39,7 @@ pub enum EventWrapper { pub enum EventProxy { HiDpiFactorChangedProxy { window_id: id, - suggested_size: LogicalSize, + suggested_size: LogicalSize, hidpi_factor: f64, }, } diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index c7cac81622..1c464b066a 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -43,7 +43,7 @@ pub struct CGSize { } impl CGSize { - pub fn new(size: LogicalSize) -> CGSize { + pub fn new(size: LogicalSize) -> CGSize { CGSize { width: size.width as _, height: size.height as _, diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index 71ad739083..e265954a69 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -88,7 +88,7 @@ impl VideoMode { } } - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { self.size.into() } @@ -171,8 +171,8 @@ impl fmt::Debug for MonitorHandle { #[derive(Debug)] struct MonitorHandle { name: Option, - size: PhysicalSize, - position: PhysicalPosition, + size: PhysicalSize, + position: PhysicalPosition, hidpi_factor: f64, } @@ -216,14 +216,14 @@ impl Inner { } } - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { unsafe { let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds]; PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) } } - pub fn position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { unsafe { let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds]; (bounds.origin.x as f64, bounds.origin.y as f64).into() diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index e31a99ed65..a2419e9d05 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -103,8 +103,12 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { let window: id = msg_send![object, window]; assert!(!window.is_null()); app_state::handle_nonuser_events( - std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))) - .chain(std::iter::once(EventWrapper::StaticEvent(Event::RedrawEventsCleared))), + std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested( + RootWindowId(window.into()), + ))) + .chain(std::iter::once(EventWrapper::StaticEvent( + Event::RedrawEventsCleared, + ))), ); let superclass: &'static Class = msg_send![object, superclass]; let () = msg_send![super(object, superclass), drawRect: rect]; @@ -125,8 +129,8 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; let dpi_factor: CGFloat = msg_send![screen, scale]; let size = crate::dpi::LogicalSize { - width: screen_frame.size.width as _, - height: screen_frame.size.height as _, + width: screen_frame.size.width as f64, + height: screen_frame.size.height as f64, } .to_physical(dpi_factor.into()); app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { @@ -508,7 +512,9 @@ pub unsafe fn create_window( let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0]; msg_send![window, setScreen:video_mode.monitor().ui_screen()] } - Some(Fullscreen::Borderless(ref monitor)) => msg_send![window, setScreen:monitor.ui_screen()], + Some(Fullscreen::Borderless(ref monitor)) => { + msg_send![window, setScreen:monitor.ui_screen()] + } None => (), } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index f24e61c0d7..0fec4f75c9 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -76,24 +76,24 @@ impl Inner { } } - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result, NotSupportedError> { unsafe { let safe_area = self.safe_area_screen_space(); let position = LogicalPosition { - x: safe_area.origin.x as _, - y: safe_area.origin.y as _, + x: safe_area.origin.x as f64, + y: safe_area.origin.y as f64, }; let dpi_factor = self.hidpi_factor(); Ok(position.to_physical(dpi_factor)) } } - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result, NotSupportedError> { unsafe { let screen_frame = self.screen_frame(); let position = LogicalPosition { - x: screen_frame.origin.x as _, - y: screen_frame.origin.y as _, + x: screen_frame.origin.x as f64, + y: screen_frame.origin.y as f64, }; let dpi_factor = self.hidpi_factor(); Ok(position.to_physical(dpi_factor)) @@ -103,7 +103,7 @@ impl Inner { pub fn set_outer_position(&self, physical_position: Position) { unsafe { let dpi_factor = self.hidpi_factor(); - let position = physical_position.to_logical(dpi_factor); + let position = physical_position.to_logical::(dpi_factor); let screen_frame = self.screen_frame(); let new_screen_frame = CGRect { origin: CGPoint { @@ -117,25 +117,25 @@ impl Inner { } } - pub fn inner_size(&self) -> PhysicalSize { + pub fn inner_size(&self) -> PhysicalSize { unsafe { let dpi_factor = self.hidpi_factor(); let safe_area = self.safe_area_screen_space(); let size = LogicalSize { - width: safe_area.size.width as _, - height: safe_area.size.height as _, + width: safe_area.size.width as f64, + height: safe_area.size.height as f64, }; size.to_physical(dpi_factor) } } - pub fn outer_size(&self) -> PhysicalSize { + pub fn outer_size(&self) -> PhysicalSize { unsafe { let dpi_factor = self.hidpi_factor(); let screen_frame = self.screen_frame(); let size = LogicalSize { - width: screen_frame.size.width as _, - height: screen_frame.size.height as _, + width: screen_frame.size.width as f64, + height: screen_frame.size.height as f64, }; size.to_physical(dpi_factor) } @@ -356,7 +356,7 @@ impl Window { let frame = match window_attributes.inner_size { Some(dim) => { let dpi_factor = msg_send![screen, scale]; - let size = dim.to_logical(dpi_factor); + let size = dim.to_logical::(dpi_factor); CGRect { origin: screen_bounds.origin, size: CGSize { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 350e05f06e..cd7ed4a704 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -132,7 +132,7 @@ impl MonitorHandle { } #[inline] - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { match self { &MonitorHandle::X(ref m) => m.size(), &MonitorHandle::Wayland(ref m) => m.size(), @@ -140,7 +140,7 @@ impl MonitorHandle { } #[inline] - pub fn position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { match self { &MonitorHandle::X(ref m) => m.position(), &MonitorHandle::Wayland(ref m) => m.position(), @@ -172,7 +172,7 @@ pub enum VideoMode { impl VideoMode { #[inline] - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { match self { &VideoMode::X(ref m) => m.size(), &VideoMode::Wayland(ref m) => m.size(), @@ -246,7 +246,7 @@ impl Window { } #[inline] - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result, NotSupportedError> { match self { &Window::X(ref w) => w.outer_position(), &Window::Wayland(ref w) => w.outer_position(), @@ -254,7 +254,7 @@ impl Window { } #[inline] - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result, NotSupportedError> { match self { &Window::X(ref m) => m.inner_position(), &Window::Wayland(ref m) => m.inner_position(), @@ -270,7 +270,7 @@ impl Window { } #[inline] - pub fn inner_size(&self) -> PhysicalSize { + pub fn inner_size(&self) -> PhysicalSize { match self { &Window::X(ref w) => w.inner_size(), &Window::Wayland(ref w) => w.inner_size(), @@ -278,7 +278,7 @@ impl Window { } #[inline] - pub fn outer_size(&self) -> PhysicalSize { + pub fn outer_size(&self) -> PhysicalSize { match self { &Window::X(ref w) => w.outer_size(), &Window::Wayland(ref w) => w.outer_size(), diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index c538e77f8b..72d7815669 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -729,7 +729,7 @@ impl EventLoop { if let Some(dpi) = window.new_dpi { let dpi = dpi as f64; - let logical_size = LogicalSize::from(*window.size); + let logical_size = LogicalSize::::from(*window.size); let mut new_inner_size = Some(logical_size.to_physical(dpi)); callback(Event::WindowEvent { @@ -741,7 +741,7 @@ impl EventLoop { }); if let Some(new_size) = new_inner_size { - let (w, h) = new_size.to_logical(dpi).into(); + let (w, h) = new_size.to_logical::(dpi).into(); frame.resize(w, h); *window.size = (w, h); } @@ -947,7 +947,7 @@ pub struct VideoMode { impl VideoMode { #[inline] - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { self.size.into() } @@ -1007,8 +1007,8 @@ impl fmt::Debug for MonitorHandle { struct MonitorHandle { name: Option, native_identifier: u32, - size: PhysicalSize, - position: PhysicalPosition, + size: PhysicalSize, + position: PhysicalPosition, hidpi_factor: i32, } @@ -1036,7 +1036,7 @@ impl MonitorHandle { self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0) } - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { match self.mgr.with_info(&self.proxy, |_, info| { info.modes .iter() @@ -1049,7 +1049,7 @@ impl MonitorHandle { .into() } - pub fn position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { self.mgr .with_info(&self.proxy, |_, info| info.location) .unwrap_or((0, 0)) diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 2c509bbfa7..be2d0df938 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -60,7 +60,7 @@ impl Window { let dpi = get_dpi_factor(&surface) as f64; let (width, height) = attributes .inner_size - .map(|size| size.to_logical(dpi).into()) + .map(|size| size.to_logical::(dpi).into()) .unwrap_or((800, 600)); // Create the window @@ -147,12 +147,12 @@ impl Window { frame.set_min_size( attributes .min_inner_size - .map(|size| size.to_logical(dpi).into()), + .map(|size| size.to_logical::(dpi).into()), ); frame.set_max_size( attributes .max_inner_size - .map(|size| size.to_logical(dpi).into()), + .map(|size| size.to_logical::(dpi).into()), ); let kill_switch = Arc::new(Mutex::new(false)); @@ -206,12 +206,12 @@ impl Window { } #[inline] - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result, NotSupportedError> { Err(NotSupportedError::new()) } #[inline] - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result, NotSupportedError> { Err(NotSupportedError::new()) } @@ -220,9 +220,9 @@ impl Window { // Not possible with wayland } - pub fn inner_size(&self) -> PhysicalSize { + pub fn inner_size(&self) -> PhysicalSize { let dpi = self.hidpi_factor() as f64; - let size = LogicalSize::from(*self.size.lock().unwrap()); + let size = LogicalSize::::from(*self.size.lock().unwrap()); size.to_physical(dpi) } @@ -231,11 +231,11 @@ impl Window { } #[inline] - pub fn outer_size(&self) -> PhysicalSize { + pub fn outer_size(&self) -> PhysicalSize { let dpi = self.hidpi_factor() as f64; let (w, h) = self.size.lock().unwrap().clone(); // let (w, h) = super::wayland_window::add_borders(w as i32, h as i32); - let size = LogicalSize::from((w, h)); + let size = LogicalSize::::from((w, h)); size.to_physical(dpi) } @@ -243,7 +243,7 @@ impl Window { // NOTE: This will only resize the borders, the contents must be updated by the user pub fn set_inner_size(&self, size: Size) { let dpi = self.hidpi_factor() as f64; - let (w, h) = size.to_logical(dpi).into(); + let (w, h) = size.to_logical::(dpi).into(); self.frame.lock().unwrap().resize(w, h); *(self.size.lock().unwrap()) = (w, h); } @@ -254,7 +254,7 @@ impl Window { self.frame .lock() .unwrap() - .set_min_size(dimensions.map(|dim| dim.to_logical(dpi).into())); + .set_min_size(dimensions.map(|dim| dim.to_logical::(dpi).into())); } #[inline] @@ -263,7 +263,7 @@ impl Window { self.frame .lock() .unwrap() - .set_max_size(dimensions.map(|dim| dim.to_logical(dpi).into())); + .set_max_size(dimensions.map(|dim| dim.to_logical::(dpi).into())); } #[inline] diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 884a7ea149..33a5e14902 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -707,7 +707,7 @@ impl EventProcessor { }); if cursor_moved == Some(true) { let position = - PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); + PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32); callback(Event::WindowEvent { window_id, @@ -814,7 +814,7 @@ impl EventProcessor { }); let position = - PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); + PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32); // The mods field on this event isn't actually populated, so query the // pointer device. In the future, we can likely remove this round-trip by @@ -883,7 +883,7 @@ impl EventProcessor { .unwrap_or(2); let position = - PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); + PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32); callback(Event::WindowEvent { window_id, @@ -941,7 +941,7 @@ impl EventProcessor { window_id, event: WindowEvent::CursorMoved { device_id: mkdid(util::VIRTUAL_CORE_POINTER), - position: location, + position: location.cast(), modifiers, }, }); diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 2612643e2a..98c8022b40 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -38,7 +38,7 @@ pub struct VideoMode { impl VideoMode { #[inline] - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { self.size.into() } @@ -157,11 +157,11 @@ impl MonitorHandle { self.id as u32 } - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { self.dimensions.into() } - pub fn position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { self.position.into() } diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs index 6b59d13a2a..149418e320 100644 --- a/src/platform_impl/linux/x11/util/geometry.rs +++ b/src/platform_impl/linux/x11/util/geometry.rs @@ -137,9 +137,9 @@ impl FrameExtentsHeuristic { pub fn inner_pos_to_outer_logical( &self, - mut logical: LogicalPosition, + mut logical: LogicalPosition, factor: f64, - ) -> LogicalPosition { + ) -> LogicalPosition { use self::FrameExtentsHeuristicPath::*; if self.heuristic_path != UnsupportedBordered { let frame_extents = self.frame_extents.as_logical(factor); @@ -166,9 +166,9 @@ impl FrameExtentsHeuristic { pub fn inner_size_to_outer_logical( &self, - mut logical: LogicalSize, + mut logical: LogicalSize, factor: f64, - ) -> LogicalSize { + ) -> LogicalSize { let frame_extents = self.frame_extents.as_logical(factor); logical.width += frame_extents.left + frame_extents.right; logical.height += frame_extents.top + frame_extents.bottom; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 0a72875b09..9fd7d89198 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -138,17 +138,17 @@ impl UnownedWindow { let max_inner_size: Option<(u32, u32)> = window_attrs .max_inner_size - .map(|size| size.to_physical(dpi_factor).into()); + .map(|size| size.to_physical::(dpi_factor).into()); let min_inner_size: Option<(u32, u32)> = window_attrs .min_inner_size - .map(|size| size.to_physical(dpi_factor).into()); + .map(|size| size.to_physical::(dpi_factor).into()); let dimensions = { // x11 only applies constraints when the window is actively resized // by the user, so we have to manually apply the initial constraints let mut dimensions: (u32, u32) = window_attrs .inner_size - .map(|size| size.to_physical(dpi_factor)) + .map(|size| size.to_physical::(dpi_factor)) .or_else(|| Some((800, 600).into())) .map(Into::into) .unwrap(); @@ -320,10 +320,10 @@ impl UnownedWindow { { let mut min_inner_size = window_attrs .min_inner_size - .map(|size| size.to_physical(dpi_factor)); + .map(|size| size.to_physical::(dpi_factor)); let mut max_inner_size = window_attrs .max_inner_size - .map(|size| size.to_physical(dpi_factor)); + .map(|size| size.to_physical::(dpi_factor)); if !window_attrs.resizable { if util::wm_name_is_one_of(&["Xfwm4"]) { warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4"); @@ -941,7 +941,7 @@ impl UnownedWindow { } #[inline] - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result, NotSupportedError> { let extents = (*self.shared_state.lock()).frame_extents.clone(); if let Some(extents) = extents { let (x, y) = self.inner_position_physical(); @@ -962,7 +962,7 @@ impl UnownedWindow { } #[inline] - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result, NotSupportedError> { Ok(self.inner_position_physical().into()) } @@ -993,7 +993,7 @@ impl UnownedWindow { #[inline] pub fn set_outer_position(&self, position: Position) { - let (x, y) = position.to_physical(self.hidpi_factor()).into(); + let (x, y) = position.to_physical::(self.hidpi_factor()).into(); self.set_position_physical(x, y); } @@ -1007,12 +1007,12 @@ impl UnownedWindow { } #[inline] - pub fn inner_size(&self) -> PhysicalSize { + pub fn inner_size(&self) -> PhysicalSize { self.inner_size_physical().into() } #[inline] - pub fn outer_size(&self) -> PhysicalSize { + pub fn outer_size(&self) -> PhysicalSize { let extents = self.shared_state.lock().frame_extents.clone(); if let Some(extents) = extents { let (width, height) = self.inner_size_physical(); @@ -1039,7 +1039,7 @@ impl UnownedWindow { #[inline] pub fn set_inner_size(&self, size: Size) { let dpi_factor = self.hidpi_factor(); - let (width, height) = size.to_physical(dpi_factor).into(); + let (width, height) = size.to_physical::(dpi_factor).into(); self.set_inner_size_physical(width, height); } @@ -1063,7 +1063,7 @@ impl UnownedWindow { pub fn set_min_inner_size(&self, dimensions: Option) { self.shared_state.lock().min_inner_size = dimensions; let physical_dimensions = - dimensions.map(|dimensions| dimensions.to_physical(self.hidpi_factor()).into()); + dimensions.map(|dimensions| dimensions.to_physical::(self.hidpi_factor()).into()); self.set_min_inner_size_physical(physical_dimensions); } @@ -1076,7 +1076,7 @@ impl UnownedWindow { pub fn set_max_inner_size(&self, dimensions: Option) { self.shared_state.lock().max_inner_size = dimensions; let physical_dimensions = - dimensions.map(|dimensions| dimensions.to_physical(self.hidpi_factor()).into()); + dimensions.map(|dimensions| dimensions.to_physical::(self.hidpi_factor()).into()); self.set_max_inner_size_physical(physical_dimensions); } @@ -1135,10 +1135,10 @@ impl UnownedWindow { let dpi_factor = self.hidpi_factor(); let min_inner_size = min_size - .map(|size| size.to_physical(dpi_factor)) + .map(|size| size.to_physical::(dpi_factor)) .map(Into::into); let max_inner_size = max_size - .map(|size| size.to_physical(dpi_factor)) + .map(|size| size.to_physical::(dpi_factor)) .map(Into::into); self.update_normal_hints(|normal_hints| { normal_hints.set_min_size(min_inner_size); @@ -1274,7 +1274,7 @@ impl UnownedWindow { #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - let (x, y) = position.to_physical(self.hidpi_factor()).into(); + let (x, y) = position.to_physical::(self.hidpi_factor()).into(); self.set_cursor_position_physical(x, y) } @@ -1287,7 +1287,7 @@ impl UnownedWindow { #[inline] pub fn set_ime_position(&self, spot: Position) { - let (x, y) = spot.to_physical(self.hidpi_factor()).into(); + let (x, y) = spot.to_physical::(self.hidpi_factor()).into(); self.set_ime_position_physical(x, y); } diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 618be00233..7d6b92b2fc 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -185,7 +185,7 @@ impl Handler { &self, callback: &mut Box, ns_window: IdRef, - suggested_size: LogicalSize, + suggested_size: LogicalSize, hidpi_factor: f64, ) { let size = suggested_size.to_physical(hidpi_factor); diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index bbf111645b..ef33ba234f 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -24,7 +24,7 @@ pub enum EventWrapper { pub enum EventProxy { HiDpiFactorChangedProxy { ns_window: IdRef, - suggested_size: LogicalSize, + suggested_size: LogicalSize, hidpi_factor: f64, }, } diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 976f6c9048..11a0aa2b5a 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -84,7 +84,7 @@ impl Clone for NativeDisplayMode { } impl VideoMode { - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { self.size.into() } @@ -166,8 +166,8 @@ impl fmt::Debug for MonitorHandle { struct MonitorHandle { name: Option, native_identifier: u32, - size: PhysicalSize, - position: PhysicalPosition, + size: PhysicalSize, + position: PhysicalPosition, hidpi_factor: f64, } @@ -199,18 +199,18 @@ impl MonitorHandle { self.0 } - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { let MonitorHandle(display_id) = *self; let display = CGDisplay::new(display_id); let height = display.pixels_high(); let width = display.pixels_wide(); - PhysicalSize::from_logical((width as f64, height as f64), self.hidpi_factor()) + PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.hidpi_factor()) } #[inline] - pub fn position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { let bounds = unsafe { CGDisplayBounds(self.native_identifier()) }; - PhysicalPosition::from_logical( + PhysicalPosition::from_logical::<_, f64>( (bounds.origin.x as f64, bounds.origin.y as f64), self.hidpi_factor(), ) diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index c2d117b61c..8aa8db9a27 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -73,10 +73,10 @@ pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyl struct SetContentSizeData { ns_window: id, - size: LogicalSize, + size: LogicalSize, } impl SetContentSizeData { - fn new_ptr(ns_window: id, size: LogicalSize) -> *mut Self { + fn new_ptr(ns_window: id, size: LogicalSize) -> *mut Self { Box::into_raw(Box::new(SetContentSizeData { ns_window, size })) } } @@ -98,7 +98,7 @@ extern "C" fn set_content_size_callback(context: *mut c_void) { } // `setContentSize:` isn't thread-safe either, though it doesn't log any errors // and just fails silently. Anyway, GCD to the rescue! -pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize) { +pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize) { let context = SetContentSizeData::new_ptr(ns_window, size); dispatch_async_f( dispatch_get_main_queue(), diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 4ed62756be..5c3547a439 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -68,7 +68,7 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub titlebar_hidden: bool, pub titlebar_buttons_hidden: bool, pub fullsize_content_view: bool, - pub resize_increments: Option, + pub resize_increments: Option>, pub disallow_hidpi: bool, } @@ -299,7 +299,7 @@ pub struct UnownedWindow { pub shared_state: Arc>, decorations: AtomicBool, cursor_state: Weak>, - pub inner_rect: Option, + pub inner_rect: Option>, } unsafe impl Send for UnownedWindow {} @@ -440,7 +440,7 @@ impl UnownedWindow { AppState::queue_redraw(RootWindowId(self.id())); } - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result, NotSupportedError> { let frame_rect = unsafe { NSWindow::frame(*self.ns_window) }; let position = LogicalPosition::new( frame_rect.origin.x as f64, @@ -450,7 +450,7 @@ impl UnownedWindow { Ok(position.to_physical(dpi_factor)) } - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result, NotSupportedError> { let content_rect = unsafe { NSWindow::contentRectForFrameRect_(*self.ns_window, NSWindow::frame(*self.ns_window)) }; @@ -480,18 +480,18 @@ impl UnownedWindow { } #[inline] - pub fn inner_size(&self) -> PhysicalSize { + pub fn inner_size(&self) -> PhysicalSize { let view_frame = unsafe { NSView::frame(*self.ns_view) }; - let logical: LogicalSize = + let logical: LogicalSize = (view_frame.size.width as f64, view_frame.size.height as f64).into(); let dpi_factor = self.hidpi_factor(); logical.to_physical(dpi_factor) } #[inline] - pub fn outer_size(&self) -> PhysicalSize { + pub fn outer_size(&self) -> PhysicalSize { let view_frame = unsafe { NSWindow::frame(*self.ns_window) }; - let logical: LogicalSize = + let logical: LogicalSize = (view_frame.size.width as f64, view_frame.size.height as f64).into(); let dpi_factor = self.hidpi_factor(); logical.to_physical(dpi_factor) @@ -591,11 +591,11 @@ impl UnownedWindow { pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> { let physical_window_position = self.inner_position().unwrap(); let dpi_factor = self.hidpi_factor(); - let window_position = physical_window_position.to_logical(dpi_factor); - let logical_cursor_position = cursor_position.to_logical(dpi_factor); + let window_position = physical_window_position.to_logical::(dpi_factor); + let logical_cursor_position = cursor_position.to_logical::(dpi_factor); let point = appkit::CGPoint { - x: (logical_cursor_position.x + window_position.x) as CGFloat, - y: (logical_cursor_position.y + window_position.y) as CGFloat, + x: logical_cursor_position.x + window_position.x, + y: logical_cursor_position.y + window_position.y, }; CGDisplay::warp_mouse_cursor_position(point) .map_err(|e| ExternalError::Os(os_error!(OsError::CGError(e))))?; @@ -1095,7 +1095,7 @@ impl Drop for UnownedWindow { } } -unsafe fn set_min_inner_size(window: V, mut min_size: LogicalSize) { +unsafe fn set_min_inner_size(window: V, mut min_size: LogicalSize) { let mut current_rect = NSWindow::frame(window); let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); // Convert from client area size to window size @@ -1119,7 +1119,7 @@ unsafe fn set_min_inner_size(window: V, mut min_size: Logica } } -unsafe fn set_max_inner_size(window: V, mut max_size: LogicalSize) { +unsafe fn set_max_inner_size(window: V, mut max_size: LogicalSize) { let mut current_rect = NSWindow::frame(window); let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); // Convert from client area size to window size diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index d4ac37b54d..6ffe88c3a4 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -118,7 +118,7 @@ impl WindowDelegateState { (unsafe { NSWindow::backingScaleFactor(*self.ns_window) }) as f64 } - fn view_size(&self) -> LogicalSize { + fn view_size(&self) -> LogicalSize { let ns_size = unsafe { NSView::frame(*self.ns_view).size }; LogicalSize::new(ns_size.width as f64, ns_size.height as f64) } diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs index 4889c88db9..9442d3b87f 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -9,7 +9,7 @@ impl Handle { 1.0 } - pub fn position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { PhysicalPosition { x: 0.0, y: 0.0 } } @@ -17,7 +17,7 @@ impl Handle { None } - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { PhysicalSize { width: 0.0, height: 0.0, @@ -33,7 +33,7 @@ impl Handle { pub struct Mode; impl Mode { - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { unimplemented!(); } diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index a0ff5af2e8..07ba42b54e 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -96,7 +96,7 @@ impl Canvas { self.raw.height() as f64 } - pub fn set_size(&self, size: LogicalSize) { + pub fn set_size(&self, size: LogicalSize) { self.raw.set_width(size.width as u32); self.raw.set_height(size.height as u32); } diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index 223ef69325..8c534dc053 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -23,7 +23,7 @@ pub fn mouse_modifiers(event: &impl IMouseEvent) -> ModifiersState { m } -pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition { +pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition { LogicalPosition { x: event.offset_x() as f64, y: event.offset_y() as f64, diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index d49dbf02ac..0ac4b1725c 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -33,7 +33,7 @@ impl WindowExtStdweb for Window { } } -pub fn window_size() -> LogicalSize { +pub fn window_size() -> LogicalSize { let window = window(); let width = window.inner_width() as f64; let height = window.inner_height() as f64; diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 0543055eb6..c21b8cbd66 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -94,7 +94,7 @@ impl Canvas { self.raw.height() as f64 } - pub fn set_size(&self, size: LogicalSize) { + pub fn set_size(&self, size: LogicalSize) { self.raw.set_width(size.width as u32); self.raw.set_height(size.height as u32); } diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 9a11f3bfa1..1c1bdba41c 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -22,7 +22,7 @@ pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState { m } -pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { +pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { LogicalPosition { x: event.offset_x() as f64, y: event.offset_y() as f64, diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 205519d1ea..4424a03bcb 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -40,7 +40,7 @@ impl WindowExtWebSys for Window { } } -pub fn window_size() -> LogicalSize { +pub fn window_size() -> LogicalSize { let window = web_sys::window().expect("Failed to obtain window"); let width = window .inner_width() diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index c14df77f4b..d63086aab1 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -15,7 +15,7 @@ use std::collections::VecDeque; pub struct Window { canvas: backend::Canvas, previous_pointer: RefCell<&'static str>, - position: RefCell, + position: RefCell>, id: Id, register_redraw_request: Box, } @@ -72,17 +72,17 @@ impl Window { (self.register_redraw_request)(); } - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result, NotSupportedError> { let (x, y) = self.canvas.position(); Ok(LogicalPosition { x, y }) } - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result, NotSupportedError> { Ok(*self.position.borrow()) } - pub fn set_outer_position(&self, position: LogicalPosition) { + pub fn set_outer_position(&self, position: LogicalPosition) { *self.position.borrow_mut() = position; self.canvas.set_attribute("position", "fixed"); @@ -91,7 +91,7 @@ impl Window { } #[inline] - pub fn inner_size(&self) -> LogicalSize { + pub fn inner_size(&self) -> LogicalSize { LogicalSize { width: self.canvas.width() as f64, height: self.canvas.height() as f64, @@ -99,7 +99,7 @@ impl Window { } #[inline] - pub fn outer_size(&self) -> LogicalSize { + pub fn outer_size(&self) -> LogicalSize { LogicalSize { width: self.canvas.width() as f64, height: self.canvas.height() as f64, @@ -107,17 +107,17 @@ impl Window { } #[inline] - pub fn set_inner_size(&self, size: LogicalSize) { + pub fn set_inner_size(&self, size: LogicalSize) { self.canvas.set_size(size); } #[inline] - pub fn set_min_inner_size(&self, _dimensions: Option) { + pub fn set_min_inner_size(&self, _dimensions: Option>) { // Intentionally a no-op: users can't resize canvas elements } #[inline] - pub fn set_max_inner_size(&self, _dimensions: Option) { + pub fn set_max_inner_size(&self, _dimensions: Option>) { // Intentionally a no-op: users can't resize canvas elements } @@ -178,7 +178,10 @@ impl Window { } #[inline] - pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> { + pub fn set_cursor_position( + &self, + _position: LogicalPosition, + ) -> Result<(), ExternalError> { // Intentionally a no-op, as the web does not support setting cursor positions Ok(()) } @@ -243,7 +246,7 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, _position: LogicalPosition) { + pub fn set_ime_position(&self, _position: LogicalPosition) { // Currently a no-op as it does not seem there is good support for this on web } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 0568aa8c94..7783c6790b 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -707,7 +707,7 @@ unsafe extern "system" fn public_window_callback( let windowpos = lparam as *const winuser::WINDOWPOS; if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE { let physical_position = - PhysicalPosition::new((*windowpos).x as f64, (*windowpos).y as f64); + PhysicalPosition::new((*windowpos).x as u32, (*windowpos).y as u32); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Moved(physical_position), @@ -829,8 +829,8 @@ unsafe extern "system" fn public_window_callback( }); } - let x = windowsx::GET_X_LPARAM(lparam) as f64; - let y = windowsx::GET_Y_LPARAM(lparam) as f64; + let x = windowsx::GET_X_LPARAM(lparam) as i32; + let y = windowsx::GET_Y_LPARAM(lparam) as i32; let position = PhysicalPosition::new(x, y); subclass_input.send_event(Event::WindowEvent { @@ -1489,8 +1489,8 @@ unsafe extern "system" fn public_window_callback( // We calculate our own size because the default suggested rect doesn't do a great job // of preserving the window's logical size. let suggested_physical_inner_size = old_physical_inner_size - .to_logical(old_dpi_factor) - .to_physical(new_dpi_factor); + .to_logical::(old_dpi_factor) + .to_physical::(new_dpi_factor); // `allow_resize` prevents us from re-applying DPI adjustment to the restored size after // exiting fullscreen (the restored size is already DPI adjusted). diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index e87a48c600..0b506dc3ba 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -62,7 +62,7 @@ impl std::fmt::Debug for VideoMode { } impl VideoMode { - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { self.size.into() } @@ -185,7 +185,7 @@ impl MonitorHandle { } #[inline] - pub fn size(&self) -> PhysicalSize { + pub fn size(&self) -> PhysicalSize { let monitor_info = get_monitor_info(self.0).unwrap(); PhysicalSize { width: (monitor_info.rcMonitor.right - monitor_info.rcMonitor.left) as u32, @@ -194,11 +194,11 @@ impl MonitorHandle { } #[inline] - pub fn position(&self) -> PhysicalPosition { + pub fn position(&self) -> PhysicalPosition { let monitor_info = get_monitor_info(self.0).unwrap(); PhysicalPosition { - x: monitor_info.rcMonitor.left as f64, - y: monitor_info.rcMonitor.top as f64, + x: monitor_info.rcMonitor.left, + y: monitor_info.rcMonitor.top, } } diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 36fc225368..1b9845038a 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -76,7 +76,7 @@ pub fn get_client_rect(hwnd: HWND) -> Result { } } -pub fn adjust_size(hwnd: HWND, size: PhysicalSize) -> PhysicalSize { +pub fn adjust_size(hwnd: HWND, size: PhysicalSize) -> PhysicalSize { let (width, height): (u32, u32) = size.into(); let rect = RECT { left: 0, diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 481ccdccb8..8a89c44ecf 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -147,24 +147,24 @@ impl Window { } #[inline] - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result, NotSupportedError> { util::get_window_rect(self.window.0) - .map(|rect| Ok(PhysicalPosition::new(rect.left as f64, rect.top as f64))) + .map(|rect| Ok(PhysicalPosition::new(rect.left as i32, rect.top as i32))) .expect("Unexpected GetWindowRect failure; please report this error to https://github.com/rust-windowing/winit") } #[inline] - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result, NotSupportedError> { let mut position: POINT = unsafe { mem::zeroed() }; if unsafe { winuser::ClientToScreen(self.window.0, &mut position) } == 0 { panic!("Unexpected ClientToScreen failure: please report this error to https://github.com/rust-windowing/winit") } - Ok(PhysicalPosition::new(position.x as f64, position.y as f64)) + Ok(PhysicalPosition::new(position.x as i32, position.y as i32)) } #[inline] pub fn set_outer_position(&self, position: Position) { - let (x, y): (i32, i32) = position.to_physical(self.hidpi_factor()).into(); + let (x, y): (i32, i32) = position.to_physical::(self.hidpi_factor()).into(); let window_state = Arc::clone(&self.window_state); let window = self.window.clone(); @@ -192,7 +192,7 @@ impl Window { } #[inline] - pub fn inner_size(&self) -> PhysicalSize { + pub fn inner_size(&self) -> PhysicalSize { let mut rect: RECT = unsafe { mem::zeroed() }; if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 { panic!("Unexpected GetClientRect failure: please report this error to https://github.com/rust-windowing/winit") @@ -204,7 +204,7 @@ impl Window { } #[inline] - pub fn outer_size(&self) -> PhysicalSize { + pub fn outer_size(&self) -> PhysicalSize { util::get_window_rect(self.window.0) .map(|rect| { PhysicalSize::new( @@ -250,7 +250,7 @@ impl Window { #[inline] pub fn set_inner_size(&self, size: Size) { let dpi_factor = self.hidpi_factor(); - let (width, height) = size.to_physical(dpi_factor).into(); + let (width, height) = size.to_physical::(dpi_factor).into(); let window_state = Arc::clone(&self.window_state); let window = self.window.clone(); @@ -363,7 +363,7 @@ impl Window { #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { let dpi_factor = self.hidpi_factor(); - let (x, y) = position.to_physical(dpi_factor).into(); + let (x, y) = position.to_physical::(dpi_factor).into(); let mut point = POINT { x, y }; unsafe { diff --git a/src/window.rs b/src/window.rs index b97b9c6a58..b961f41030 100644 --- a/src/window.rs +++ b/src/window.rs @@ -422,7 +422,7 @@ impl Window { /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] - pub fn inner_position(&self) -> Result { + pub fn inner_position(&self) -> Result, NotSupportedError> { self.window.inner_position() } @@ -441,7 +441,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window in the screen space coordinate system. #[inline] - pub fn outer_position(&self) -> Result { + pub fn outer_position(&self) -> Result, NotSupportedError> { self.window.outer_position() } @@ -470,7 +470,7 @@ impl Window { /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] - pub fn inner_size(&self) -> PhysicalSize { + pub fn inner_size(&self) -> PhysicalSize { self.window.inner_size() } @@ -498,7 +498,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window in /// screen space coordinates. #[inline] - pub fn outer_size(&self) -> PhysicalSize { + pub fn outer_size(&self) -> PhysicalSize { self.window.outer_size() } diff --git a/tests/serde_objects.rs b/tests/serde_objects.rs index df51898861..ad729dcd1b 100644 --- a/tests/serde_objects.rs +++ b/tests/serde_objects.rs @@ -31,8 +31,9 @@ fn events_serde() { #[test] fn dpi_serde() { - needs_serde::(); - needs_serde::(); - needs_serde::(); - needs_serde::(); + needs_serde::>(); + needs_serde::>(); + needs_serde::>(); + needs_serde::>(); + needs_serde::>(); } From 28a20aec102bd33339a10a7047e1ac18f4c8d514 Mon Sep 17 00:00:00 2001 From: Antonino Siena Date: Tue, 26 Nov 2019 23:56:47 +0100 Subject: [PATCH 054/239] Dpi Type conversions into/from arrays (#1283) * Added array conversion methods * Cargo fmt * Undo wrong fmt --- src/dpi.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/dpi.rs b/src/dpi.rs index a1a0491cbd..664b22cb6d 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -191,6 +191,18 @@ impl Into<(X, X)> for LogicalPosition

{ } } +impl From<[X; 2]> for LogicalPosition

{ + fn from([x, y]: [X; 2]) -> LogicalPosition

{ + LogicalPosition::new(x.cast(), y.cast()) + } +} + +impl Into<[X; 2]> for LogicalPosition

{ + fn into(self: Self) -> [X; 2] { + [self.x.cast(), self.y.cast()] + } +} + /// A position represented in physical pixels. /// /// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, @@ -248,6 +260,18 @@ impl Into<(X, X)> for PhysicalPosition

{ } } +impl From<[X; 2]> for PhysicalPosition

{ + fn from([x, y]: [X; 2]) -> PhysicalPosition

{ + PhysicalPosition::new(x.cast(), y.cast()) + } +} + +impl Into<[X; 2]> for PhysicalPosition

{ + fn into(self: Self) -> [X; 2] { + [self.x.cast(), self.y.cast()] + } +} + /// A size represented in logical pixels. /// /// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, @@ -302,6 +326,18 @@ impl Into<(X, X)> for LogicalSize

{ } } +impl From<[X; 2]> for LogicalSize

{ + fn from([x, y]: [X; 2]) -> LogicalSize

{ + LogicalSize::new(x.cast(), y.cast()) + } +} + +impl Into<[X; 2]> for LogicalSize

{ + fn into(self: Self) -> [X; 2] { + [self.width.cast(), self.height.cast()] + } +} + /// A size represented in physical pixels. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -352,6 +388,18 @@ impl Into<(X, X)> for PhysicalSize

{ } } +impl From<[X; 2]> for PhysicalSize

{ + fn from([x, y]: [X; 2]) -> PhysicalSize

{ + PhysicalSize::new(x.cast(), y.cast()) + } +} + +impl Into<[X; 2]> for PhysicalSize

{ + fn into(self: Self) -> [X; 2] { + [self.width.cast(), self.height.cast()] + } +} + #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Size { From 777d9edeaa9d1648c07e2b48d4019791adfc0fc3 Mon Sep 17 00:00:00 2001 From: Michael Tang Date: Tue, 31 Dec 2019 14:39:33 -0800 Subject: [PATCH 055/239] Implement hidpi for web platform (#1233) * fix: use a 'static lifetime for the web backend's `Event` types * implement hidpi for stdweb (web-sys wip?) * fix: make all canvas resizes go through backend::set_canvas_size * update Window docs for web, make `inner/outer_position` return the position in the viewport --- Cargo.toml | 1 + src/platform_impl/web/event_loop/mod.rs | 3 +- src/platform_impl/web/event_loop/runner.rs | 21 ++++--- .../web/event_loop/window_target.rs | 24 ++++---- src/platform_impl/web/monitor.rs | 6 +- src/platform_impl/web/stdweb/canvas.rs | 31 +++++------ src/platform_impl/web/stdweb/mod.rs | 24 +++++++- src/platform_impl/web/web_sys/canvas.rs | 30 +++++----- src/platform_impl/web/web_sys/mod.rs | 25 ++++++++- src/platform_impl/web/window.rs | 55 ++++++++----------- src/window.rs | 8 +++ 11 files changed, 137 insertions(+), 91 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2f5c9a1ff..5e198f5b00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ version = "0.3.22" optional = true features = [ 'console', + 'CssStyleDeclaration', 'BeforeUnloadEvent', 'Document', 'DomRect', diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index 7124cee7e1..50640a1faf 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -37,7 +37,8 @@ impl EventLoop { pub fn run(self, mut event_handler: F) -> ! where - F: 'static + FnMut(Event, &root::EventLoopWindowTarget, &mut root::ControlFlow), + F: 'static + + FnMut(Event<'static, T>, &root::EventLoopWindowTarget, &mut root::ControlFlow), { let target = root::EventLoopWindowTarget { p: self.elw.p.clone(), diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index da7d5bad27..b480054cb6 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -11,7 +11,7 @@ use std::{ rc::Rc, }; -pub struct Shared(Rc>); +pub struct Shared(Rc>); impl Clone for Shared { fn clone(&self) -> Self { @@ -19,21 +19,21 @@ impl Clone for Shared { } } -pub struct Execution { +pub struct Execution { runner: RefCell>>, - events: RefCell>>, + events: RefCell>>, id: RefCell, redraw_pending: RefCell>, } -struct Runner { +struct Runner { state: State, is_busy: bool, - event_handler: Box, &mut root::ControlFlow)>, + event_handler: Box, &mut root::ControlFlow)>, } impl Runner { - pub fn new(event_handler: Box, &mut root::ControlFlow)>) -> Self { + pub fn new(event_handler: Box, &mut root::ControlFlow)>) -> Self { Runner { state: State::Init, is_busy: false, @@ -55,7 +55,10 @@ impl Shared { // Set the event callback to use for the event loop runner // This the event callback is a fairly thin layer over the user-provided callback that closes // over a RootEventLoopWindowTarget reference - pub fn set_listener(&self, event_handler: Box, &mut root::ControlFlow)>) { + pub fn set_listener( + &self, + event_handler: Box, &mut root::ControlFlow)>, + ) { self.0.runner.replace(Some(Runner::new(event_handler))); self.send_event(Event::NewEvents(StartCause::Init)); @@ -79,7 +82,7 @@ impl Shared { // Add an event to the event loop runner // // It will determine if the event should be immediately sent to the user or buffered for later - pub fn send_event(&self, event: Event) { + pub fn send_event(&self, event: Event<'static, T>) { // If the event loop is closed, it should discard any new events if self.is_closed() { return; @@ -153,7 +156,7 @@ impl Shared { // handle_event takes in events and either queues them or applies a callback // // It should only ever be called from send_event - fn handle_event(&self, event: Event, control: &mut root::ControlFlow) { + fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) { let is_closed = self.is_closed(); match *self.0.runner.borrow_mut() { diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 2e0f32dbc2..abe7b8e3d6 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,5 +1,5 @@ use super::{backend, device, proxy::Proxy, runner, window}; -use crate::dpi::LogicalSize; +use crate::dpi::{PhysicalSize, Size}; use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; use crate::window::WindowId; @@ -28,7 +28,7 @@ impl WindowTarget { Proxy::new(self.runner.clone()) } - pub fn run(&self, event_handler: Box, &mut ControlFlow)>) { + pub fn run(&self, event_handler: Box, &mut ControlFlow)>) { self.runner.set_listener(event_handler); } @@ -170,25 +170,27 @@ impl WindowTarget { let runner = self.runner.clone(); let raw = canvas.raw().clone(); - let mut intended_size = LogicalSize { - width: raw.width() as f64, - height: raw.height() as f64, + + // The size to restore to after exiting fullscreen. + let mut intended_size = PhysicalSize { + width: raw.width() as u32, + height: raw.height() as u32, }; canvas.on_fullscreen_change(move || { // If the canvas is marked as fullscreen, it is moving *into* fullscreen // If it is not, it is moving *out of* fullscreen let new_size = if backend::is_fullscreen(&raw) { - intended_size = LogicalSize { - width: raw.width() as f64, - height: raw.height() as f64, + intended_size = PhysicalSize { + width: raw.width() as u32, + height: raw.height() as u32, }; - backend::window_size() + backend::window_size().to_physical(backend::hidpi_factor()) } else { intended_size }; - raw.set_width(new_size.width as u32); - raw.set_height(new_size.height as u32); + + backend::set_canvas_size(&raw, Size::Physical(new_size)); runner.send_event(Event::WindowEvent { window_id: WindowId(id), event: WindowEvent::Resized(new_size), diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs index 9442d3b87f..ac06d0cba6 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -10,7 +10,7 @@ impl Handle { } pub fn position(&self) -> PhysicalPosition { - PhysicalPosition { x: 0.0, y: 0.0 } + PhysicalPosition { x: 0, y: 0 } } pub fn name(&self) -> Option { @@ -19,8 +19,8 @@ impl Handle { pub fn size(&self) -> PhysicalSize { PhysicalSize { - width: 0.0, - height: 0.0, + width: 0, + height: 0, } } diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 07ba42b54e..99778aa6ae 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -1,5 +1,5 @@ use super::event; -use crate::dpi::{LogicalPosition, LogicalSize}; +use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use crate::platform_impl::OsError; @@ -19,6 +19,7 @@ use stdweb::web::{ }; pub struct Canvas { + /// Note: resizing the CanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. raw: CanvasElement, on_focus: Option, on_blur: Option, @@ -82,23 +83,20 @@ impl Canvas { .expect(&format!("Set attribute: {}", attribute)); } - pub fn position(&self) -> (f64, f64) { + pub fn position(&self) -> LogicalPosition { let bounds = self.raw.get_bounding_client_rect(); - (bounds.get_x(), bounds.get_y()) + LogicalPosition { + x: bounds.get_x(), + y: bounds.get_y(), + } } - pub fn width(&self) -> f64 { - self.raw.width() as f64 - } - - pub fn height(&self) -> f64 { - self.raw.height() as f64 - } - - pub fn set_size(&self, size: LogicalSize) { - self.raw.set_width(size.width as u32); - self.raw.set_height(size.height as u32); + pub fn size(&self) -> PhysicalSize { + PhysicalSize { + width: self.raw.width() as u32, + height: self.raw.height() as u32, + } } pub fn raw(&self) -> &CanvasElement { @@ -209,12 +207,13 @@ impl Canvas { pub fn on_cursor_move(&mut self, mut handler: F) where - F: 'static + FnMut(i32, LogicalPosition, ModifiersState), + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), { + // todo self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| { handler( event.pointer_id(), - event::mouse_position(&event), + event::mouse_position(&event).to_physical(super::hidpi_factor()), event::mouse_modifiers(&event), ); })); diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 0ac4b1725c..b87a1b9f66 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -5,7 +5,7 @@ mod timeout; pub use self::canvas::Canvas; pub use self::timeout::Timeout; -use crate::dpi::LogicalSize; +use crate::dpi::{LogicalSize, Size}; use crate::platform::web::WindowExtStdweb; use crate::window::Window; @@ -41,6 +41,28 @@ pub fn window_size() -> LogicalSize { LogicalSize { width, height } } +pub fn hidpi_factor() -> f64 { + let window = window(); + window.device_pixel_ratio() +} + +pub fn set_canvas_size(raw: &CanvasElement, size: Size) { + use stdweb::*; + + let hidpi_factor = hidpi_factor(); + + let physical_size = size.to_physical::(hidpi_factor); + let logical_size = size.to_logical::(hidpi_factor); + + raw.set_width(physical_size.width); + raw.set_height(physical_size.height); + + js! { + @{raw.as_ref()}.style.width = @{logical_size.width} + "px"; + @{raw.as_ref()}.style.height = @{logical_size.height} + "px"; + } +} + pub fn is_fullscreen(canvas: &CanvasElement) -> bool { match document().fullscreen_element() { Some(elem) => { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index c21b8cbd66..7d8d292a88 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,5 +1,5 @@ use super::event; -use crate::dpi::{LogicalPosition, LogicalSize}; +use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use crate::platform_impl::OsError; @@ -11,6 +11,7 @@ use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent}; pub struct Canvas { + /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. raw: HtmlCanvasElement, on_focus: Option>, on_blur: Option>, @@ -80,23 +81,20 @@ impl Canvas { .expect(&format!("Set attribute: {}", attribute)); } - pub fn position(&self) -> (f64, f64) { + pub fn position(&self) -> LogicalPosition { let bounds = self.raw.get_bounding_client_rect(); - (bounds.x(), bounds.y()) + LogicalPosition { + x: bounds.x(), + y: bounds.y(), + } } - pub fn width(&self) -> f64 { - self.raw.width() as f64 - } - - pub fn height(&self) -> f64 { - self.raw.height() as f64 - } - - pub fn set_size(&self, size: LogicalSize) { - self.raw.set_width(size.width as u32); - self.raw.set_height(size.height as u32); + pub fn size(&self) -> PhysicalSize { + PhysicalSize { + width: self.raw.width(), + height: self.raw.height(), + } } pub fn raw(&self) -> &HtmlCanvasElement { @@ -218,12 +216,12 @@ impl Canvas { pub fn on_cursor_move(&mut self, mut handler: F) where - F: 'static + FnMut(i32, LogicalPosition, ModifiersState), + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), { self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| { handler( event.pointer_id(), - event::mouse_position(&event), + event::mouse_position(&event).to_physical(super::hidpi_factor()), event::mouse_modifiers(&event), ); })); diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 4424a03bcb..bde099a735 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -5,7 +5,7 @@ mod timeout; pub use self::canvas::Canvas; pub use self::timeout::Timeout; -use crate::dpi::LogicalSize; +use crate::dpi::{LogicalSize, Size}; use crate::platform::web::WindowExtWebSys; use crate::window::Window; use wasm_bindgen::{closure::Closure, JsCast}; @@ -56,6 +56,29 @@ pub fn window_size() -> LogicalSize { LogicalSize { width, height } } +pub fn hidpi_factor() -> f64 { + let window = web_sys::window().expect("Failed to obtain window"); + window.device_pixel_ratio() +} + +pub fn set_canvas_size(raw: &HtmlCanvasElement, size: Size) { + let hidpi_factor = hidpi_factor(); + + let physical_size = size.to_physical::(hidpi_factor); + let logical_size = size.to_logical::(hidpi_factor); + + raw.set_width(physical_size.width); + raw.set_height(physical_size.height); + + let style = raw.style(); + style + .set_property("width", &format!("{}px", logical_size.width)) + .expect("Failed to set canvas width"); + style + .set_property("height", &format!("{}px", logical_size.height)) + .expect("Failed to set canvas height"); +} + pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool { let window = window().expect("Failed to obtain window"); let document = window.document().expect("Failed to obtain document"); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index d63086aab1..2d20178490 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -1,4 +1,4 @@ -use crate::dpi::{LogicalPosition, LogicalSize}; +use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::icon::Icon; use crate::monitor::MonitorHandle as RootMH; @@ -15,7 +15,6 @@ use std::collections::VecDeque; pub struct Window { canvas: backend::Canvas, previous_pointer: RefCell<&'static str>, - position: RefCell>, id: Id, register_redraw_request: Box, } @@ -39,15 +38,14 @@ impl Window { let window = Window { canvas, previous_pointer: RefCell::new("auto"), - position: RefCell::new(LogicalPosition { x: 0.0, y: 0.0 }), id, register_redraw_request, }; - window.set_inner_size(attr.inner_size.unwrap_or(LogicalSize { + window.set_inner_size(attr.inner_size.unwrap_or(Size::Logical(LogicalSize { width: 1024.0, height: 768.0, - })); + }))); window.set_title(&attr.title); window.set_maximized(attr.maximized); window.set_visible(attr.visible); @@ -72,18 +70,17 @@ impl Window { (self.register_redraw_request)(); } - pub fn outer_position(&self) -> Result, NotSupportedError> { - let (x, y) = self.canvas.position(); - - Ok(LogicalPosition { x, y }) + pub fn outer_position(&self) -> Result, NotSupportedError> { + Ok(self.canvas.position().to_physical(self.hidpi_factor())) } - pub fn inner_position(&self) -> Result, NotSupportedError> { - Ok(*self.position.borrow()) + pub fn inner_position(&self) -> Result, NotSupportedError> { + // Note: the canvas element has no window decorations, so this is equal to `outer_position`. + self.outer_position() } - pub fn set_outer_position(&self, position: LogicalPosition) { - *self.position.borrow_mut() = position; + pub fn set_outer_position(&self, position: Position) { + let position = position.to_logical::(self.hidpi_factor()); self.canvas.set_attribute("position", "fixed"); self.canvas.set_attribute("left", &position.x.to_string()); @@ -91,33 +88,28 @@ impl Window { } #[inline] - pub fn inner_size(&self) -> LogicalSize { - LogicalSize { - width: self.canvas.width() as f64, - height: self.canvas.height() as f64, - } + pub fn inner_size(&self) -> PhysicalSize { + self.canvas.size() } #[inline] - pub fn outer_size(&self) -> LogicalSize { - LogicalSize { - width: self.canvas.width() as f64, - height: self.canvas.height() as f64, - } + pub fn outer_size(&self) -> PhysicalSize { + // Note: the canvas element has no window decorations, so this is equal to `inner_size`. + self.inner_size() } #[inline] - pub fn set_inner_size(&self, size: LogicalSize) { - self.canvas.set_size(size); + pub fn set_inner_size(&self, size: Size) { + backend::set_canvas_size(self.canvas.raw(), size); } #[inline] - pub fn set_min_inner_size(&self, _dimensions: Option>) { + pub fn set_min_inner_size(&self, _dimensions: Option) { // Intentionally a no-op: users can't resize canvas elements } #[inline] - pub fn set_max_inner_size(&self, _dimensions: Option>) { + pub fn set_max_inner_size(&self, _dimensions: Option) { // Intentionally a no-op: users can't resize canvas elements } @@ -128,7 +120,7 @@ impl Window { #[inline] pub fn hidpi_factor(&self) -> f64 { - 1.0 + super::backend::hidpi_factor() } #[inline] @@ -178,10 +170,7 @@ impl Window { } #[inline] - pub fn set_cursor_position( - &self, - _position: LogicalPosition, - ) -> Result<(), ExternalError> { + pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { // Intentionally a no-op, as the web does not support setting cursor positions Ok(()) } @@ -246,7 +235,7 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, _position: LogicalPosition) { + pub fn set_ime_position(&self, _position: Position) { // Currently a no-op as it does not seem there is good support for this on web } diff --git a/src/window.rs b/src/window.rs index b961f41030..ae9c41c911 100644 --- a/src/window.rs +++ b/src/window.rs @@ -419,6 +419,8 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window's [safe area] in the screen space coordinate system. + /// - **Web:** Returns the top-left coordinates relative to the viewport. _Note: this returns the + /// same value as `outer_position`._ /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] @@ -440,6 +442,7 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window in the screen space coordinate system. + /// - **Web:** Returns the top-left coordinates relative to the viewport. #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { self.window.outer_position() @@ -454,6 +457,7 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the /// window in the screen space coordinate system. + /// - **Web:** Sets the top-left coordinates relative to the viewport. #[inline] pub fn set_outer_position>(&self, position: P) { self.window.set_outer_position(position.into()) @@ -467,6 +471,7 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window's /// [safe area] in screen space coordinates. + /// - **Web:** Returns the size of the canvas element. /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] @@ -483,6 +488,7 @@ impl Window { /// /// - **iOS:** Unimplemented. Currently this panics, as it's not clear what `set_inner_size` /// would mean for iOS. + /// - **Web:** Sets the size of the canvas element. #[inline] pub fn set_inner_size>(&self, size: S) { self.window.set_inner_size(size.into()) @@ -497,6 +503,8 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. Returns the `PhysicalSize` of the window in /// screen space coordinates. + /// - **Web:** Returns the size of the canvas element. _Note: this returns the same value as + /// `inner_size`._ #[inline] pub fn outer_size(&self) -> PhysicalSize { self.window.outer_size() From 55166da43747cdb9f1430fd46e909928b1f0eccc Mon Sep 17 00:00:00 2001 From: Osspial Date: Fri, 3 Jan 2020 00:28:41 -0500 Subject: [PATCH 056/239] Remove Option from HiDpiFactorChanged in favor of a bare PhysicalSize (#1346) * Remove Option from HiDpiFactorChanged in favor of a bare PhysicalSize * Fix macos and ios builds --- src/event.rs | 5 ++-- src/platform_impl/ios/app_state.rs | 17 +++++++------- src/platform_impl/linux/wayland/event_loop.rs | 10 ++++---- .../linux/x11/event_processor.rs | 23 +++++++++++-------- src/platform_impl/macos/app_state.rs | 6 ++--- src/platform_impl/windows/event_loop.rs | 20 ++++++++-------- 6 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/event.rs b/src/event.rs index 87f6943c38..12e58e746d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -302,13 +302,12 @@ pub enum WindowEvent<'a> { /// /// After this event callback has been processed, the window will be resized to whatever value /// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested - /// by the OS, but it can be changed to any value. If `new_inner_size` is set to `None`, no resizing - /// will occur. + /// by the OS, but it can be changed to any value. /// /// For more information about DPI in general, see the [`dpi`](dpi/index.html) module. HiDpiFactorChanged { hidpi_factor: f64, - new_inner_size: &'a mut Option>, + new_inner_size: &'a mut PhysicalSize, }, /// The system window theme has changed. diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index e295091640..229e17d7f9 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -861,8 +861,8 @@ fn handle_hidpi_proxy( hidpi_factor: f64, window_id: id, ) { - let size = suggested_size.to_physical(hidpi_factor); - let new_inner_size = &mut Some(size); + let mut size = suggested_size.to_physical(hidpi_factor); + let new_inner_size = &mut size; let event = Event::WindowEvent { window_id: RootWindowId(window_id.into()), event: WindowEvent::HiDpiFactorChanged { @@ -872,13 +872,12 @@ fn handle_hidpi_proxy( }; event_handler.handle_nonuser_event(event, &mut control_flow); let (view, screen_frame) = get_view_and_screen_frame(window_id); - if let Some(physical_size) = new_inner_size { - let logical_size = physical_size.to_logical(hidpi_factor); - let size = CGSize::new(logical_size); - let new_frame: CGRect = CGRect::new(screen_frame.origin, size); - unsafe { - let () = msg_send![view, setFrame: new_frame]; - } + let physical_size = *new_inner_size; + let logical_size = physical_size.to_logical(hidpi_factor); + let size = CGSize::new(logical_size); + let new_frame: CGRect = CGRect::new(screen_frame.origin, size); + unsafe { + let () = msg_send![view, setFrame: new_frame]; } } diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 72d7815669..4eded25344 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -730,7 +730,7 @@ impl EventLoop { if let Some(dpi) = window.new_dpi { let dpi = dpi as f64; let logical_size = LogicalSize::::from(*window.size); - let mut new_inner_size = Some(logical_size.to_physical(dpi)); + let mut new_inner_size = logical_size.to_physical(dpi); callback(Event::WindowEvent { window_id, @@ -740,11 +740,9 @@ impl EventLoop { }, }); - if let Some(new_size) = new_inner_size { - let (w, h) = new_size.to_logical::(dpi).into(); - frame.resize(w, h); - *window.size = (w, h); - } + let (w, h) = new_inner_size.to_logical::(dpi).into(); + frame.resize(w, h); + *window.size = (w, h); } } if window.closed { diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 33a5e14902..9ce17f273a 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -422,7 +422,8 @@ impl EventProcessor { height, ); - let mut new_inner_size = Some(PhysicalSize::new(new_width, new_height)); + let old_inner_size = PhysicalSize::new(width, height); + let mut new_inner_size = PhysicalSize::new(new_width, new_height); callback(Event::WindowEvent { window_id, @@ -432,9 +433,12 @@ impl EventProcessor { }, }); - if let Some(new_size) = new_inner_size { - window.set_inner_size_physical(new_size.width, new_size.height); - shared_state_lock.dpi_adjusted = Some(new_size.into()); + if new_inner_size != old_inner_size { + window.set_inner_size_physical( + new_inner_size.width, + new_inner_size.height, + ); + shared_state_lock.dpi_adjusted = Some(new_inner_size.into()); // if the DPI factor changed, force a resize event to ensure the logical // size is computed with the right DPI factor resized = true; @@ -1135,9 +1139,10 @@ impl EventProcessor { *window_id, ), ); - let mut new_inner_size = Some( - PhysicalSize::new(new_width, new_height), - ); + let old_inner_size = + PhysicalSize::new(width, height); + let mut new_inner_size = + PhysicalSize::new(new_width, new_height); callback(Event::WindowEvent { window_id, @@ -1147,9 +1152,9 @@ impl EventProcessor { }, }); - if let Some(new_size) = new_inner_size { + if new_inner_size != old_inner_size { let (new_width, new_height) = - new_size.into(); + new_inner_size.into(); window.set_inner_size_physical( new_width, new_height, ); diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 7d6b92b2fc..a60d68619d 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -188,8 +188,8 @@ impl Handler { suggested_size: LogicalSize, hidpi_factor: f64, ) { - let size = suggested_size.to_physical(hidpi_factor); - let new_inner_size = &mut Some(size); + let mut size = suggested_size.to_physical(hidpi_factor); + let new_inner_size = &mut size; let event = Event::WindowEvent { window_id: WindowId(get_window_id(*ns_window)), event: WindowEvent::HiDpiFactorChanged { @@ -200,7 +200,7 @@ impl Handler { callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap()); - let physical_size = new_inner_size.unwrap_or(size); + let physical_size = *new_inner_size; let logical_size = physical_size.to_logical(hidpi_factor); let size = NSSize::new(logical_size.width, logical_size.height); unsafe { NSWindow::setContentSize_(*ns_window, size) }; diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 7783c6790b..ebcabcef6a 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1486,27 +1486,25 @@ unsafe extern "system" fn public_window_callback( (old_physical_inner_rect.bottom - old_physical_inner_rect.top) as u32, ); - // We calculate our own size because the default suggested rect doesn't do a great job - // of preserving the window's logical size. - let suggested_physical_inner_size = old_physical_inner_size - .to_logical::(old_dpi_factor) - .to_physical::(new_dpi_factor); - // `allow_resize` prevents us from re-applying DPI adjustment to the restored size after // exiting fullscreen (the restored size is already DPI adjusted). - let mut new_inner_size_opt = - Some(suggested_physical_inner_size).filter(|_| allow_resize); + let mut new_physical_inner_size = match allow_resize { + // We calculate our own size because the default suggested rect doesn't do a great job + // of preserving the window's logical size. + true => old_physical_inner_size + .to_logical::(old_dpi_factor) + .to_physical::(new_dpi_factor), + false => old_physical_inner_size, + }; let _ = subclass_input.send_event_unbuffered(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: HiDpiFactorChanged { hidpi_factor: new_dpi_factor, - new_inner_size: &mut new_inner_size_opt, + new_inner_size: &mut new_physical_inner_size, }, }); - let new_physical_inner_size = new_inner_size_opt.unwrap_or(old_physical_inner_size); - // Unset maximized if we're changing the window's size. if new_physical_inner_size != old_physical_inner_size { WindowState::set_window_flags(subclass_input.window_state.lock(), window, |f| { From 85ea3f1d5d1016af087061e9f3e2dd11002c4f29 Mon Sep 17 00:00:00 2001 From: Osspial Date: Fri, 3 Jan 2020 13:54:06 -0500 Subject: [PATCH 057/239] Use i32 in Position::Physical (#1350) * Use i32 in Position::Physical * Fix multithreaded example * format --- examples/multithreaded.rs | 5 ++++- src/dpi.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index c91ce5a593..ba52ef596d 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -126,7 +126,10 @@ fn main() { if let Size::Physical(size) = WINDOW_SIZE.into() { window .set_cursor_position(Position::Physical( - PhysicalPosition::new(size.width / 2, size.height / 2), + PhysicalPosition::new( + size.width as i32 / 2, + size.height as i32 / 2, + ), )) .unwrap() } diff --git a/src/dpi.rs b/src/dpi.rs index 664b22cb6d..6f0311d30c 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -444,7 +444,7 @@ impl From> for Size { #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Position { - Physical(PhysicalPosition), + Physical(PhysicalPosition), Logical(LogicalPosition), } From d29f7f34aa4158123a6742c9fe8da857b1425d8e Mon Sep 17 00:00:00 2001 From: Osspial Date: Fri, 3 Jan 2020 14:52:27 -0500 Subject: [PATCH 058/239] Rename hidpi_factor to scale_factor (#1334) * Rename hidpi_factor to scale_factor * Deprecate WINIT_HIDPI_FACTOR environment variable in favor of WINIT_X11_SCALE_FACTOR * Rename HiDpiFactorChanged to DpiChanged and update docs I'm renaming it to DpiChanged instead of ScaleFactorChanged, since I'd like Winit to expose the raw DPI value at some point in the near future, and DpiChanged is a more apt name for that purpose. * Format * Fix macos and ios again * Fix bad macos rebase --- CHANGELOG.md | 2 + src/dpi.rs | 78 +++++++++---------- src/event.rs | 12 +-- src/monitor.rs | 8 +- src/platform/ios.rs | 20 ++--- src/platform_impl/android/mod.rs | 16 ++-- src/platform_impl/ios/app_state.rs | 16 ++-- src/platform_impl/ios/event_loop.rs | 4 +- src/platform_impl/ios/monitor.rs | 6 +- src/platform_impl/ios/view.rs | 26 +++---- src/platform_impl/ios/window.rs | 46 ++++++----- src/platform_impl/linux/mod.rs | 12 +-- src/platform_impl/linux/wayland/event_loop.rs | 10 +-- src/platform_impl/linux/wayland/window.rs | 12 +-- .../linux/x11/event_processor.rs | 28 +++---- src/platform_impl/linux/x11/monitor.rs | 12 +-- src/platform_impl/linux/x11/util/randr.rs | 24 +++--- src/platform_impl/linux/x11/window.rs | 22 +++--- src/platform_impl/macos/app_state.rs | 20 ++--- src/platform_impl/macos/event.rs | 4 +- src/platform_impl/macos/monitor.rs | 10 +-- src/platform_impl/macos/window.rs | 26 +++---- src/platform_impl/macos/window_delegate.rs | 28 +++---- .../web/event_loop/window_target.rs | 2 +- src/platform_impl/web/monitor.rs | 2 +- src/platform_impl/web/stdweb/canvas.rs | 2 +- src/platform_impl/web/stdweb/mod.rs | 8 +- src/platform_impl/web/web_sys/canvas.rs | 2 +- src/platform_impl/web/web_sys/mod.rs | 8 +- src/platform_impl/web/window.rs | 8 +- src/platform_impl/windows/event_loop.rs | 6 +- src/platform_impl/windows/monitor.rs | 2 +- src/platform_impl/windows/window.rs | 8 +- src/window.rs | 10 +-- 34 files changed, 252 insertions(+), 248 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 048c2e75a3..7684b9c235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,8 @@ - On X11, generate synthetic key events for keys held when a window gains or loses focus. - On X11, issue a `CursorMoved` event when a `Touch` event occurs, as X11 implicitly moves the cursor for such events. +- Rename `hidpi_factor` to `scale_factor` +- On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR` # 0.20.0 Alpha 4 (2019-10-18) diff --git a/src/dpi.rs b/src/dpi.rs index 6f0311d30c..66467f08cd 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -19,62 +19,62 @@ //! //! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100 //! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI (dots per inch) factor. -//! On a "typical" desktop display, the DPI factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical -//! pixels. However, a 1440p display may have a DPI factor of 1.25, so the button is rendered as 125x125 physical pixels. +//! On a "typical" desktop display, the scale factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical +//! pixels. However, a 1440p display may have a scale factor of 1.25, so the button is rendered as 125x125 physical pixels. //! Ideally, the button now has approximately the same perceived size across varying displays. //! -//! Failure to account for the DPI factor can create a badly degraded user experience. Most notably, it can make users +//! Failure to account for the scale factor can create a badly degraded user experience. Most notably, it can make users //! feel like they have bad eyesight, which will potentially cause them to think about growing elderly, resulting in //! them entering an existential panic. Once users enter that state, they will no longer be focused on your application. //! -//! There are two ways to get the DPI factor: -//! - You can track the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) event of your -//! windows. This event is sent any time the DPI factor changes, either because the window moved to another monitor, +//! There are two ways to get the scale factor: +//! - You can track the [`DpiChanged`](crate::event::WindowEvent::DpiChanged) event of your +//! windows. This event is sent any time the scale factor changes, either because the window moved to another monitor, //! or because the user changed the configuration of their screen. -//! - You can also retrieve the DPI factor of a monitor by calling -//! [`MonitorHandle::hidpi_factor`](crate::monitor::MonitorHandle::hidpi_factor), or the -//! current DPI factor applied to a window by calling -//! [`Window::hidpi_factor`](crate::window::Window::hidpi_factor), which is roughly equivalent -//! to `window.current_monitor().hidpi_factor()`. +//! - You can also retrieve the scale factor of a monitor by calling +//! [`MonitorHandle::scale_factor`](crate::monitor::MonitorHandle::scale_factor), or the +//! current scale factor applied to a window by calling +//! [`Window::scale_factor`](crate::window::Window::scale_factor), which is roughly equivalent +//! to `window.current_monitor().scale_factor()`. //! -//! Depending on the platform, the window's actual DPI factor may only be known after +//! Depending on the platform, the window's actual scale factor may only be known after //! the event loop has started and your window has been drawn once. To properly handle these cases, -//! the most robust way is to monitor the [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) -//! event and dynamically adapt your drawing logic to follow the DPI factor. +//! the most robust way is to monitor the [`DpiChanged`](crate::event::WindowEvent::DpiChanged) +//! event and dynamically adapt your drawing logic to follow the scale factor. //! -//! Here's an overview of what sort of DPI factors you can expect, and where they come from: +//! Here's an overview of what sort of scale factors you can expect, and where they come from: //! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the display settings. -//! While users are free to select any option they want, they're only given a selection of "nice" DPI factors, i.e. -//! 1.0, 1.25, 1.5... on Windows 7, the DPI factor is global and changing it requires logging out. -//! - **macOS:** The buzzword is "retina displays", which have a DPI factor of 2.0. Otherwise, the DPI factor is 1.0. -//! Intermediate DPI factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any -//! display to use that 2.0 DPI factor, given the use of the command line. -//! - **X11:** On X11, we calculate the DPI factor based on the millimeter dimensions provided by XRandR. This can +//! While users are free to select any option they want, they're only given a selection of "nice" scale factors, i.e. +//! 1.0, 1.25, 1.5... on Windows 7, the scale factor is global and changing it requires logging out. +//! - **macOS:** The buzzword is "retina displays", which have a scale factor of 2.0. Otherwise, the scale factor is 1.0. +//! Intermediate scale factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any +//! display to use that 2.0 scale factor, given the use of the command line. +//! - **X11:** On X11, we calcuate the scale factor based on the millimeter dimensions provided by XRandR. This can //! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be -//! overridden using the `WINIT_HIDPI_FACTOR` environment variable, though that's not recommended. -//! - **Wayland:** On Wayland, DPI factors are set per-screen by the server, and are always integers (most often 1 or 2). -//! - **iOS:** DPI factors are both constant and device-specific on iOS. -//! - **Android:** This feature isn't yet implemented on Android, so the DPI factor will always be returned as 1.0. -//! - **Web:** DPI factors are handled by the browser and will always be 1.0 for your application. +//! overridden using the `WINIT_X11_SCALE_FACTOR` environment variable, though that's not recommended. +//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always integers (most often 1 or 2). +//! - **iOS:** scale factors are both constant and device-specific on iOS. +//! - **Android:** This feature isn't yet implemented on Android, so the scale factor will always be returned as 1.0. +//! - **Web:** scale factors are handled by the browser and will always be 1.0 for your application. //! //! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This //! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a //! [`Resized`](crate::event::WindowEvent::Resized) event, even on platforms where no resize actually occurs, //! such as macOS and Wayland. As a result, it's not necessary to separately handle -//! [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) if you're only listening for size. +//! [`DpiChanged`](crate::event::WindowEvent::DpiChanged) if you're only listening for size. //! //! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your //! framebuffer's size should be in physical pixels. //! //! `winit` will send [`Resized`](crate::event::WindowEvent::Resized) events whenever a window's logical size -//! changes, and [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events -//! whenever the DPI factor changes. Receiving either of these events means that the physical size of your window has +//! changes, and [`DpiChanged`](crate::event::WindowEvent::DpiChanged) events +//! whenever the scale factor changes. Receiving either of these events means that the physical size of your window has //! changed, and you should recompute it using the latest values you received for each. If the logical size and the -//! DPI factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer +//! scale factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer //! these events and process them at the end of the queue. //! -//! If you never received any [`HiDpiFactorChanged`](crate::event::WindowEvent::HiDpiFactorChanged) events, -//! then your window's DPI factor is 1. +//! If you never received any [`DpiChanged`](crate::event::WindowEvent::DpiChanged) events, +//! then your window's scale factor is 1. pub trait Pixel: Copy + Into { fn from_f64(f: f64) -> Self; @@ -124,13 +124,13 @@ impl Pixel for f64 { } } -/// Checks that the DPI factor is a normal positive `f64`. +/// Checks that the scale factor is a normal positive `f64`. /// -/// All functions that take a DPI factor assert that this will return `true`. If you're sourcing DPI factors from +/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from /// anywhere other than winit, it's recommended to validate them using this function before passing them to winit; /// otherwise, you risk panics. #[inline] -pub fn validate_hidpi_factor(dpi_factor: f64) -> bool { +pub fn validate_scale_factor(dpi_factor: f64) -> bool { dpi_factor.is_sign_positive() && dpi_factor.is_normal() } @@ -164,7 +164,7 @@ impl LogicalPosition

{ #[inline] pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition { - assert!(validate_hidpi_factor(dpi_factor)); + assert!(validate_scale_factor(dpi_factor)); let x = self.x.into() * dpi_factor; let y = self.y.into() * dpi_factor; PhysicalPosition::new(x, y).cast() @@ -233,7 +233,7 @@ impl PhysicalPosition

{ #[inline] pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition { - assert!(validate_hidpi_factor(dpi_factor)); + assert!(validate_scale_factor(dpi_factor)); let x = self.x.into() / dpi_factor; let y = self.y.into() / dpi_factor; LogicalPosition::new(x, y).cast() @@ -299,7 +299,7 @@ impl LogicalSize

{ #[inline] pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize { - assert!(validate_hidpi_factor(dpi_factor)); + assert!(validate_scale_factor(dpi_factor)); let width = self.width.into() * dpi_factor; let height = self.height.into() * dpi_factor; PhysicalSize::new(width, height).cast() @@ -361,7 +361,7 @@ impl PhysicalSize

{ #[inline] pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize { - assert!(validate_hidpi_factor(dpi_factor)); + assert!(validate_scale_factor(dpi_factor)); let width = self.width.into() / dpi_factor; let height = self.height.into() / dpi_factor; LogicalSize::new(width, height).cast() diff --git a/src/event.rs b/src/event.rs index 12e58e746d..c0084fd159 100644 --- a/src/event.rs +++ b/src/event.rs @@ -292,21 +292,21 @@ pub enum WindowEvent<'a> { /// Touch event has been received Touch(Touch), - /// The DPI factor of the window has changed. + /// The window's scale factor has changed. /// /// The following user actions can cause DPI changes: /// /// * Changing the display's resolution. - /// * Changing the display's DPI factor (e.g. in Control Panel on Windows). - /// * Moving the window to a display with a different DPI factor. + /// * Changing the display's scale factor (e.g. in Control Panel on Windows). + /// * Moving the window to a display with a different scale factor. /// /// After this event callback has been processed, the window will be resized to whatever value /// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested /// by the OS, but it can be changed to any value. /// /// For more information about DPI in general, see the [`dpi`](dpi/index.html) module. - HiDpiFactorChanged { - hidpi_factor: f64, + DpiChanged { + scale_factor: f64, new_inner_size: &'a mut PhysicalSize, }, @@ -394,7 +394,7 @@ impl<'a> WindowEvent<'a> { }), Touch(touch) => Some(Touch(touch)), ThemeChanged(theme) => Some(ThemeChanged(theme)), - HiDpiFactorChanged { .. } => None, + DpiChanged { .. } => None, } } } diff --git a/src/monitor.rs b/src/monitor.rs index c9d6e061ce..8977c11a4a 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -148,18 +148,18 @@ impl MonitorHandle { self.inner.position() } - /// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa. + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. /// /// See the [`dpi`](crate::dpi) module for more information. /// /// ## Platform-specific /// - /// - **X11:** Can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. + /// - **X11:** Can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable. /// - **Android:** Always returns 1.0. /// - **Web:** Always returns 1.0 #[inline] - pub fn hidpi_factor(&self) -> f64 { - self.inner.hidpi_factor() + pub fn scale_factor(&self) -> f64 { + self.inner.scale_factor() } /// Returns all fullscreen video modes supported by this monitor. diff --git a/src/platform/ios.rs b/src/platform/ios.rs index 9218204fa6..9d982ac511 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -43,14 +43,14 @@ pub trait WindowExtIOS { /// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc fn ui_view(&self) -> *mut c_void; - /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`. + /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// /// The default value is device dependent, and it's recommended GLES or Metal applications set - /// this to [`MonitorHandle::hidpi_factor()`]. + /// this to [`MonitorHandle::scale_factor()`]. /// /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc - fn set_hidpi_factor(&self, hidpi_factor: f64); + fn set_scale_factor(&self, scale_factor: f64); /// Sets the valid orientations for the [`Window`]. /// @@ -113,8 +113,8 @@ impl WindowExtIOS for Window { } #[inline] - fn set_hidpi_factor(&self, hidpi_factor: f64) { - self.window.set_hidpi_factor(hidpi_factor) + fn set_scale_factor(&self, scale_factor: f64) { + self.window.set_scale_factor(scale_factor) } #[inline] @@ -148,14 +148,14 @@ pub trait WindowBuilderExtIOS { /// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder; - /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`. + /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`. /// /// The default value is device dependent, and it's recommended GLES or Metal applications set - /// this to [`MonitorHandle::hidpi_factor()`]. + /// this to [`MonitorHandle::scale_factor()`]. /// /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc - fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder; + fn with_scale_factor(self, scale_factor: f64) -> WindowBuilder; /// Sets the valid orientations for the [`Window`]. /// @@ -204,8 +204,8 @@ impl WindowBuilderExtIOS for WindowBuilder { } #[inline] - fn with_hidpi_factor(mut self, hidpi_factor: f64) -> WindowBuilder { - self.platform_specific.hidpi_factor = Some(hidpi_factor); + fn with_scale_factor(mut self, scale_factor: f64) -> WindowBuilder { + self.platform_specific.scale_factor = Some(scale_factor); self } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 11a140de6b..a4cf29faf0 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -61,7 +61,7 @@ impl EventLoop { while let Ok(event) = self.event_rx.try_recv() { let e = match event { android_glue::Event::EventMotion(motion) => { - let dpi_factor = MonitorHandle.hidpi_factor(); + let dpi_factor = MonitorHandle.scale_factor(); let location = LogicalPosition::from_physical( (motion.x as f64, motion.y as f64), dpi_factor, @@ -102,7 +102,7 @@ impl EventLoop { if native_window.is_null() { None } else { - let dpi_factor = MonitorHandle.hidpi_factor(); + let dpi_factor = MonitorHandle.scale_factor(); let physical_size = MonitorHandle.size(); let size = LogicalSize::from_physical(physical_size, dpi_factor); Some(Event::WindowEvent { @@ -195,14 +195,14 @@ impl fmt::Debug for MonitorHandle { name: Option, dimensions: PhysicalSize, position: PhysicalPosition, - hidpi_factor: f64, + scale_factor: f64, } let monitor_id_proxy = MonitorHandle { name: self.name(), dimensions: self.size(), position: self.outer_position(), - hidpi_factor: self.hidpi_factor(), + scale_factor: self.scale_factor(), }; monitor_id_proxy.fmt(f) @@ -234,7 +234,7 @@ impl MonitorHandle { } #[inline] - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { 1.0 } } @@ -319,7 +319,7 @@ impl Window { if self.native_window.is_null() { None } else { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let physical_size = self.current_monitor().size(); Some(LogicalSize::from_physical(physical_size, dpi_factor)) } @@ -336,8 +336,8 @@ impl Window { } #[inline] - pub fn hidpi_factor(&self) -> f64 { - self.current_monitor().hidpi_factor() + pub fn scale_factor(&self) -> f64 { + self.current_monitor().scale_factor() } #[inline] diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 229e17d7f9..6a3a6126c8 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -840,15 +840,15 @@ fn handle_event_proxy( proxy: EventProxy, ) { match proxy { - EventProxy::HiDpiFactorChangedProxy { + EventProxy::DpiChangedProxy { suggested_size, - hidpi_factor, + scale_factor, window_id, } => handle_hidpi_proxy( event_handler, control_flow, suggested_size, - hidpi_factor, + scale_factor, window_id, ), } @@ -858,22 +858,22 @@ fn handle_hidpi_proxy( event_handler: &mut Box, mut control_flow: ControlFlow, suggested_size: LogicalSize, - hidpi_factor: f64, + scale_factor: f64, window_id: id, ) { - let mut size = suggested_size.to_physical(hidpi_factor); + let mut size = suggested_size.to_physical(scale_factor); let new_inner_size = &mut size; let event = Event::WindowEvent { window_id: RootWindowId(window_id.into()), - event: WindowEvent::HiDpiFactorChanged { - hidpi_factor, + event: WindowEvent::DpiChanged { + scale_factor, new_inner_size, }, }; event_handler.handle_nonuser_event(event, &mut control_flow); let (view, screen_frame) = get_view_and_screen_frame(window_id); let physical_size = *new_inner_size; - let logical_size = physical_size.to_logical(hidpi_factor); + let logical_size = physical_size.to_logical(scale_factor); let size = CGSize::new(logical_size); let new_frame: CGRect = CGRect::new(screen_frame.origin, size); unsafe { diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index b2987298d0..f9f28b5a14 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -37,10 +37,10 @@ pub enum EventWrapper { #[derive(Debug, PartialEq)] pub enum EventProxy { - HiDpiFactorChangedProxy { + DpiChangedProxy { window_id: id, suggested_size: LogicalSize, - hidpi_factor: f64, + scale_factor: f64, }, } diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index e265954a69..42c71ffc7a 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -173,14 +173,14 @@ impl fmt::Debug for MonitorHandle { name: Option, size: PhysicalSize, position: PhysicalPosition, - hidpi_factor: f64, + scale_factor: f64, } let monitor_id_proxy = MonitorHandle { name: self.name(), size: self.size(), position: self.position(), - hidpi_factor: self.hidpi_factor(), + scale_factor: self.scale_factor(), }; monitor_id_proxy.fmt(f) @@ -230,7 +230,7 @@ impl Inner { } } - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { unsafe { let scale: CGFloat = msg_send![self.ui_screen(), nativeScale]; scale as f64 diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index a2419e9d05..33ecac37a4 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -143,13 +143,13 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { extern "C" fn set_content_scale_factor( object: &mut Object, _: Sel, - untrusted_hidpi_factor: CGFloat, + untrusted_scale_factor: CGFloat, ) { unsafe { let superclass: &'static Class = msg_send![object, superclass]; let () = msg_send![ super(object, superclass), - setContentScaleFactor: untrusted_hidpi_factor + setContentScaleFactor: untrusted_scale_factor ]; let window: id = msg_send![object, window]; @@ -168,9 +168,9 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { && dpi_factor.is_finite() && dpi_factor.is_sign_positive() && dpi_factor > 0.0, - "invalid hidpi_factor set on UIView", + "invalid scale_factor set on UIView", ); - let hidpi_factor: f64 = dpi_factor.into(); + let scale_factor: f64 = dpi_factor.into(); let bounds: CGRect = msg_send![object, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; @@ -181,17 +181,15 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { height: screen_frame.size.height as _, }; app_state::handle_nonuser_events( - std::iter::once(EventWrapper::EventProxy( - EventProxy::HiDpiFactorChangedProxy { - window_id: window, - hidpi_factor, - suggested_size: size, - }, - )) + std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { + window_id: window, + scale_factor, + suggested_size: size, + })) .chain(std::iter::once(EventWrapper::StaticEvent( Event::WindowEvent { window_id: RootWindowId(window.into()), - event: WindowEvent::Resized(size.to_physical(hidpi_factor)), + event: WindowEvent::Resized(size.to_physical(scale_factor)), }, ))), ); @@ -426,8 +424,8 @@ pub unsafe fn create_view( let view: id = msg_send![view, initWithFrame: frame]; assert!(!view.is_null(), "Failed to initialize `UIView` instance"); let () = msg_send![view, setMultipleTouchEnabled: YES]; - if let Some(hidpi_factor) = platform_attributes.hidpi_factor { - let () = msg_send![view, setContentScaleFactor: hidpi_factor as CGFloat]; + if let Some(scale_factor) = platform_attributes.scale_factor { + let () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat]; } view diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 0fec4f75c9..2ae4866485 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -83,7 +83,7 @@ impl Inner { x: safe_area.origin.x as f64, y: safe_area.origin.y as f64, }; - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); Ok(position.to_physical(dpi_factor)) } } @@ -95,14 +95,14 @@ impl Inner { x: screen_frame.origin.x as f64, y: screen_frame.origin.y as f64, }; - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); Ok(position.to_physical(dpi_factor)) } } pub fn set_outer_position(&self, physical_position: Position) { unsafe { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let position = physical_position.to_logical::(dpi_factor); let screen_frame = self.screen_frame(); let new_screen_frame = CGRect { @@ -119,7 +119,7 @@ impl Inner { pub fn inner_size(&self) -> PhysicalSize { unsafe { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let safe_area = self.safe_area_screen_space(); let size = LogicalSize { width: safe_area.size.width as f64, @@ -131,7 +131,7 @@ impl Inner { pub fn outer_size(&self) -> PhysicalSize { unsafe { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let screen_frame = self.screen_frame(); let size = LogicalSize { width: screen_frame.size.width as f64, @@ -157,7 +157,7 @@ impl Inner { warn!("`Window::set_resizable` is ignored on iOS") } - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { unsafe { let hidpi: CGFloat = msg_send![self.view, contentScaleFactor]; hidpi as _ @@ -398,11 +398,11 @@ impl Window { }; app_state::set_key_window(window); - // Like the Windows and macOS backends, we send a `HiDpiFactorChanged` and `Resized` + // Like the Windows and macOS backends, we send a `DpiChanged` and `Resized` // event on window creation if the DPI factor != 1.0 let dpi_factor: CGFloat = msg_send![view, contentScaleFactor]; - let hidpi_factor: f64 = dpi_factor.into(); - if hidpi_factor != 1.0 { + let scale_factor: f64 = dpi_factor.into(); + if scale_factor != 1.0 { let bounds: CGRect = msg_send![view, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; @@ -413,17 +413,15 @@ impl Window { height: screen_frame.size.height as _, }; app_state::handle_nonuser_events( - std::iter::once(EventWrapper::EventProxy( - EventProxy::HiDpiFactorChangedProxy { - window_id: window, - hidpi_factor, - suggested_size: size, - }, - )) + std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { + window_id: window, + scale_factor, + suggested_size: size, + })) .chain(std::iter::once(EventWrapper::StaticEvent( Event::WindowEvent { window_id: RootWindowId(window.into()), - event: WindowEvent::Resized(size.to_physical(hidpi_factor)), + event: WindowEvent::Resized(size.to_physical(scale_factor)), }, ))), ); @@ -446,14 +444,14 @@ impl Inner { self.view } - pub fn set_hidpi_factor(&self, hidpi_factor: f64) { + pub fn set_scale_factor(&self, scale_factor: f64) { unsafe { assert!( - dpi::validate_hidpi_factor(hidpi_factor), - "`WindowExtIOS::set_hidpi_factor` received an invalid hidpi factor" + dpi::validate_scale_factor(scale_factor), + "`WindowExtIOS::set_scale_factor` received an invalid hidpi factor" ); - let hidpi_factor = hidpi_factor as CGFloat; - let () = msg_send![self.view, setContentScaleFactor: hidpi_factor]; + let scale_factor = scale_factor as CGFloat; + let () = msg_send![self.view, setContentScaleFactor: scale_factor]; } } @@ -619,7 +617,7 @@ impl From for WindowId { #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub root_view_class: &'static Class, - pub hidpi_factor: Option, + pub scale_factor: Option, pub valid_orientations: ValidOrientations, pub prefers_home_indicator_hidden: bool, pub prefers_status_bar_hidden: bool, @@ -630,7 +628,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { fn default() -> PlatformSpecificWindowBuilderAttributes { PlatformSpecificWindowBuilderAttributes { root_view_class: class!(UIView), - hidpi_factor: None, + scale_factor: None, valid_orientations: Default::default(), prefers_home_indicator_hidden: false, prefers_status_bar_hidden: false, diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index cd7ed4a704..e3bfe78f07 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -148,10 +148,10 @@ impl MonitorHandle { } #[inline] - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { match self { - &MonitorHandle::X(ref m) => m.hidpi_factor(), - &MonitorHandle::Wayland(ref m) => m.hidpi_factor() as f64, + &MonitorHandle::X(ref m) => m.scale_factor(), + &MonitorHandle::Wayland(ref m) => m.scale_factor() as f64, } } @@ -342,10 +342,10 @@ impl Window { } #[inline] - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { match self { - &Window::X(ref w) => w.hidpi_factor(), - &Window::Wayland(ref w) => w.hidpi_factor() as f64, + &Window::X(ref w) => w.scale_factor(), + &Window::Wayland(ref w) => w.scale_factor() as f64, } } diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 4eded25344..3bd0e4cd4a 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -734,8 +734,8 @@ impl EventLoop { callback(Event::WindowEvent { window_id, - event: WindowEvent::HiDpiFactorChanged { - hidpi_factor: dpi, + event: WindowEvent::DpiChanged { + scale_factor: dpi, new_inner_size: &mut new_inner_size, }, }); @@ -1007,7 +1007,7 @@ impl fmt::Debug for MonitorHandle { native_identifier: u32, size: PhysicalSize, position: PhysicalPosition, - hidpi_factor: i32, + scale_factor: i32, } let monitor_id_proxy = MonitorHandle { @@ -1015,7 +1015,7 @@ impl fmt::Debug for MonitorHandle { native_identifier: self.native_identifier(), size: self.size(), position: self.position(), - hidpi_factor: self.hidpi_factor(), + scale_factor: self.scale_factor(), }; monitor_id_proxy.fmt(f) @@ -1055,7 +1055,7 @@ impl MonitorHandle { } #[inline] - pub fn hidpi_factor(&self) -> i32 { + pub fn scale_factor(&self) -> i32 { self.mgr .with_info(&self.proxy, |_, info| info.scale_factor) .unwrap_or(1) diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index be2d0df938..f241f503b4 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -221,7 +221,7 @@ impl Window { } pub fn inner_size(&self) -> PhysicalSize { - let dpi = self.hidpi_factor() as f64; + let dpi = self.scale_factor() as f64; let size = LogicalSize::::from(*self.size.lock().unwrap()); size.to_physical(dpi) } @@ -232,7 +232,7 @@ impl Window { #[inline] pub fn outer_size(&self) -> PhysicalSize { - let dpi = self.hidpi_factor() as f64; + let dpi = self.scale_factor() as f64; let (w, h) = self.size.lock().unwrap().clone(); // let (w, h) = super::wayland_window::add_borders(w as i32, h as i32); let size = LogicalSize::::from((w, h)); @@ -242,7 +242,7 @@ impl Window { #[inline] // NOTE: This will only resize the borders, the contents must be updated by the user pub fn set_inner_size(&self, size: Size) { - let dpi = self.hidpi_factor() as f64; + let dpi = self.scale_factor() as f64; let (w, h) = size.to_logical::(dpi).into(); self.frame.lock().unwrap().resize(w, h); *(self.size.lock().unwrap()) = (w, h); @@ -250,7 +250,7 @@ impl Window { #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { - let dpi = self.hidpi_factor() as f64; + let dpi = self.scale_factor() as f64; self.frame .lock() .unwrap() @@ -259,7 +259,7 @@ impl Window { #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { - let dpi = self.hidpi_factor() as f64; + let dpi = self.scale_factor() as f64; self.frame .lock() .unwrap() @@ -272,7 +272,7 @@ impl Window { } #[inline] - pub fn hidpi_factor(&self) -> i32 { + pub fn scale_factor(&self) -> i32 { get_dpi_factor(&self.surface) } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 9ce17f273a..9cb922d3da 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -401,23 +401,23 @@ impl EventProcessor { .dpi_adjusted .unwrap_or_else(|| (xev.width as u32, xev.height as u32)); - let last_hidpi_factor = shared_state_lock.last_monitor.hidpi_factor; - let new_hidpi_factor = { + let last_scale_factor = shared_state_lock.last_monitor.scale_factor; + let new_scale_factor = { let window_rect = util::AaRect::new(new_outer_position, new_inner_size); let monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); if monitor.is_dummy() { // Avoid updating monitor using a dummy monitor handle - last_hidpi_factor + last_scale_factor } else { shared_state_lock.last_monitor = monitor.clone(); - monitor.hidpi_factor + monitor.scale_factor } }; - if last_hidpi_factor != new_hidpi_factor { + if last_scale_factor != new_scale_factor { let (new_width, new_height) = window.adjust_for_dpi( - last_hidpi_factor, - new_hidpi_factor, + last_scale_factor, + new_scale_factor, width, height, ); @@ -427,8 +427,8 @@ impl EventProcessor { callback(Event::WindowEvent { window_id, - event: WindowEvent::HiDpiFactorChanged { - hidpi_factor: new_hidpi_factor, + event: WindowEvent::DpiChanged { + scale_factor: new_scale_factor, new_inner_size: &mut new_inner_size, }, }); @@ -1118,7 +1118,7 @@ impl EventProcessor { .iter() .find(|prev_monitor| prev_monitor.name == new_monitor.name) .map(|prev_monitor| { - if new_monitor.hidpi_factor != prev_monitor.hidpi_factor { + if new_monitor.scale_factor != prev_monitor.scale_factor { for (window_id, window) in wt.windows.borrow().iter() { if let Some(window) = window.upgrade() { // Check if the window is on this monitor @@ -1128,8 +1128,8 @@ impl EventProcessor { window.inner_size_physical(); let (new_width, new_height) = window .adjust_for_dpi( - prev_monitor.hidpi_factor, - new_monitor.hidpi_factor, + prev_monitor.scale_factor, + new_monitor.scale_factor, width, height, ); @@ -1146,8 +1146,8 @@ impl EventProcessor { callback(Event::WindowEvent { window_id, - event: WindowEvent::HiDpiFactorChanged { - hidpi_factor: new_monitor.hidpi_factor, + event: WindowEvent::DpiChanged { + scale_factor: new_monitor.scale_factor, new_inner_size: &mut new_inner_size, }, }); diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 98c8022b40..274e0cda9f 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -73,7 +73,7 @@ pub struct MonitorHandle { /// If the monitor is the primary one primary: bool, /// The DPI scale factor - pub(crate) hidpi_factor: f64, + pub(crate) scale_factor: f64, /// Used to determine which windows are on this monitor pub(crate) rect: util::AaRect, /// Supported video modes on this monitor @@ -114,14 +114,14 @@ impl MonitorHandle { crtc: *mut XRRCrtcInfo, primary: bool, ) -> Option { - let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? }; + let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? }; let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) }; let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) }; let rect = util::AaRect::new(position, dimensions); Some(MonitorHandle { id, name, - hidpi_factor, + scale_factor, dimensions, position, primary, @@ -134,7 +134,7 @@ impl MonitorHandle { MonitorHandle { id: 0, name: "".into(), - hidpi_factor: 1.0, + scale_factor: 1.0, dimensions: (1, 1), position: (0, 0), primary: true, @@ -166,8 +166,8 @@ impl MonitorHandle { } #[inline] - pub fn hidpi_factor(&self) -> f64 { - self.hidpi_factor + pub fn scale_factor(&self) -> f64 { + self.scale_factor } #[inline] diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 4630389e8d..b9258c68f1 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -4,7 +4,7 @@ use super::{ ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources}, *, }; -use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode}; +use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode}; /// Represents values of `WINIT_HIDPI_FACTOR`. pub enum EnvVarDPI { @@ -26,7 +26,7 @@ pub fn calc_dpi_factor( let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt(); // Quantize 1/12 step size let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0); - assert!(validate_hidpi_factor(dpi_factor)); + assert!(validate_scale_factor(dpi_factor)); dpi_factor } @@ -100,8 +100,14 @@ impl XConnection { (*output_info).nameLen as usize, ); let name = String::from_utf8_lossy(name_slice).into(); - // Override DPI if `WINIT_HIDPI_FACTOR` variable is set - let dpi_env = env::var("WINIT_HIDPI_FACTOR").ok().map_or_else( + // Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set + let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok(); + if deprecated_dpi_override.is_some() { + warn!( + "The WINIT_HIDPI_FACTOR environment variable is deprecated; use WINIT_X11_SCALE_FACTOR" + ) + } + let dpi_env = env::var("WINIT_X11_SCALE_FACTOR").ok().map_or_else( || EnvVarDPI::NotSet, |var| { if var.to_lowercase() == "randr" { @@ -112,14 +118,14 @@ impl XConnection { EnvVarDPI::NotSet } else { panic!( - "`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", + "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", var ); } }, ); - let hidpi_factor = match dpi_env { + let scale_factor = match dpi_env { EnvVarDPI::Randr => calc_dpi_factor( ((*crtc).width as u32, (*crtc).height as u32), ( @@ -128,9 +134,9 @@ impl XConnection { ), ), EnvVarDPI::Scale(dpi_override) => { - if !validate_hidpi_factor(dpi_override) { + if !validate_scale_factor(dpi_override) { panic!( - "`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", + "`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", dpi_override, ); } @@ -152,7 +158,7 @@ impl XConnection { }; (self.xrandr.XRRFreeOutputInfo)(output_info); - Some((name, hidpi_factor, modes)) + Some((name, scale_factor, modes)) } pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> { unsafe { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 9fd7d89198..dcbfd59c31 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -132,9 +132,9 @@ impl UnownedWindow { }) .unwrap_or_else(|| monitors.swap_remove(0)) }; - let dpi_factor = guessed_monitor.hidpi_factor(); + let dpi_factor = guessed_monitor.scale_factor(); - info!("Guessed window DPI factor: {}", dpi_factor); + info!("Guessed window scale factor: {}", dpi_factor); let max_inner_size: Option<(u32, u32)> = window_attrs .max_inner_size @@ -993,7 +993,7 @@ impl UnownedWindow { #[inline] pub fn set_outer_position(&self, position: Position) { - let (x, y) = position.to_physical::(self.hidpi_factor()).into(); + let (x, y) = position.to_physical::(self.scale_factor()).into(); self.set_position_physical(x, y); } @@ -1038,7 +1038,7 @@ impl UnownedWindow { #[inline] pub fn set_inner_size(&self, size: Size) { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let (width, height) = size.to_physical::(dpi_factor).into(); self.set_inner_size_physical(width, height); } @@ -1063,7 +1063,7 @@ impl UnownedWindow { pub fn set_min_inner_size(&self, dimensions: Option) { self.shared_state.lock().min_inner_size = dimensions; let physical_dimensions = - dimensions.map(|dimensions| dimensions.to_physical::(self.hidpi_factor()).into()); + dimensions.map(|dimensions| dimensions.to_physical::(self.scale_factor()).into()); self.set_min_inner_size_physical(physical_dimensions); } @@ -1076,7 +1076,7 @@ impl UnownedWindow { pub fn set_max_inner_size(&self, dimensions: Option) { self.shared_state.lock().max_inner_size = dimensions; let physical_dimensions = - dimensions.map(|dimensions| dimensions.to_physical::(self.hidpi_factor()).into()); + dimensions.map(|dimensions| dimensions.to_physical::(self.scale_factor()).into()); self.set_max_inner_size_physical(physical_dimensions); } @@ -1133,7 +1133,7 @@ impl UnownedWindow { self.set_maximizable_inner(resizable).queue(); - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let min_inner_size = min_size .map(|size| size.to_physical::(dpi_factor)) .map(Into::into); @@ -1259,8 +1259,8 @@ impl UnownedWindow { } #[inline] - pub fn hidpi_factor(&self) -> f64 { - self.current_monitor().hidpi_factor + pub fn scale_factor(&self) -> f64 { + self.current_monitor().scale_factor } pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> { @@ -1274,7 +1274,7 @@ impl UnownedWindow { #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - let (x, y) = position.to_physical::(self.hidpi_factor()).into(); + let (x, y) = position.to_physical::(self.scale_factor()).into(); self.set_cursor_position_physical(x, y) } @@ -1287,7 +1287,7 @@ impl UnownedWindow { #[inline] pub fn set_ime_position(&self, spot: Position) { - let (x, y) = spot.to_physical::(self.hidpi_factor()).into(); + let (x, y) = spot.to_physical::(self.scale_factor()).into(); self.set_ime_position_physical(x, y); } diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index a60d68619d..6625d517a8 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -181,19 +181,19 @@ impl Handler { } } - fn handle_hidpi_factor_changed_event( + fn handle_scale_factor_changed_event( &self, callback: &mut Box, ns_window: IdRef, suggested_size: LogicalSize, - hidpi_factor: f64, + scale_factor: f64, ) { - let mut size = suggested_size.to_physical(hidpi_factor); + let mut size = suggested_size.to_physical(scale_factor); let new_inner_size = &mut size; let event = Event::WindowEvent { window_id: WindowId(get_window_id(*ns_window)), - event: WindowEvent::HiDpiFactorChanged { - hidpi_factor, + event: WindowEvent::DpiChanged { + scale_factor, new_inner_size, }, }; @@ -201,22 +201,22 @@ impl Handler { callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap()); let physical_size = *new_inner_size; - let logical_size = physical_size.to_logical(hidpi_factor); + let logical_size = physical_size.to_logical(scale_factor); let size = NSSize::new(logical_size.width, logical_size.height); unsafe { NSWindow::setContentSize_(*ns_window, size) }; } fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box) { match proxy { - EventProxy::HiDpiFactorChangedProxy { + EventProxy::DpiChangedProxy { ns_window, suggested_size, - hidpi_factor, - } => self.handle_hidpi_factor_changed_event( + scale_factor, + } => self.handle_scale_factor_changed_event( callback, ns_window, suggested_size, - hidpi_factor, + scale_factor, ), } } diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index ef33ba234f..07a7bf0cd1 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -22,10 +22,10 @@ pub enum EventWrapper { #[derive(Debug, PartialEq)] pub enum EventProxy { - HiDpiFactorChangedProxy { + DpiChangedProxy { ns_window: IdRef, suggested_size: LogicalSize, - hidpi_factor: f64, + scale_factor: f64, }, } diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 11a0aa2b5a..d9f0baa776 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -168,7 +168,7 @@ impl fmt::Debug for MonitorHandle { native_identifier: u32, size: PhysicalSize, position: PhysicalPosition, - hidpi_factor: f64, + scale_factor: f64, } let monitor_id_proxy = MonitorHandle { @@ -176,7 +176,7 @@ impl fmt::Debug for MonitorHandle { native_identifier: self.native_identifier(), size: self.size(), position: self.position(), - hidpi_factor: self.hidpi_factor(), + scale_factor: self.scale_factor(), }; monitor_id_proxy.fmt(f) @@ -204,7 +204,7 @@ impl MonitorHandle { let display = CGDisplay::new(display_id); let height = display.pixels_high(); let width = display.pixels_wide(); - PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.hidpi_factor()) + PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor()) } #[inline] @@ -212,11 +212,11 @@ impl MonitorHandle { let bounds = unsafe { CGDisplayBounds(self.native_identifier()) }; PhysicalPosition::from_logical::<_, f64>( (bounds.origin.x as f64, bounds.origin.y as f64), - self.hidpi_factor(), + self.scale_factor(), ) } - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { let screen = match self.ns_screen() { Some(screen) => screen, None => return 1.0, // default to 1.0 when we can't find the screen diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 5c3547a439..d32da6580a 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -135,10 +135,10 @@ fn create_window( Some(screen) => NSScreen::frame(screen), None => { let screen = NSScreen::mainScreen(nil); - let hidpi_factor = NSScreen::backingScaleFactor(screen) as f64; + let scale_factor = NSScreen::backingScaleFactor(screen) as f64; let (width, height) = match attrs.inner_size { Some(size) => { - let logical = size.to_logical(hidpi_factor); + let logical = size.to_logical(scale_factor); (logical.width, logical.height) } None => (800.0, 600.0), @@ -446,7 +446,7 @@ impl UnownedWindow { frame_rect.origin.x as f64, util::bottom_left_to_top_left(frame_rect), ); - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); Ok(position.to_physical(dpi_factor)) } @@ -458,12 +458,12 @@ impl UnownedWindow { content_rect.origin.x as f64, util::bottom_left_to_top_left(content_rect), ); - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); Ok(position.to_physical(dpi_factor)) } pub fn set_outer_position(&self, position: Position) { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let position = position.to_logical(dpi_factor); let dummy = NSRect::new( NSPoint::new( @@ -484,7 +484,7 @@ impl UnownedWindow { let view_frame = unsafe { NSView::frame(*self.ns_view) }; let logical: LogicalSize = (view_frame.size.width as f64, view_frame.size.height as f64).into(); - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); logical.to_physical(dpi_factor) } @@ -493,14 +493,14 @@ impl UnownedWindow { let view_frame = unsafe { NSWindow::frame(*self.ns_window) }; let logical: LogicalSize = (view_frame.size.width as f64, view_frame.size.height as f64).into(); - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); logical.to_physical(dpi_factor) } #[inline] pub fn set_inner_size(&self, size: Size) { unsafe { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); util::set_content_size_async(*self.ns_window, size.to_logical(dpi_factor)); } } @@ -511,7 +511,7 @@ impl UnownedWindow { width: 0.0, height: 0.0, })); - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); set_min_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor)); } } @@ -522,7 +522,7 @@ impl UnownedWindow { width: std::f32::MAX as f64, height: std::f32::MAX as f64, })); - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); set_max_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor)); } } @@ -583,14 +583,14 @@ impl UnownedWindow { } #[inline] - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { unsafe { NSWindow::backingScaleFactor(*self.ns_window) as _ } } #[inline] pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> { let physical_window_position = self.inner_position().unwrap(); - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let window_position = physical_window_position.to_logical::(dpi_factor); let logical_cursor_position = cursor_position.to_logical::(dpi_factor); let point = appkit::CGPoint { @@ -929,7 +929,7 @@ impl UnownedWindow { #[inline] pub fn set_ime_position(&self, spot: Position) { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let logical_spot = spot.to_logical(dpi_factor); unsafe { view::set_ime_position( diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 6ffe88c3a4..7766893782 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -48,18 +48,18 @@ pub struct WindowDelegateState { impl WindowDelegateState { pub fn new(window: &Arc, initial_fullscreen: bool) -> Self { - let hidpi_factor = window.hidpi_factor(); + let scale_factor = window.scale_factor(); let mut delegate_state = WindowDelegateState { ns_window: window.ns_window.clone(), ns_view: window.ns_view.clone(), window: Arc::downgrade(&window), initial_fullscreen, previous_position: None, - previous_dpi_factor: hidpi_factor, + previous_dpi_factor: scale_factor, }; - if hidpi_factor != 1.0 { - delegate_state.emit_static_hidpi_factor_changed_event(); + if scale_factor != 1.0 { + delegate_state.emit_static_scale_factor_changed_event(); } delegate_state @@ -80,26 +80,26 @@ impl WindowDelegateState { AppState::queue_event(EventWrapper::StaticEvent(event)); } - pub fn emit_static_hidpi_factor_changed_event(&mut self) { - let hidpi_factor = self.get_hidpi_factor(); - if hidpi_factor == self.previous_dpi_factor { + pub fn emit_static_scale_factor_changed_event(&mut self) { + let scale_factor = self.get_scale_factor(); + if scale_factor == self.previous_dpi_factor { return (); }; - self.previous_dpi_factor = hidpi_factor; - let wrapper = EventWrapper::EventProxy(EventProxy::HiDpiFactorChangedProxy { + self.previous_dpi_factor = scale_factor; + let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { ns_window: IdRef::retain(*self.ns_window), suggested_size: self.view_size(), - hidpi_factor, + scale_factor, }); AppState::queue_event(wrapper); } pub fn emit_resize_event(&mut self) { let rect = unsafe { NSView::frame(*self.ns_view) }; - let hidpi_factor = self.get_hidpi_factor(); + let scale_factor = self.get_scale_factor(); let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); - let size = logical_size.to_physical(hidpi_factor); + let size = logical_size.to_physical(scale_factor); self.emit_event(WindowEvent::Resized(size)); } @@ -114,7 +114,7 @@ impl WindowDelegateState { } } - fn get_hidpi_factor(&self) -> f64 { + fn get_scale_factor(&self) -> f64 { (unsafe { NSWindow::backingScaleFactor(*self.ns_window) }) as f64 } @@ -301,7 +301,7 @@ extern "C" fn window_did_move(this: &Object, _: Sel, _: id) { extern "C" fn window_did_change_backing_properties(this: &Object, _: Sel, _: id) { trace!("Triggered `windowDidChangeBackingProperties:`"); with_state(this, |state| { - state.emit_static_hidpi_factor_changed_event(); + state.emit_static_scale_factor_changed_event(); }); trace!("Completed `windowDidChangeBackingProperties:`"); } diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index abe7b8e3d6..6596cebee7 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -185,7 +185,7 @@ impl WindowTarget { height: raw.height() as u32, }; - backend::window_size().to_physical(backend::hidpi_factor()) + backend::window_size().to_physical(backend::scale_factor()) } else { intended_size }; diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs index ac06d0cba6..d2b3c364ae 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -5,7 +5,7 @@ use crate::monitor::{MonitorHandle, VideoMode}; pub struct Handle; impl Handle { - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { 1.0 } diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 99778aa6ae..94586d0480 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -213,7 +213,7 @@ impl Canvas { self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| { handler( event.pointer_id(), - event::mouse_position(&event).to_physical(super::hidpi_factor()), + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_modifiers(&event), ); })); diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index b87a1b9f66..6b45e68306 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -41,7 +41,7 @@ pub fn window_size() -> LogicalSize { LogicalSize { width, height } } -pub fn hidpi_factor() -> f64 { +pub fn scale_factor() -> f64 { let window = window(); window.device_pixel_ratio() } @@ -49,10 +49,10 @@ pub fn hidpi_factor() -> f64 { pub fn set_canvas_size(raw: &CanvasElement, size: Size) { use stdweb::*; - let hidpi_factor = hidpi_factor(); + let scale_factor = scale_factor(); - let physical_size = size.to_physical::(hidpi_factor); - let logical_size = size.to_logical::(hidpi_factor); + let physical_size = size.to_physical::(scale_factor); + let logical_size = size.to_logical::(scale_factor); raw.set_width(physical_size.width); raw.set_height(physical_size.height); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 7d8d292a88..70b0ecc91a 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -221,7 +221,7 @@ impl Canvas { self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| { handler( event.pointer_id(), - event::mouse_position(&event).to_physical(super::hidpi_factor()), + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_modifiers(&event), ); })); diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index bde099a735..fec8640228 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -56,16 +56,16 @@ pub fn window_size() -> LogicalSize { LogicalSize { width, height } } -pub fn hidpi_factor() -> f64 { +pub fn scale_factor() -> f64 { let window = web_sys::window().expect("Failed to obtain window"); window.device_pixel_ratio() } pub fn set_canvas_size(raw: &HtmlCanvasElement, size: Size) { - let hidpi_factor = hidpi_factor(); + let scale_factor = scale_factor(); - let physical_size = size.to_physical::(hidpi_factor); - let logical_size = size.to_logical::(hidpi_factor); + let physical_size = size.to_physical::(scale_factor); + let logical_size = size.to_logical::(scale_factor); raw.set_width(physical_size.width); raw.set_height(physical_size.height); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 2d20178490..8c1a1b9176 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -71,7 +71,7 @@ impl Window { } pub fn outer_position(&self) -> Result, NotSupportedError> { - Ok(self.canvas.position().to_physical(self.hidpi_factor())) + Ok(self.canvas.position().to_physical(self.scale_factor())) } pub fn inner_position(&self) -> Result, NotSupportedError> { @@ -80,7 +80,7 @@ impl Window { } pub fn set_outer_position(&self, position: Position) { - let position = position.to_logical::(self.hidpi_factor()); + let position = position.to_logical::(self.scale_factor()); self.canvas.set_attribute("position", "fixed"); self.canvas.set_attribute("left", &position.x.to_string()); @@ -119,8 +119,8 @@ impl Window { } #[inline] - pub fn hidpi_factor(&self) -> f64 { - super::backend::hidpi_factor() + pub fn scale_factor(&self) -> f64 { + super::backend::scale_factor() } #[inline] diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index ebcabcef6a..200ba47ba2 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1415,7 +1415,7 @@ unsafe extern "system" fn public_window_callback( // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change // DPI, therefore all applications are closed while DPI is changing. winuser::WM_DPICHANGED => { - use crate::event::WindowEvent::HiDpiFactorChanged; + use crate::event::WindowEvent::DpiChanged; // This message actually provides two DPI values - x and y. However MSDN says that // "you only need to use either the X-axis or the Y-axis value when scaling your @@ -1499,8 +1499,8 @@ unsafe extern "system" fn public_window_callback( let _ = subclass_input.send_event_unbuffered(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: HiDpiFactorChanged { - hidpi_factor: new_dpi_factor, + event: DpiChanged { + scale_factor: new_dpi_factor, new_inner_size: &mut new_physical_inner_size, }, }); diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 0b506dc3ba..7136f35dfe 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -203,7 +203,7 @@ impl MonitorHandle { } #[inline] - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96)) } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 8a89c44ecf..83609a27ca 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -164,7 +164,7 @@ impl Window { #[inline] pub fn set_outer_position(&self, position: Position) { - let (x, y): (i32, i32) = position.to_physical::(self.hidpi_factor()).into(); + let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); let window_state = Arc::clone(&self.window_state); let window = self.window.clone(); @@ -249,7 +249,7 @@ impl Window { #[inline] pub fn set_inner_size(&self, size: Size) { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let (width, height) = size.to_physical::(dpi_factor).into(); let window_state = Arc::clone(&self.window_state); @@ -356,13 +356,13 @@ impl Window { } #[inline] - pub fn hidpi_factor(&self) -> f64 { + pub fn scale_factor(&self) -> f64 { self.window_state.lock().dpi_factor } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - let dpi_factor = self.hidpi_factor(); + let dpi_factor = self.scale_factor(); let (x, y) = position.to_physical::(dpi_factor).into(); let mut point = POINT { x, y }; diff --git a/src/window.rs b/src/window.rs index ae9c41c911..1ee76d4f42 100644 --- a/src/window.rs +++ b/src/window.rs @@ -366,25 +366,25 @@ impl Window { WindowId(self.window.id()) } - /// Returns the DPI factor that can be used to map logical pixels to physical pixels, and vice versa. + /// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa. /// /// See the [`dpi`](crate::dpi) module for more information. /// /// Note that this value can change depending on user action (for example if the window is - /// moved to another screen); as such, tracking `WindowEvent::HiDpiFactorChanged` events is + /// moved to another screen); as such, tracking `WindowEvent::DpiChanged` events is /// the most robust way to track the DPI you need to use to draw. /// /// ## Platform-specific /// - /// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_HIDPI_FACTOR` environment variable. + /// - **X11:** This respects Xft.dpi, and can be overridden using the `WINIT_X11_SCALE_FACTOR` environment variable. /// - **Android:** Always returns 1.0. /// - **iOS:** Can only be called on the main thread. Returns the underlying `UIView`'s /// [`contentScaleFactor`]. /// /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc #[inline] - pub fn hidpi_factor(&self) -> f64 { - self.window.hidpi_factor() + pub fn scale_factor(&self) -> f64 { + self.window.scale_factor() } /// Emits a `WindowEvent::RedrawRequested` event in the associated event loop after all OS From ac69a9c0dc234081477a2e079a05eaaab3cc39be Mon Sep 17 00:00:00 2001 From: Murarth Date: Sat, 4 Jan 2020 10:58:39 -0700 Subject: [PATCH 059/239] Silence warnings about use of deprecated fields --- src/event.rs | 3 +++ src/platform_impl/linux/wayland/keyboard.rs | 3 +++ src/platform_impl/linux/x11/event_processor.rs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/event.rs b/src/event.rs index c0084fd159..117a4c7266 100644 --- a/src/event.rs +++ b/src/event.rs @@ -341,6 +341,7 @@ impl<'a> WindowEvent<'a> { input, is_synthetic, }), + #[allow(deprecated)] CursorMoved { device_id, position, @@ -352,6 +353,7 @@ impl<'a> WindowEvent<'a> { }), CursorEntered { device_id } => Some(CursorEntered { device_id }), CursorLeft { device_id } => Some(CursorLeft { device_id }), + #[allow(deprecated)] MouseWheel { device_id, delta, @@ -363,6 +365,7 @@ impl<'a> WindowEvent<'a> { phase, modifiers, }), + #[allow(deprecated)] MouseInput { device_id, state, diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index ff61b9e377..ed7aa20a15 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -54,6 +54,7 @@ pub fn init_keyboard( }; let vkcode = key_to_vkey(rawkey, keysym); my_sink.send_window_event( + #[allow(deprecated)] WindowEvent::KeyboardInput { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), @@ -96,6 +97,7 @@ pub fn init_keyboard( let state = ElementState::Pressed; let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym); repeat_sink.send_window_event( + #[allow(deprecated)] WindowEvent::KeyboardInput { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, @@ -155,6 +157,7 @@ pub fn init_keyboard( _ => unreachable!(), }; my_sink.send_window_event( + #[allow(deprecated)] WindowEvent::KeyboardInput { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 9cb922d3da..daf9738379 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -561,6 +561,7 @@ impl EventProcessor { let modifiers = self.device_mod_state.modifiers(); + #[allow(deprecated)] callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { @@ -1051,6 +1052,7 @@ impl EventProcessor { let virtual_keycode = events::keysym_to_element(keysym as c_uint); let modifiers = self.device_mod_state.modifiers(); + #[allow(deprecated)] callback(Event::DeviceEvent { device_id, event: DeviceEvent::Key(KeyboardInput { @@ -1203,6 +1205,7 @@ impl EventProcessor { let keysym = wt.xconn.keycode_to_keysym(keycode); let virtual_keycode = events::keysym_to_element(keysym as c_uint); + #[allow(deprecated)] callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { From 7753bbba9427473b0493c0dafcf9b662f414a616 Mon Sep 17 00:00:00 2001 From: Murarth Date: Sat, 4 Jan 2020 11:03:44 -0700 Subject: [PATCH 060/239] Fix examples --- examples/multithreaded.rs | 1 + examples/window_debug.rs | 17 ++++++----------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index ba52ef596d..65e85464ba 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -50,6 +50,7 @@ fn main() { ); } } + #[allow(deprecated)] WindowEvent::KeyboardInput { input: KeyboardInput { diff --git a/examples/window_debug.rs b/examples/window_debug.rs index 0ecd37b398..5f8293f872 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -12,20 +12,18 @@ fn main() { let window = WindowBuilder::new() .with_title("A fantastic window!") - .with_inner_size(LogicalSize::from((100, 100))) + .with_inner_size(LogicalSize::new(100.0, 100.0)) .build(&event_loop) .unwrap(); eprintln!("debugging keys:"); eprintln!(" (E) Enter exclusive fullscreen"); eprintln!(" (F) Toggle borderless fullscreen"); - #[cfg(waiting_for_set_minimized)] eprintln!(" (M) Toggle minimized"); eprintln!(" (Q) Quit event loop"); eprintln!(" (V) Toggle visibility"); eprintln!(" (X) Toggle maximized"); - #[cfg(waiting_for_set_minimized)] let mut minimized = false; let mut maximized = false; let mut visible = true; @@ -43,7 +41,6 @@ fn main() { }), .. } => match key { - #[cfg(waiting_for_set_minimized)] VirtualKeyCode::M => { if minimized { minimized = !minimized; @@ -68,16 +65,15 @@ fn main() { .. } => match key { VirtualKeyCode::E => { - fn area(size: PhysicalSize) -> f64 { + fn area(size: PhysicalSize) -> u32 { size.width * size.height } let monitor = window.current_monitor(); - if let Some(mode) = monitor.video_modes().max_by(|a, b| { - area(a.size()) - .partial_cmp(&area(b.size())) - .expect("NaN in video mode size") - }) { + if let Some(mode) = monitor + .video_modes() + .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) + { window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); } else { eprintln!("no video modes available"); @@ -91,7 +87,6 @@ fn main() { window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } } - #[cfg(waiting_for_set_minimized)] VirtualKeyCode::M => { minimized = !minimized; window.set_minimized(minimized); From 28b82fb9aac602569820f78258eb83aa588fba9e Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 4 Jan 2020 13:36:14 -0500 Subject: [PATCH 061/239] Try to fix iOS build --- src/platform_impl/ios/app_state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 6a3a6126c8..a6f3608b3f 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -803,7 +803,7 @@ pub unsafe fn handle_main_events_cleared() { handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared)); let mut this = AppState::get_mut(); - let mut redraw_events: Vec> = this + let mut redraw_events: Vec = this .main_events_cleared_transition() .into_iter() .map(|window| { @@ -812,7 +812,7 @@ pub unsafe fn handle_main_events_cleared() { .collect(); if !redraw_events.is_empty() { - redraw_events.push(Event::RedrawEventsCleared); + redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared)); } drop(this); From 9b122c380485b061f0583f8650b1d93bf61f33df Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 4 Jan 2020 19:19:17 -0500 Subject: [PATCH 062/239] Update the DPI module docs (#1349) * Update the DPI module docs * Fix HiDpiFactorChanged doc link * Incorporate lokathor and icefox feedback * Adjust documented desktop resolution range * X11 is one of the reasons I use Windows * Address DPI generics and float->int rounding * Revise DPI value statement to better reflect best practices * Address some of freya's feedback * phrasing * Rephrase X11 DPI stuff --- src/dpi.rs | 170 ++++++++++++++++++++++++++++----------------------- src/event.rs | 2 +- 2 files changed, 96 insertions(+), 76 deletions(-) diff --git a/src/dpi.rs b/src/dpi.rs index 66467f08cd..798a678363 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -1,80 +1,106 @@ -//! DPI is important, so read the docs for this module if you don't want to be confused. +//! UI scaling is important, so read the docs for this module if you don't want to be confused. //! -//! Originally, `winit` dealt entirely in physical pixels (excluding unintentional inconsistencies), but now all -//! window-related functions both produce and consume logical pixels. Monitor-related functions still use physical -//! pixels, as do any context-related functions in `glutin`. +//! ## Why should I care about UI scaling? //! -//! If you've never heard of these terms before, then you're not alone, and this documentation will explain the -//! concepts. +//! Modern computer screens don't have a consistent relationship between resolution and size. +//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens +//! normally being less than a quarter the size of their desktop counterparts. What's more, neither +//! desktop nor mobile screens are consistent resolutions within their own size classes - common +//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K +//! and beyond. //! -//! Modern screens have a defined physical resolution, most commonly 1920x1080. Indepedent of that is the amount of -//! space the screen occupies, which is to say, the height and width in millimeters. The relationship between these two -//! measurements is the *pixel density*. Mobile screens require a high pixel density, as they're held close to the -//! eyes. Larger displays also require a higher pixel density, hence the growing presence of 1440p and 4K displays. +//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with +//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen, +//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up +//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially +//! problematic with text rendering, where quarter-sized text becomes a significant legibility +//! problem. //! -//! So, this presents a problem. Let's say we want to render a square 100px button. It will occupy 100x100 of the -//! screen's pixels, which in many cases, seems perfectly fine. However, because this size doesn't account for the -//! screen's dimensions or pixel density, the button's size can vary quite a bit. On a 4K display, it would be unusably -//! small. +//! Failure to account for the scale factor can create a significantly degraded user experience. +//! Most notably, it can make users feel like they have bad eyesight, which will potentially cause +//! them to think about growing elderly, resulting in them having an existential crisis. Once users +//! enter that state, they will no longer be focused on your application. //! -//! That's a description of what happens when the button is 100x100 *physical* pixels. Instead, let's try using 100x100 -//! *logical* pixels. To map logical pixels to physical pixels, we simply multiply by the DPI (dots per inch) factor. -//! On a "typical" desktop display, the scale factor will be 1.0, so 100x100 logical pixels equates to 100x100 physical -//! pixels. However, a 1440p display may have a scale factor of 1.25, so the button is rendered as 125x125 physical pixels. -//! Ideally, the button now has approximately the same perceived size across varying displays. +//! ## How should I handle it? //! -//! Failure to account for the scale factor can create a badly degraded user experience. Most notably, it can make users -//! feel like they have bad eyesight, which will potentially cause them to think about growing elderly, resulting in -//! them entering an existential panic. Once users enter that state, they will no longer be focused on your application. +//! The solution to this problem is to account for the device's *scale factor*. The scale factor is +//! the factor UI elements should be scaled by to be consistent with the rest of the user's system - +//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device +//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`. //! -//! There are two ways to get the scale factor: -//! - You can track the [`DpiChanged`](crate::event::WindowEvent::DpiChanged) event of your -//! windows. This event is sent any time the scale factor changes, either because the window moved to another monitor, -//! or because the user changed the configuration of their screen. -//! - You can also retrieve the scale factor of a monitor by calling -//! [`MonitorHandle::scale_factor`](crate::monitor::MonitorHandle::scale_factor), or the -//! current scale factor applied to a window by calling -//! [`Window::scale_factor`](crate::window::Window::scale_factor), which is roughly equivalent -//! to `window.current_monitor().scale_factor()`. +//! The scale factor correlates with, but no has direct relationship to, the screen's actual DPI +//! (dots per inch). Operating systems used to define the scale factor in terms of the screen's +//! approximate DPI (at the time, 72 pixels per inch), but [Microsoft decided to report that the DPI +//! was roughly 1/3 bigger than the screen's actual DPI (so, 96 pixels per inch) in order to make +//! text more legible][microsoft_dpi]. As a result, the exact DPI as defined by the OS doesn't carry +//! a whole lot of weight when designing cross-platform UIs. Scaled pixels should generally be used +//! as the base unit for on-screen UI measurement, instead of DPI-dependent units such as +//! [points][points] or [picas][picas]. //! -//! Depending on the platform, the window's actual scale factor may only be known after -//! the event loop has started and your window has been drawn once. To properly handle these cases, -//! the most robust way is to monitor the [`DpiChanged`](crate::event::WindowEvent::DpiChanged) -//! event and dynamically adapt your drawing logic to follow the scale factor. +//! ### Position and Size types //! -//! Here's an overview of what sort of scale factors you can expect, and where they come from: -//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the display settings. -//! While users are free to select any option they want, they're only given a selection of "nice" scale factors, i.e. -//! 1.0, 1.25, 1.5... on Windows 7, the scale factor is global and changing it requires logging out. -//! - **macOS:** The buzzword is "retina displays", which have a scale factor of 2.0. Otherwise, the scale factor is 1.0. -//! Intermediate scale factors are never used, thus 1440p displays/etc. aren't properly supported. It's possible for any -//! display to use that 2.0 scale factor, given the use of the command line. -//! - **X11:** On X11, we calcuate the scale factor based on the millimeter dimensions provided by XRandR. This can -//! result in a wide range of possible values, including some interesting ones like 1.0833333333333333. This can be -//! overridden using the `WINIT_X11_SCALE_FACTOR` environment variable, though that's not recommended. -//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always integers (most often 1 or 2). -//! - **iOS:** scale factors are both constant and device-specific on iOS. -//! - **Android:** This feature isn't yet implemented on Android, so the scale factor will always be returned as 1.0. -//! - **Web:** scale factors are handled by the browser and will always be 1.0 for your application. +//! Winit's `Physical(Position|Size)` types correspond with the actual pixels on the device, and the +//! `Logical(Position|Size)` types correspond to the physical pixels divided by the scale factor. +//! All of Winit's functions return physical types, but can take either logical or physical +//! coordinates as input, allowing you to use the most convenient coordinate system for your +//! particular application. //! -//! The window's logical size is conserved across DPI changes, resulting in the physical size changing instead. This -//! may be surprising on X11, but is quite standard elsewhere. Physical size changes always produce a -//! [`Resized`](crate::event::WindowEvent::Resized) event, even on platforms where no resize actually occurs, -//! such as macOS and Wayland. As a result, it's not necessary to separately handle -//! [`DpiChanged`](crate::event::WindowEvent::DpiChanged) if you're only listening for size. +//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the +//! API to have integer precision where appropriate (e.g. most window manipulation functions) and +//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch +//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so +//! will truncate the fractional part of the float, rather than properly round to the nearest +//! integer. Use the provided `cast` function or `From`/`Into` conversions, which handle the +//! rounding properly. Note that precision loss will still occur when rounding from a float to an +//! int, although rounding lessens the problem. //! -//! Your GPU has no awareness of the concept of logical pixels, and unless you like wasting pixel density, your -//! framebuffer's size should be in physical pixels. +//! ### Events //! -//! `winit` will send [`Resized`](crate::event::WindowEvent::Resized) events whenever a window's logical size -//! changes, and [`DpiChanged`](crate::event::WindowEvent::DpiChanged) events -//! whenever the scale factor changes. Receiving either of these events means that the physical size of your window has -//! changed, and you should recompute it using the latest values you received for each. If the logical size and the -//! scale factor change simultaneously, `winit` will send both events together; thus, it's recommended to buffer -//! these events and process them at the end of the queue. +//! Winit will dispatch a [`DpiChanged`](crate::event::WindowEvent::DpiChanged) +//! event whenever a window's scale factor has changed. This can happen if the user drags their +//! window from a standard-resolution monitor to a high-DPI monitor, or if the user changes their +//! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how +//! the platform changes the window's size to reflect the new scale factor. If a window hasn't +//! received a [`DpiChanged`](crate::event::WindowEvent::DpiChanged) event, +//! then its scale factor is `1.0`. //! -//! If you never received any [`DpiChanged`](crate::event::WindowEvent::DpiChanged) events, -//! then your window's scale factor is 1. +//! ## How is the scale factor calculated? +//! +//! Scale factor is calculated differently on different platforms: +//! +//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the +//! display settings. While users are free to select any option they want, they're only given a +//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is +//! global and changing it requires logging out. See [this article][windows_1] for technical +//! details. +//! - **macOS:** "retina displays" have a scale factor of 2.0. Otherwise, the scale factor is 1.0. +//! Intermediate scale factors are never used. It's possible for any display to use that 2.0 scale +//! factor, given the use of the command line. +//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit +//! currently uses a three-pronged approach: +//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present. +//! + If not present, use the value set in `Xft.dpi` in Xresources. +//! + Otherwise, calcuate the scale factor based on the millimeter monitor dimensions provided by XRandR. +//! +//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the +//! XRandR scaling method. Generally speaking, you should try to configure the standard system +//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`. +//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always +//! integers (most often 1 or 2). +//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range +//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more +//! information. +//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the +//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. +//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. +//! +//! [microsoft_dpi]: https://blogs.msdn.microsoft.com/fontblog/2005/11/08/where-does-96-dpi-come-from-in-windows/ +//! [points]: https://en.wikipedia.org/wiki/Point_(typography) +//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) +//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows +//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html +//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ +//! [android_1]: https://developer.android.com/training/multiscreen/screendensities pub trait Pixel: Copy + Into { fn from_f64(f: f64) -> Self; @@ -136,9 +162,9 @@ pub fn validate_scale_factor(dpi_factor: f64) -> bool { /// A position represented in logical pixels. /// -/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, -/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which -/// does the rounding for you. +/// The position is stored as floats, so please be careful. Casting floats to integers truncates the +/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>` +/// implementation is provided which does the rounding for you. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct LogicalPosition

{ @@ -204,10 +230,6 @@ impl Into<[X; 2]> for LogicalPosition

{ } /// A position represented in physical pixels. -/// -/// The position is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, -/// which can cause noticable issues. To help with that, an `Into<(i32, i32)>` implementation is provided which -/// does the rounding for you. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PhysicalPosition

{ @@ -273,10 +295,6 @@ impl Into<[X; 2]> for PhysicalPosition

{ } /// A size represented in logical pixels. -/// -/// The size is stored as floats, so please be careful. Casting floats to integers truncates the fractional part, -/// which can cause noticable issues. To help with that, an `Into<(u32, u32)>` implementation is provided which -/// does the rounding for you. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct LogicalSize

{ @@ -400,6 +418,7 @@ impl Into<[X; 2]> for PhysicalSize

{ } } +/// A size that's either physical or logical. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Size { @@ -441,6 +460,7 @@ impl From> for Size { } } +/// A position that's either physical or logical. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Position { diff --git a/src/event.rs b/src/event.rs index 117a4c7266..4a61f3192e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -304,7 +304,7 @@ pub enum WindowEvent<'a> { /// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested /// by the OS, but it can be changed to any value. /// - /// For more information about DPI in general, see the [`dpi`](dpi/index.html) module. + /// For more information about DPI in general, see the [`dpi`](crate::dpi) module. DpiChanged { scale_factor: f64, new_inner_size: &'a mut PhysicalSize, From a1b8d265d0c6446299a314000c4b08b3b0b06fa4 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 5 Jan 2020 16:34:37 -0500 Subject: [PATCH 063/239] Refine DPI docs --- src/dpi.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/dpi.rs b/src/dpi.rs index 798a678363..e800a10445 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -28,14 +28,10 @@ //! for example, a button that's normally 50 pixels across would be 100 pixels across on a device //! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`. //! -//! The scale factor correlates with, but no has direct relationship to, the screen's actual DPI -//! (dots per inch). Operating systems used to define the scale factor in terms of the screen's -//! approximate DPI (at the time, 72 pixels per inch), but [Microsoft decided to report that the DPI -//! was roughly 1/3 bigger than the screen's actual DPI (so, 96 pixels per inch) in order to make -//! text more legible][microsoft_dpi]. As a result, the exact DPI as defined by the OS doesn't carry -//! a whole lot of weight when designing cross-platform UIs. Scaled pixels should generally be used -//! as the base unit for on-screen UI measurement, instead of DPI-dependent units such as -//! [points][points] or [picas][picas]. +//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's +//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's +//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather +//! than any DPI-dependent units. //! //! ### Position and Size types //! @@ -94,7 +90,6 @@ //! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. //! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. //! -//! [microsoft_dpi]: https://blogs.msdn.microsoft.com/fontblog/2005/11/08/where-does-96-dpi-come-from-in-windows/ //! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) //! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows From 3aa3880e6982e57e5682562f3c6a7295b8c2e806 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 5 Jan 2020 16:57:32 -0500 Subject: [PATCH 064/239] Add changelog entry --- CHANGELOG.md | 10 ++++++++-- src/dpi.rs | 4 ++-- src/event.rs | 4 ++-- src/platform_impl/ios/app_state.rs | 2 +- src/platform_impl/ios/window.rs | 2 +- src/platform_impl/linux/wayland/event_loop.rs | 2 +- src/platform_impl/linux/x11/event_processor.rs | 4 ++-- src/platform_impl/macos/app_state.rs | 2 +- src/platform_impl/windows/event_loop.rs | 4 ++-- src/window.rs | 2 +- 10 files changed, 21 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7684b9c235..242655135b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Unreleased - On X11, fix `ModifiersChanged` emitting incorrect modifier change events +- **Breaking**: Overhaul how Winit handles DPI: + + Window functions and events now return `PhysicalSize` instead of `LogicalSize`. + + Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types. + + `hidpi_factor` has been renamed to `scale_factor`. + + `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS + resizes the window in response to the change. + + On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`. + + `Size` and `Position` types are generic over their exact pixel type. # 0.20.0 Alpha 6 (2020-01-03) @@ -47,8 +55,6 @@ - On X11, generate synthetic key events for keys held when a window gains or loses focus. - On X11, issue a `CursorMoved` event when a `Touch` event occurs, as X11 implicitly moves the cursor for such events. -- Rename `hidpi_factor` to `scale_factor` -- On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR` # 0.20.0 Alpha 4 (2019-10-18) diff --git a/src/dpi.rs b/src/dpi.rs index e800a10445..2057d01283 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -52,12 +52,12 @@ //! //! ### Events //! -//! Winit will dispatch a [`DpiChanged`](crate::event::WindowEvent::DpiChanged) +//! Winit will dispatch a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) //! event whenever a window's scale factor has changed. This can happen if the user drags their //! window from a standard-resolution monitor to a high-DPI monitor, or if the user changes their //! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how //! the platform changes the window's size to reflect the new scale factor. If a window hasn't -//! received a [`DpiChanged`](crate::event::WindowEvent::DpiChanged) event, +//! received a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) event, //! then its scale factor is `1.0`. //! //! ## How is the scale factor calculated? diff --git a/src/event.rs b/src/event.rs index 4a61f3192e..9f30e8811a 100644 --- a/src/event.rs +++ b/src/event.rs @@ -305,7 +305,7 @@ pub enum WindowEvent<'a> { /// by the OS, but it can be changed to any value. /// /// For more information about DPI in general, see the [`dpi`](crate::dpi) module. - DpiChanged { + ScaleFactorChanged { scale_factor: f64, new_inner_size: &'a mut PhysicalSize, }, @@ -397,7 +397,7 @@ impl<'a> WindowEvent<'a> { }), Touch(touch) => Some(Touch(touch)), ThemeChanged(theme) => Some(ThemeChanged(theme)), - DpiChanged { .. } => None, + ScaleFactorChanged { .. } => None, } } } diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index a6f3608b3f..0d388c509a 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -865,7 +865,7 @@ fn handle_hidpi_proxy( let new_inner_size = &mut size; let event = Event::WindowEvent { window_id: RootWindowId(window_id.into()), - event: WindowEvent::DpiChanged { + event: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size, }, diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 2ae4866485..a5d3e89337 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -398,7 +398,7 @@ impl Window { }; app_state::set_key_window(window); - // Like the Windows and macOS backends, we send a `DpiChanged` and `Resized` + // Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized` // event on window creation if the DPI factor != 1.0 let dpi_factor: CGFloat = msg_send![view, contentScaleFactor]; let scale_factor: f64 = dpi_factor.into(); diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 3bd0e4cd4a..1ceedf19af 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -734,7 +734,7 @@ impl EventLoop { callback(Event::WindowEvent { window_id, - event: WindowEvent::DpiChanged { + event: WindowEvent::ScaleFactorChanged { scale_factor: dpi, new_inner_size: &mut new_inner_size, }, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index daf9738379..0becc2e85e 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -427,7 +427,7 @@ impl EventProcessor { callback(Event::WindowEvent { window_id, - event: WindowEvent::DpiChanged { + event: WindowEvent::ScaleFactorChanged { scale_factor: new_scale_factor, new_inner_size: &mut new_inner_size, }, @@ -1148,7 +1148,7 @@ impl EventProcessor { callback(Event::WindowEvent { window_id, - event: WindowEvent::DpiChanged { + event: WindowEvent::ScaleFactorChanged { scale_factor: new_monitor.scale_factor, new_inner_size: &mut new_inner_size, }, diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 6625d517a8..1cb4e50558 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -192,7 +192,7 @@ impl Handler { let new_inner_size = &mut size; let event = Event::WindowEvent { window_id: WindowId(get_window_id(*ns_window)), - event: WindowEvent::DpiChanged { + event: WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size, }, diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 200ba47ba2..53e937a04e 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1415,7 +1415,7 @@ unsafe extern "system" fn public_window_callback( // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change // DPI, therefore all applications are closed while DPI is changing. winuser::WM_DPICHANGED => { - use crate::event::WindowEvent::DpiChanged; + use crate::event::WindowEvent::ScaleFactorChanged; // This message actually provides two DPI values - x and y. However MSDN says that // "you only need to use either the X-axis or the Y-axis value when scaling your @@ -1499,7 +1499,7 @@ unsafe extern "system" fn public_window_callback( let _ = subclass_input.send_event_unbuffered(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: DpiChanged { + event: ScaleFactorChanged { scale_factor: new_dpi_factor, new_inner_size: &mut new_physical_inner_size, }, diff --git a/src/window.rs b/src/window.rs index 1ee76d4f42..073ed2fd8f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -371,7 +371,7 @@ impl Window { /// See the [`dpi`](crate::dpi) module for more information. /// /// Note that this value can change depending on user action (for example if the window is - /// moved to another screen); as such, tracking `WindowEvent::DpiChanged` events is + /// moved to another screen); as such, tracking `WindowEvent::ScaleFactorChanged` events is /// the most robust way to track the DPI you need to use to draw. /// /// ## Platform-specific From ec1ae68cfc9a14391afa2380a7f3a4f8f32dc813 Mon Sep 17 00:00:00 2001 From: Murarth Date: Fri, 3 Jan 2020 23:14:11 -0700 Subject: [PATCH 065/239] X11: Properly update window size constraints on DPI change (#1356) * In `WindowBuilderExtUnix` methods, use `Size` instead of `LogicalSize` --- src/platform/unix.rs | 10 ++--- src/platform_impl/linux/mod.rs | 4 +- .../linux/x11/event_processor.rs | 2 + src/platform_impl/linux/x11/window.rs | 43 ++++++++++++------- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/platform/unix.rs b/src/platform/unix.rs index fbf8a920a7..0484d71e2d 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -5,7 +5,7 @@ use std::{os::raw, ptr, sync::Arc}; use smithay_client_toolkit::window::{ButtonState, Theme}; use crate::{ - dpi::LogicalSize, + dpi::Size, event_loop::{EventLoop, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, @@ -380,9 +380,9 @@ pub trait WindowBuilderExtUnix { /// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11. fn with_gtk_theme_variant(self, variant: String) -> Self; /// Build window with resize increment hint. Only implemented on X11. - fn with_resize_increments(self, increments: LogicalSize) -> Self; + fn with_resize_increments>(self, increments: S) -> Self; /// Build window with base size hint. Only implemented on X11. - fn with_base_size(self, base_size: LogicalSize) -> Self; + fn with_base_size>(self, base_size: S) -> Self; /// Build window with a given application ID. It should match the `.desktop` file distributed with /// your program. Only relevant on Wayland. @@ -431,13 +431,13 @@ impl WindowBuilderExtUnix for WindowBuilder { } #[inline] - fn with_resize_increments(mut self, increments: LogicalSize) -> Self { + fn with_resize_increments>(mut self, increments: S) -> Self { self.platform_specific.resize_increments = Some(increments.into()); self } #[inline] - fn with_base_size(mut self, base_size: LogicalSize) -> Self { + fn with_base_size>(mut self, base_size: S) -> Self { self.platform_specific.base_size = Some(base_size.into()); self } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index e3bfe78f07..dfb18341fd 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -34,8 +34,8 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; pub struct PlatformSpecificWindowBuilderAttributes { pub visual_infos: Option, pub screen_id: Option, - pub resize_increments: Option<(u32, u32)>, - pub base_size: Option<(u32, u32)>, + pub resize_increments: Option, + pub base_size: Option, pub class: Option<(String, String)>, pub override_redirect: bool, pub x11_window_types: Vec, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 0becc2e85e..014b9b02d4 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -420,6 +420,7 @@ impl EventProcessor { new_scale_factor, width, height, + &shared_state_lock, ); let old_inner_size = PhysicalSize::new(width, height); @@ -1134,6 +1135,7 @@ impl EventProcessor { new_monitor.scale_factor, width, height, + &*window.shared_state.lock(), ); let window_id = crate::window::WindowId( diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index dcbfd59c31..d8bb99e83f 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -47,6 +47,8 @@ pub struct SharedState { pub frame_extents: Option, pub min_inner_size: Option, pub max_inner_size: Option, + pub resize_increments: Option, + pub base_size: Option, pub visibility: Visibility, } @@ -83,6 +85,8 @@ impl SharedState { frame_extents: None, min_inner_size: None, max_inner_size: None, + resize_increments: None, + base_size: None, }) } } @@ -235,7 +239,7 @@ impl UnownedWindow { ) }; - let window = UnownedWindow { + let mut window = UnownedWindow { xconn: Arc::clone(xconn), xwindow, root, @@ -324,6 +328,7 @@ impl UnownedWindow { let mut max_inner_size = window_attrs .max_inner_size .map(|size| size.to_physical::(dpi_factor)); + if !window_attrs.resizable { if util::wm_name_is_one_of(&["Xfwm4"]) { warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4"); @@ -331,9 +336,11 @@ impl UnownedWindow { max_inner_size = Some(dimensions.into()); min_inner_size = Some(dimensions.into()); - let mut shared_state_lock = window.shared_state.lock(); - shared_state_lock.min_inner_size = window_attrs.min_inner_size; - shared_state_lock.max_inner_size = window_attrs.max_inner_size; + let mut shared_state = window.shared_state.get_mut(); + shared_state.min_inner_size = window_attrs.min_inner_size; + shared_state.max_inner_size = window_attrs.max_inner_size; + shared_state.resize_increments = pl_attribs.resize_increments; + shared_state.base_size = pl_attribs.base_size; } } @@ -341,8 +348,16 @@ impl UnownedWindow { normal_hints.set_size(Some(dimensions)); normal_hints.set_min_size(min_inner_size.map(Into::into)); normal_hints.set_max_size(max_inner_size.map(Into::into)); - normal_hints.set_resize_increments(pl_attribs.resize_increments); - normal_hints.set_base_size(pl_attribs.base_size); + normal_hints.set_resize_increments( + pl_attribs + .resize_increments + .map(|size| size.to_physical::(dpi_factor).into()), + ); + normal_hints.set_base_size( + pl_attribs + .base_size + .map(|size| size.to_physical::(dpi_factor).into()), + ); xconn.set_normal_hints(window.xwindow, normal_hints).queue(); } @@ -1086,18 +1101,16 @@ impl UnownedWindow { new_dpi_factor: f64, width: u32, height: u32, + shared_state: &SharedState, ) -> (u32, u32) { let scale_factor = new_dpi_factor / old_dpi_factor; self.update_normal_hints(|normal_hints| { - let dpi_adjuster = |(width, height): (u32, u32)| -> (u32, u32) { - let new_width = width as f64 * scale_factor; - let new_height = height as f64 * scale_factor; - (new_width.round() as u32, new_height.round() as u32) - }; - let max_size = normal_hints.get_max_size().map(&dpi_adjuster); - let min_size = normal_hints.get_min_size().map(&dpi_adjuster); - let resize_increments = normal_hints.get_resize_increments().map(&dpi_adjuster); - let base_size = normal_hints.get_base_size().map(&dpi_adjuster); + let dpi_adjuster = + |size: Size| -> (u32, u32) { size.to_physical::(new_dpi_factor).into() }; + let max_size = shared_state.max_inner_size.map(&dpi_adjuster); + let min_size = shared_state.min_inner_size.map(&dpi_adjuster); + let resize_increments = shared_state.resize_increments.map(&dpi_adjuster); + let base_size = shared_state.base_size.map(&dpi_adjuster); normal_hints.set_max_size(max_size); normal_hints.set_min_size(min_size); normal_hints.set_resize_increments(resize_increments); From 627a127f1b96608649eee5ac474bf762daffbc3a Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 5 Jan 2020 17:11:25 -0500 Subject: [PATCH 066/239] we did it bois (#1352) --- CHANGELOG.md | 4 +++- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 242655135b..937995c660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 0.20.0 (2020-01-05) + - On X11, fix `ModifiersChanged` emitting incorrect modifier change events - **Breaking**: Overhaul how Winit handles DPI: + Window functions and events now return `PhysicalSize` instead of `LogicalSize`. @@ -8,7 +10,7 @@ + `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS resizes the window in response to the change. + On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`. - + `Size` and `Position` types are generic over their exact pixel type. + + `Size` and `Position` types are now generic over their exact pixel type. # 0.20.0 Alpha 6 (2020-01-03) diff --git a/Cargo.toml b/Cargo.toml index 5e198f5b00..be6b943099 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.20.0-alpha6" +version = "0.20.0" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index ff5b59020b..b4c673a11c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ```toml [dependencies] -winit = "0.20.0-alpha6" +winit = "0.20.0" ``` ## [Documentation](https://docs.rs/winit) From 6a330a2894873d29fbbfdeebfc1a215577213996 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 6 Jan 2020 15:28:58 -0500 Subject: [PATCH 067/239] On Windows, fix bug where RedrawRequested would only get emitted every other iteration of the event loop (#1366) * Fix bug causing RedrawRequested events to only get emitted every other iteration of the event loop. * Initialize simple_logger in examples. This PR's primary bug was discovered because a friend of mine reported that winit was emitting concerning log messages, which I'd never seen since none of the examples print out the log messages. This addresses that, to hopefully reduce the chance of bugs going unnoticed in the future. * Add changelog entry * Format --- CHANGELOG.md | 2 + Cargo.toml | 2 +- examples/cursor.rs | 1 + examples/cursor_grab.rs | 1 + examples/custom_events.rs | 1 + examples/fullscreen.rs | 1 + examples/handling_close.rs | 1 + examples/min_max_size.rs | 1 + examples/minimize.rs | 1 + examples/monitor_list.rs | 1 + examples/multithreaded.rs | 4 +- examples/multiwindow.rs | 1 + examples/request_redraw.rs | 1 + examples/resizable.rs | 1 + examples/timer.rs | 1 + examples/transparent.rs | 1 + examples/video_modes.rs | 1 + examples/window.rs | 1 + examples/window_debug.rs | 1 + examples/window_icon.rs | 2 + examples/window_run_return.rs | 1 + src/platform_impl/windows/event_loop.rs | 125 ++++++---- .../windows/event_loop/runner.rs | 225 +++++++++++++++--- src/platform_impl/windows/util.rs | 32 +++ src/platform_impl/windows/window.rs | 36 +-- 25 files changed, 323 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 937995c660..6548f58136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. + # 0.20.0 (2020-01-05) - On X11, fix `ModifiersChanged` emitting incorrect modifier change events diff --git a/Cargo.toml b/Cargo.toml index be6b943099..a1948b921c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ bitflags = "1" [dev-dependencies] image = "0.21" -env_logger = "0.5" +simple_logger = "1" [target.'cfg(target_os = "android")'.dependencies.android_glue] version = "0.2" diff --git a/examples/cursor.rs b/examples/cursor.rs index 70c2ec0320..de45adae33 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -5,6 +5,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index dd25179c1d..5ed193ecbe 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -5,6 +5,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/custom_events.rs b/examples/custom_events.rs index 3ed0c8b2e8..c59299cab4 100644 --- a/examples/custom_events.rs +++ b/examples/custom_events.rs @@ -11,6 +11,7 @@ fn main() { Timer, } + simple_logger::init().unwrap(); let event_loop = EventLoop::::with_user_event(); let _window = WindowBuilder::new() diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index d4b83bb0f7..4cdb7b63ed 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -5,6 +5,7 @@ use winit::monitor::{MonitorHandle, VideoMode}; use winit::window::{Fullscreen, WindowBuilder}; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: "); diff --git a/examples/handling_close.rs b/examples/handling_close.rs index d67c0046a7..cbd5705369 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -5,6 +5,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let _window = WindowBuilder::new() diff --git a/examples/min_max_size.rs b/examples/min_max_size.rs index 8a5b6699a0..f4fd00c1bb 100644 --- a/examples/min_max_size.rs +++ b/examples/min_max_size.rs @@ -6,6 +6,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); diff --git a/examples/minimize.rs b/examples/minimize.rs index cce72de316..871cfe2edb 100644 --- a/examples/minimize.rs +++ b/examples/minimize.rs @@ -5,6 +5,7 @@ use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/monitor_list.rs b/examples/monitor_list.rs index a6b24d2967..66f48bafeb 100644 --- a/examples/monitor_list.rs +++ b/examples/monitor_list.rs @@ -1,6 +1,7 @@ use winit::{event_loop::EventLoop, window::WindowBuilder}; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 65e85464ba..60f9d802de 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -1,7 +1,5 @@ #[cfg(not(target_arch = "wasm32"))] fn main() { - extern crate env_logger; - use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use winit::{ @@ -14,7 +12,7 @@ fn main() { const WINDOW_COUNT: usize = 3; const WINDOW_SIZE: PhysicalSize = PhysicalSize::new(600, 400); - env_logger::init(); + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); for _ in 0..WINDOW_COUNT { diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 57a20c5bab..01c91bb646 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -6,6 +6,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let mut windows = HashMap::new(); diff --git a/examples/request_redraw.rs b/examples/request_redraw.rs index ea6760d65e..5d761ae479 100644 --- a/examples/request_redraw.rs +++ b/examples/request_redraw.rs @@ -5,6 +5,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/resizable.rs b/examples/resizable.rs index 2ddb8b25e9..6a90391937 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -6,6 +6,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let mut resizable = false; diff --git a/examples/timer.rs b/examples/timer.rs index e1e3f29097..52a1444ec2 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -7,6 +7,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let _window = WindowBuilder::new() diff --git a/examples/transparent.rs b/examples/transparent.rs index a1fdefe93a..78e3c4da33 100644 --- a/examples/transparent.rs +++ b/examples/transparent.rs @@ -5,6 +5,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/video_modes.rs b/examples/video_modes.rs index f923fa9202..366c519591 100644 --- a/examples/video_modes.rs +++ b/examples/video_modes.rs @@ -1,6 +1,7 @@ use winit::event_loop::EventLoop; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let monitor = event_loop.primary_monitor(); diff --git a/examples/window.rs b/examples/window.rs index 331ee98aa4..c028a50f3f 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -5,6 +5,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/window_debug.rs b/examples/window_debug.rs index 5f8293f872..f6e960a794 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -8,6 +8,7 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/window_icon.rs b/examples/window_icon.rs index 86ea9556ca..b7c84679bb 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -7,6 +7,8 @@ use winit::{ }; fn main() { + simple_logger::init().unwrap(); + // You'll have to choose an icon size at your own discretion. On X11, the desired size varies // by WM, and on Windows, you still have to account for screen scaling. Here we use 32px, // since it seems to work well enough in most cases. Be careful about going too high, or diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index 703c12eee5..bde7f6a17e 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -18,6 +18,7 @@ fn main() { }; let mut event_loop = EventLoop::new(); + simple_logger::init().unwrap(); let _window = WindowBuilder::new() .with_title("A fantastic window!") .build(&event_loop) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 53e937a04e..8d90e24766 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -100,13 +100,9 @@ pub(crate) struct SubclassInput { } impl SubclassInput { - unsafe fn send_event(&self, event: Event<'static, T>) { + unsafe fn send_event(&self, event: Event<'_, T>) { self.event_loop_runner.send_event(event); } - - unsafe fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> { - self.event_loop_runner.send_event_unbuffered(event) - } } struct ThreadMsgTargetSubclassInput { @@ -116,7 +112,7 @@ struct ThreadMsgTargetSubclassInput { } impl ThreadMsgTargetSubclassInput { - unsafe fn send_event(&self, event: Event<'static, T>) { + unsafe fn send_event(&self, event: Event<'_, T>) { self.event_loop_runner.send_event(event); } } @@ -216,28 +212,57 @@ impl EventLoop { unsafe { let mut msg = mem::zeroed(); - let mut msg_unprocessed = false; + let mut unread_message_exists = false; 'main: loop { + if let Err(payload) = runner.take_panic_error() { + runner.destroy_runner(); + panic::resume_unwind(payload); + } + runner.new_events(); loop { - if !msg_unprocessed { + if !unread_message_exists { if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) { break; } } + if msg.message == winuser::WM_PAINT { + unread_message_exists = true; + break; + } winuser::TranslateMessage(&mut msg); winuser::DispatchMessageW(&mut msg); - msg_unprocessed = false; + unread_message_exists = false; } - runner.events_cleared(); - if let Err(payload) = runner.take_panic_error() { - runner.destroy_runner(); - panic::resume_unwind(payload); + runner.main_events_cleared(); + loop { + if !unread_message_exists { + if 0 == winuser::PeekMessageW( + &mut msg, + ptr::null_mut(), + winuser::WM_PAINT, + winuser::WM_PAINT, + 1, + ) { + break; + } + } + + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + + unread_message_exists = false; + } + if runner.redraw_events_cleared().events_buffered() { + if runner.control_flow() == ControlFlow::Exit { + break 'main; + } + continue; } - if !msg_unprocessed { + if !unread_message_exists { let control_flow = runner.control_flow(); match control_flow { ControlFlow::Exit => break 'main, @@ -245,7 +270,7 @@ impl EventLoop { if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { break 'main; } - msg_unprocessed = true; + unread_message_exists = true; } ControlFlow::WaitUntil(resume_time) => { wait_until_time_or_msg(resume_time); @@ -651,6 +676,7 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_PAINT => { + subclass_input.event_loop_runner.main_events_cleared(); subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); commctrl::DefSubclassProc(window, msg, wparam, lparam) } @@ -1497,7 +1523,7 @@ unsafe extern "system" fn public_window_callback( false => old_physical_inner_size, }; - let _ = subclass_input.send_event_unbuffered(Event::WindowEvent { + let _ = subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ScaleFactorChanged { scale_factor: new_dpi_factor, @@ -1697,44 +1723,49 @@ unsafe extern "system" fn thread_event_target_callback( let in_modal_loop = subclass_input.event_loop_runner.in_modal_loop(); if in_modal_loop { let mut msg = mem::zeroed(); - loop { - if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { - break; + if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { + if msg.message != 0 && msg.message != winuser::WM_PAINT { + queue_call_again(); + return 0; } - // Clear all paint/timer messages from the queue before sending the events cleared message. - match msg.message { - // Flush the event queue of WM_PAINT messages. - winuser::WM_PAINT | winuser::WM_TIMER => { - // Remove the message from the message queue. - winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1); - - if msg.hwnd != window { - winuser::TranslateMessage(&mut msg); - winuser::DispatchMessageW(&mut msg); - } + + subclass_input.event_loop_runner.main_events_cleared(); + loop { + if 0 == winuser::PeekMessageW( + &mut msg, + ptr::null_mut(), + winuser::WM_PAINT, + winuser::WM_PAINT, + 1, + ) { + break; } - // If the message isn't one of those three, it may be handled by the modal - // loop so we should return control flow to it. - _ => { - queue_call_again(); - return 0; + + if msg.hwnd != window { + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); } } } + // we don't borrow until here because TODO SAFETY let runner = &subclass_input.event_loop_runner; - runner.events_cleared(); - match runner.control_flow() { - // Waiting is handled by the modal loop. - ControlFlow::Exit | ControlFlow::Wait => runner.new_events(), - ControlFlow::WaitUntil(resume_time) => { - wait_until_time_or_msg(resume_time); - runner.new_events(); - queue_call_again(); - } - ControlFlow::Poll => { - runner.new_events(); - queue_call_again(); + if runner.redraw_events_cleared().events_buffered() { + queue_call_again(); + runner.new_events(); + } else { + match runner.control_flow() { + // Waiting is handled by the modal loop. + ControlFlow::Exit | ControlFlow::Wait => runner.new_events(), + ControlFlow::WaitUntil(resume_time) => { + wait_until_time_or_msg(resume_time); + runner.new_events(); + queue_call_again(); + } + ControlFlow::Poll => { + runner.new_events(); + queue_call_again(); + } } } } diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index db807caca2..ce376eb28c 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -3,16 +3,17 @@ use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::R use winapi::{shared::windef::HWND, um::winuser}; use crate::{ - event::{Event, StartCause}, + dpi::PhysicalSize, + event::{Event, StartCause, WindowEvent}, event_loop::ControlFlow, - platform_impl::platform::event_loop::EventLoop, + platform_impl::platform::{event_loop::EventLoop, util}, window::WindowId, }; pub(crate) type EventLoopRunnerShared = Rc>; pub(crate) struct ELRShared { runner: RefCell>>, - buffer: RefCell>>, + buffer: RefCell>>, redraw_buffer: Rc>>, } struct EventLoopRunner { @@ -26,6 +27,63 @@ struct EventLoopRunner { } pub type PanicError = Box; +pub enum BufferedEvent { + Event(Event<'static, T>), + ScaleFactorChanged(WindowId, f64, PhysicalSize), +} + +#[must_use] +#[derive(Debug, Clone, Copy)] +pub enum AreEventsBuffered { + EventsBuffered, + ReadyToSleep, +} + +impl AreEventsBuffered { + pub fn events_buffered(&self) -> bool { + match self { + Self::EventsBuffered => true, + Self::ReadyToSleep => false, + } + } +} + +impl BufferedEvent { + pub fn from_event(event: Event<'_, T>) -> BufferedEvent { + match event { + Event::WindowEvent { + event: + WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + }, + window_id, + } => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size), + event => BufferedEvent::Event(event.to_static().unwrap()), + } + } + + pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) { + match self { + Self::Event(event) => dispatch(event), + Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => { + dispatch(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); + util::set_inner_size_physical( + (window_id.0).0, + new_inner_size.width as _, + new_inner_size.height as _, + ); + } + } + } +} + impl ELRShared { pub(crate) fn new() -> ELRShared { ELRShared { @@ -45,9 +103,7 @@ impl ELRShared { loop { let event = self.buffer.borrow_mut().pop_front(); match event { - Some(e) => { - runner.process_event(e); - } + Some(e) => e.dispatch_event(|e| runner.process_event(e)), None => break, } } @@ -63,35 +119,65 @@ impl ELRShared { let mut runner_ref = self.runner.borrow_mut(); if let Some(ref mut runner) = *runner_ref { runner.new_events(); + loop { + let buffered_event_opt = self.buffer.borrow_mut().pop_front(); + match buffered_event_opt { + Some(e) => e.dispatch_event(|e| runner.process_event(e)), + None => break, + } + } } } - pub(crate) unsafe fn send_event(&self, event: Event<'static, T>) { - if let Err(event) = self.send_event_unbuffered(event) { - // If the runner is already borrowed, we're in the middle of an event loop invocation. Add - // the event to a buffer to be processed later. - self.buffer_event(event); + pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) { + let handling_redraw = self + .runner + .borrow() + .as_ref() + .map(|r| RunnerState::HandlingRedraw == r.runner_state) + .unwrap_or(false); + let mut send = None; + if handling_redraw { + if let Event::RedrawRequested(_) = event { + send = Some(event); + } else { + self.buffer_event(event); + } + } else { + send = Some(event); + } + if let Some(event) = send { + if let Err(event) = self.send_event_unbuffered(event) { + // If the runner is already borrowed, we're in the middle of an event loop invocation. Add + // the event to a buffer to be processed later. + self.buffer_event(event); + } } } - pub(crate) unsafe fn send_event_unbuffered<'e>( - &self, - event: Event<'e, T>, - ) -> Result<(), Event<'e, T>> { + unsafe fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> { if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { if let Some(ref mut runner) = *runner_ref { runner.process_event(event); - // Dispatch any events that were buffered during the call to `process_event`. - loop { - // We do this instead of using a `while let` loop because if we use a `while let` - // loop the reference returned `borrow_mut()` doesn't get dropped until the end - // of the loop's body and attempts to add events to the event buffer while in - // `process_event` will fail. - let buffered_event_opt = self.buffer.borrow_mut().pop_front(); - match buffered_event_opt { - Some(event) => runner.process_event(event), - None => break, + let handling_redraw = if let RunnerState::HandlingRedraw = runner.runner_state { + true + } else { + false + }; + + if !handling_redraw { + // Dispatch any events that were buffered during the call to `process_event`. + loop { + // We do this instead of using a `while let` loop because if we use a `while let` + // loop the reference returned `borrow_mut()` doesn't get dropped until the end + // of the loop's body and attempts to add events to the event buffer while in + // `process_event` will fail. + let buffered_event_opt = self.buffer.borrow_mut().pop_front(); + match buffered_event_opt { + Some(e) => e.dispatch_event(|e| runner.process_event(e)), + None => break, + } } } @@ -111,10 +197,21 @@ impl ELRShared { } } - pub(crate) fn events_cleared(&self) { + pub(crate) fn main_events_cleared(&self) { + let mut runner_ref = self.runner.borrow_mut(); + if let Some(ref mut runner) = *runner_ref { + runner.main_events_cleared(); + } + } + + pub(crate) fn redraw_events_cleared(&self) -> AreEventsBuffered { let mut runner_ref = self.runner.borrow_mut(); if let Some(ref mut runner) = *runner_ref { - runner.events_cleared(); + runner.redraw_events_cleared(); + } + match self.buffer.borrow().len() { + 0 => AreEventsBuffered::ReadyToSleep, + _ => AreEventsBuffered::EventsBuffered, } } @@ -152,12 +249,15 @@ impl ELRShared { } } - fn buffer_event(&self, event: Event<'static, T>) { + fn buffer_event(&self, event: Event<'_, T>) { match event { Event::RedrawRequested(window_id) => { self.redraw_buffer.borrow_mut().push_back(window_id) } - _ => self.buffer.borrow_mut().push_back(event), + _ => self + .buffer + .borrow_mut() + .push_back(BufferedEvent::from_event(event)), } } } @@ -317,16 +417,20 @@ impl EventLoopRunner { (RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => { self.call_event_handler(event) } - (_, Event::RedrawRequested(_)) => { - self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; + (RunnerState::New, Event::RedrawRequested(_)) + | (RunnerState::Idle(..), Event::RedrawRequested(_)) => { + self.new_events(); + self.main_events_cleared(); self.call_event_handler(event); } + (_, Event::RedrawRequested(_)) => { + panic!("redraw event in non-redraw phase"); + } (RunnerState::HandlingRedraw, _) => { - warn!("Non-redraw event dispatched durning redraw phase"); - self.events_cleared(); - self.new_events(); - self.call_event_handler(event); + panic!( + "Non-redraw event dispatched durning redraw phase: {:?}", + event.map_nonuser_event::<()>().ok() + ); } (_, _) => { self.runner_state = RunnerState::HandlingEvents; @@ -345,17 +449,64 @@ impl EventLoopRunner { } } - fn events_cleared(&mut self) { + fn main_events_cleared(&mut self) { + match self.runner_state { + // If we were handling events, send the EventsCleared message. + RunnerState::HandlingEvents => { + self.call_event_handler(Event::MainEventsCleared); + self.runner_state = RunnerState::HandlingRedraw; + } + + RunnerState::HandlingRedraw => (), + + // If we *weren't* handling events, we don't have to do anything. + RunnerState::New | RunnerState::Idle(..) => (), + + // Some control flows require a NewEvents call even if no events were received. This + // branch handles those. + RunnerState::DeferredNewEvents(wait_start) => { + match self.control_flow { + // If we had deferred a Poll, send the Poll NewEvents and EventsCleared. + ControlFlow::Poll => { + self.call_event_handler(Event::NewEvents(StartCause::Poll)); + self.call_event_handler(Event::MainEventsCleared); + self.runner_state = RunnerState::HandlingRedraw; + } + // If we had deferred a WaitUntil and the resume time has since been reached, + // send the resume notification and EventsCleared event. + ControlFlow::WaitUntil(resume_time) => { + if Instant::now() >= resume_time { + self.call_event_handler(Event::NewEvents( + StartCause::ResumeTimeReached { + start: wait_start, + requested_resume: resume_time, + }, + )); + self.call_event_handler(Event::MainEventsCleared); + self.runner_state = RunnerState::HandlingRedraw; + } + } + // If we deferred a wait and no events were received, the user doesn't have to + // get an event. + ControlFlow::Wait | ControlFlow::Exit => (), + } + } + } + } + + fn redraw_events_cleared(&mut self) { match self.runner_state { // If we were handling events, send the EventsCleared message. RunnerState::HandlingEvents => { self.call_event_handler(Event::MainEventsCleared); + self.runner_state = RunnerState::HandlingRedraw; self.flush_redraws(); self.call_event_handler(Event::RedrawEventsCleared); self.runner_state = RunnerState::Idle(Instant::now()); } RunnerState::HandlingRedraw => { + self.flush_redraws(); self.call_event_handler(Event::RedrawEventsCleared); self.runner_state = RunnerState::Idle(Instant::now()); } diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 1b9845038a..a5c60f9bf2 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -88,6 +88,38 @@ pub fn adjust_size(hwnd: HWND, size: PhysicalSize) -> PhysicalSize { PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _) } +pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) { + unsafe { + let rect = adjust_window_rect( + window, + RECT { + top: 0, + left: 0, + bottom: y as LONG, + right: x as LONG, + }, + ) + .expect("adjust_window_rect failed"); + + let outer_x = (rect.right - rect.left).abs() as _; + let outer_y = (rect.top - rect.bottom).abs() as _; + winuser::SetWindowPos( + window, + ptr::null_mut(), + 0, + 0, + outer_x, + outer_y, + winuser::SWP_ASYNCWINDOWPOS + | winuser::SWP_NOZORDER + | winuser::SWP_NOREPOSITION + | winuser::SWP_NOMOVE + | winuser::SWP_NOACTIVATE, + ); + winuser::UpdateWindow(window); + } +} + pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option { unsafe { let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 83609a27ca..f3e7851a12 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -24,7 +24,7 @@ use winapi::{ oleidl::LPDROPTARGET, shobjidl_core::{CLSID_TaskbarList, ITaskbarList2}, wingdi::{CreateRectRgn, DeleteObject}, - winnt::{LONG, LPCWSTR}, + winnt::LPCWSTR, winuser, }, }; @@ -215,38 +215,6 @@ impl Window { .unwrap() } - pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) { - unsafe { - let rect = util::adjust_window_rect( - self.window.0, - RECT { - top: 0, - left: 0, - bottom: y as LONG, - right: x as LONG, - }, - ) - .expect("adjust_window_rect failed"); - - let outer_x = (rect.right - rect.left).abs() as c_int; - let outer_y = (rect.top - rect.bottom).abs() as c_int; - winuser::SetWindowPos( - self.window.0, - ptr::null_mut(), - 0, - 0, - outer_x, - outer_y, - winuser::SWP_ASYNCWINDOWPOS - | winuser::SWP_NOZORDER - | winuser::SWP_NOREPOSITION - | winuser::SWP_NOMOVE - | winuser::SWP_NOACTIVATE, - ); - winuser::UpdateWindow(self.window.0); - } - } - #[inline] pub fn set_inner_size(&self, size: Size) { let dpi_factor = self.scale_factor(); @@ -260,7 +228,7 @@ impl Window { }); }); - self.set_inner_size_physical(width, height); + util::set_inner_size_physical(self.window.0, width, height); } #[inline] From 6b0875728c44c20069fab7d56f22cd880e4f3027 Mon Sep 17 00:00:00 2001 From: Murarth Date: Mon, 6 Jan 2020 20:54:22 -0700 Subject: [PATCH 068/239] X11: Fix deadlock on window state with certain window events (#1369) --- CHANGELOG.md | 1 + .../linux/x11/event_processor.rs | 29 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6548f58136..b3e39009c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. +- On X11, fix deadlock on window state when handling certain window events. # 0.20.0 (2020-01-05) diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 014b9b02d4..14fc1b1fff 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -2,6 +2,8 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc}; use libc::{c_char, c_int, c_long, c_uint, c_ulong}; +use parking_lot::MutexGuard; + use super::{ events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd, DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, @@ -384,9 +386,12 @@ impl EventProcessor { .inner_pos_to_outer(new_inner_position.0, new_inner_position.1); shared_state_lock.position = Some(outer); if moved { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Moved(outer.into()), + // Temporarily unlock shared state to prevent deadlock + MutexGuard::unlocked(&mut shared_state_lock, || { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Moved(outer.into()), + }); }); } outer @@ -426,12 +431,15 @@ impl EventProcessor { let old_inner_size = PhysicalSize::new(width, height); let mut new_inner_size = PhysicalSize::new(new_width, new_height); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: new_scale_factor, - new_inner_size: &mut new_inner_size, - }, + // Temporarily unlock shared state to prevent deadlock + MutexGuard::unlocked(&mut shared_state_lock, || { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor: new_scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); }); if new_inner_size != old_inner_size { @@ -461,6 +469,9 @@ impl EventProcessor { } if resized { + // Drop the shared state lock to prevent deadlock + drop(shared_state_lock); + callback(Event::WindowEvent { window_id, event: WindowEvent::Resized(new_inner_size.into()), From 02ac7456e438a88c3c7aeef00c86ed29638be92d Mon Sep 17 00:00:00 2001 From: Bastian Kauschke Date: Tue, 7 Jan 2020 20:33:56 +0100 Subject: [PATCH 069/239] impl Default for WindowBuilder (#1373) --- CHANGELOG.md | 1 + src/window.rs | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3e39009c2..2be5dfad5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. +- `WindowBuilder` now implements `Default`. # 0.20.0 (2020-01-05) diff --git a/src/window.rs b/src/window.rs index 073ed2fd8f..78de9fd48e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -78,7 +78,7 @@ impl WindowId { } /// Object that allows you to build windows. -#[derive(Clone)] +#[derive(Clone, Default)] pub struct WindowBuilder { /// The attributes to use to create the window. pub window: WindowAttributes, @@ -185,10 +185,7 @@ impl WindowBuilder { /// Initializes a new `WindowBuilder` with default values. #[inline] pub fn new() -> Self { - WindowBuilder { - window: Default::default(), - platform_specific: Default::default(), - } + Default::default() } /// Requests the window to be of specific dimensions. From d15eb04f9eb27e34a7914aaf262666b65f476fe4 Mon Sep 17 00:00:00 2001 From: Murarth Date: Tue, 7 Jan 2020 20:55:18 -0700 Subject: [PATCH 070/239] Make docs set control_flow in a more realistic way (#1376) --- README.md | 4 +++- examples/window_run_return.rs | 4 +++- src/window.rs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b4c673a11c..38bf850245 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,14 @@ fn main() { let window = WindowBuilder::new().build(&event_loop).unwrap(); event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + match event { Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, } if window_id == window.id() => *control_flow = ControlFlow::Exit, - _ => *control_flow = ControlFlow::Wait, + _ => (), } }); } diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index bde7f6a17e..0112eb3e5b 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -28,6 +28,8 @@ fn main() { while !quit { event_loop.run_return(|event, _, control_flow| { + *control_flow = ControlFlow::Wait; + if let Event::WindowEvent { event, .. } = &event { // Print only Window events to reduce noise println!("{:?}", event); @@ -43,7 +45,7 @@ fn main() { Event::MainEventsCleared => { *control_flow = ControlFlow::Exit; } - _ => *control_flow = ControlFlow::Wait, + _ => (), } }); diff --git a/src/window.rs b/src/window.rs index 78de9fd48e..0cb7c203c9 100644 --- a/src/window.rs +++ b/src/window.rs @@ -26,12 +26,14 @@ pub use crate::icon::*; /// let window = Window::new(&event_loop).unwrap(); /// /// event_loop.run(move |event, _, control_flow| { +/// *control_flow = ControlFlow::Wait; +/// /// match event { /// Event::WindowEvent { /// event: WindowEvent::CloseRequested, /// .. /// } => *control_flow = ControlFlow::Exit, -/// _ => *control_flow = ControlFlow::Wait, +/// _ => (), /// } /// }); /// ``` From 09c4ed0694afdcec4f9fb0a3fbd6e8b51dca5833 Mon Sep 17 00:00:00 2001 From: Osspial Date: Thu, 9 Jan 2020 12:24:57 -0500 Subject: [PATCH 071/239] Remove util from gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7cd4f7f91d..5e6640bc9f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ Cargo.lock target/ rls/ .vscode/ -util/ *~ *.wasm *.ts From 4b618bd6a6c27bb4198182a45ba84ecc095503d1 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Thu, 9 Jan 2020 20:19:50 -0800 Subject: [PATCH 072/239] Don't discard high-precision cursor position data (#1375) * Don't discard high-precision cursor position data Most platforms (X11, wayland, macos, stdweb, ...) provide physical positions in f64 units, which can contain meaningful fractional data. For example, this can be empirically observed on modern X11 using a typical laptop touchpad. This is useful for e.g. content creation tools, where cursor motion might map to brush strokes on a canvas with higher-than-screen resolution, or positioning of an object in a vector space. * Update CHANGELOG.md Co-Authored-By: Murarth Co-authored-by: Murarth --- CHANGELOG.md | 1 + src/event.rs | 2 +- src/platform_impl/linux/x11/event_processor.rs | 9 +++------ src/platform_impl/web/stdweb/canvas.rs | 2 +- src/platform_impl/web/web_sys/canvas.rs | 2 +- src/platform_impl/windows/event_loop.rs | 4 ++-- 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2be5dfad5e..9df5e0376f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. +- **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends # 0.20.0 (2020-01-05) diff --git a/src/event.rs b/src/event.rs index 9f30e8811a..10ab621709 100644 --- a/src/event.rs +++ b/src/event.rs @@ -242,7 +242,7 @@ pub enum WindowEvent<'a> { /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. - position: PhysicalPosition, + position: PhysicalPosition, #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] modifiers: ModifiersState, }, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 14fc1b1fff..424bb89c35 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -723,8 +723,7 @@ impl EventProcessor { util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) }); if cursor_moved == Some(true) { - let position = - PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32); + let position = PhysicalPosition::new(xev.event_x, xev.event_y); callback(Event::WindowEvent { window_id, @@ -830,8 +829,7 @@ impl EventProcessor { event: CursorEntered { device_id }, }); - let position = - PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32); + let position = PhysicalPosition::new(xev.event_x, xev.event_y); // The mods field on this event isn't actually populated, so query the // pointer device. In the future, we can likely remove this round-trip by @@ -899,8 +897,7 @@ impl EventProcessor { .map(|device| device.attachment) .unwrap_or(2); - let position = - PhysicalPosition::new(xev.event_x as i32, xev.event_y as i32); + let position = PhysicalPosition::new(xev.event_x, xev.event_y); callback(Event::WindowEvent { window_id, diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 94586d0480..35bc8044f1 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -207,7 +207,7 @@ impl Canvas { pub fn on_cursor_move(&mut self, mut handler: F) where - F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), { // todo self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 70b0ecc91a..1d7e47463c 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -216,7 +216,7 @@ impl Canvas { pub fn on_cursor_move(&mut self, mut handler: F) where - F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), { self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| { handler( diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 8d90e24766..beb3cef50f 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -855,8 +855,8 @@ unsafe extern "system" fn public_window_callback( }); } - let x = windowsx::GET_X_LPARAM(lparam) as i32; - let y = windowsx::GET_Y_LPARAM(lparam) as i32; + let x = windowsx::GET_X_LPARAM(lparam) as f64; + let y = windowsx::GET_Y_LPARAM(lparam) as f64; let position = PhysicalPosition::new(x, y); subclass_input.send_event(Event::WindowEvent { From 9e3844ddd9b31a083d1a2089c70be17aa4ed5787 Mon Sep 17 00:00:00 2001 From: Murarth Date: Thu, 9 Jan 2020 22:29:31 -0700 Subject: [PATCH 073/239] Fix warnings on all platforms (#1383) * Fix warnings on all platforms * Also fixed a trait impl bound that I noticed along the way --- src/event_loop.rs | 10 +++------- src/icon.rs | 17 ++++++----------- src/lib.rs | 1 + src/platform_impl/linux/x11/xdisplay.rs | 18 +++++++----------- src/platform_impl/macos/event.rs | 1 + src/platform_impl/macos/view.rs | 3 +++ .../web/event_loop/window_target.rs | 2 ++ src/platform_impl/windows/event_loop.rs | 5 +++++ 8 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/event_loop.rs b/src/event_loop.rs index e205c2316a..8a05e31359 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -215,14 +215,10 @@ impl fmt::Debug for EventLoopProxy { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct EventLoopClosed(pub T); -impl fmt::Display for EventLoopClosed { +impl fmt::Display for EventLoopClosed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", error::Error::description(self)) + f.write_str("Tried to wake up a closed `EventLoop`") } } -impl error::Error for EventLoopClosed { - fn description(&self) -> &str { - "Tried to wake up a closed `EventLoop`" - } -} +impl error::Error for EventLoopClosed {} diff --git a/src/icon.rs b/src/icon.rs index bbbb53f984..d512df1ada 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -29,31 +29,26 @@ pub enum BadIcon { impl fmt::Display for BadIcon { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let msg = match self { - &BadIcon::ByteCountNotDivisibleBy4 { byte_count } => format!( + match self { + BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f, "The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.", byte_count, ), - &BadIcon::DimensionsVsPixelCount { + BadIcon::DimensionsVsPixelCount { width, height, width_x_height, pixel_count, - } => format!( + } => write!(f, "The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.", width, height, pixel_count, width_x_height, ), - }; - write!(f, "{}", msg) + } } } impl Error for BadIcon { - fn description(&self) -> &str { - "A valid icon cannot be created from these arguments" - } - - fn cause(&self) -> Option<&dyn Error> { + fn source(&self) -> Option<&(dyn Error + 'static)> { Some(self) } } diff --git a/src/lib.rs b/src/lib.rs index 9a9fc02c9e..6011203e0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,6 +123,7 @@ #[allow(unused_imports)] #[macro_use] extern crate lazy_static; +#[allow(unused_imports)] #[macro_use] extern crate log; #[cfg(feature = "serde")] diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index 176323ec9c..25065f045d 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -111,12 +111,7 @@ pub struct XError { pub minor_code: u8, } -impl Error for XError { - #[inline] - fn description(&self) -> &str { - &self.description - } -} +impl Error for XError {} impl fmt::Display for XError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { @@ -144,17 +139,18 @@ impl From for XNotSupported { } } -impl Error for XNotSupported { - #[inline] - fn description(&self) -> &str { - match *self { +impl XNotSupported { + fn description(&self) -> &'static str { + match self { XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries", XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server", } } +} +impl Error for XNotSupported { #[inline] - fn cause(&self) -> Option<&dyn Error> { + fn source(&self) -> Option<&(dyn Error + 'static)> { match *self { XNotSupported::LibraryOpenError(ref err) => Some(err), _ => None, diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 07a7bf0cd1..6e231940f2 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -287,6 +287,7 @@ pub unsafe fn modifier_event( let scancode = get_scancode(ns_event); let virtual_keycode = scancode_to_keycode(scancode); + #[allow(deprecated)] Some(WindowEvent::KeyboardInput { device_id: DEVICE_ID, input: KeyboardInput { diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 2914c1926c..4bc8bcc76e 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -631,6 +631,7 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { let is_repeat = msg_send![event, isARepeat]; + #[allow(deprecated)] let window_event = Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { @@ -683,6 +684,7 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) { let scancode = get_scancode(event) as u32; let virtual_keycode = retrieve_keycode(event); + #[allow(deprecated)] let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::KeyboardInput { @@ -797,6 +799,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { let event: id = msg_send![NSApp(), currentEvent]; + #[allow(deprecated)] let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::KeyboardInput { diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 6596cebee7..2395f34678 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -57,6 +57,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| { + #[allow(deprecated)] runner.send_event(Event::WindowEvent { window_id: WindowId(id), event: WindowEvent::KeyboardInput { @@ -74,6 +75,7 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| { + #[allow(deprecated)] runner.send_event(Event::WindowEvent { window_id: WindowId(id), event: WindowEvent::KeyboardInput { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index beb3cef50f..0d2b1497b3 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -936,6 +936,7 @@ unsafe extern "system" fn public_window_callback( commctrl::DefSubclassProc(window, msg, wparam, lparam) } else { if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::KeyboardInput { @@ -965,6 +966,7 @@ unsafe extern "system" fn public_window_callback( winuser::WM_KEYUP | winuser::WM_SYSKEYUP => { use crate::event::ElementState::Released; if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::KeyboardInput { @@ -1330,6 +1332,7 @@ unsafe extern "system" fn public_window_callback( winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC); let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::KeyboardInput { @@ -1360,6 +1363,7 @@ unsafe extern "system" fn public_window_callback( winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC); let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::KeyboardInput { @@ -1913,6 +1917,7 @@ unsafe extern "system" fn thread_event_target_callback( }); } + #[allow(deprecated)] subclass_input.send_event(Event::DeviceEvent { device_id, event: Key(KeyboardInput { From 633d0deeaeee06b1e5b90e26d8c53dd39e4d755d Mon Sep 17 00:00:00 2001 From: hatoo Date: Sat, 11 Jan 2020 00:25:55 +0900 Subject: [PATCH 074/239] Fix run_return does not return on macOS unless it receives a message (#1380) * On MacOS, fix `run_return` not exit immediately * Add CHANGELOG --- CHANGELOG.md | 1 + src/platform_impl/macos/app_state.rs | 37 +++++++++++++++------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9df5e0376f..ab17da0717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix `run_return` does not return unless it receives a message. - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 1cb4e50558..135a3d2180 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -12,11 +12,13 @@ use std::{ }; use cocoa::{ - appkit::{NSApp, NSWindow}, - base::nil, - foundation::{NSAutoreleasePool, NSSize, NSString}, + appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow}, + base::{id, nil}, + foundation::{NSAutoreleasePool, NSPoint, NSSize}, }; +use objc::runtime::YES; + use crate::{ dpi::LogicalSize, event::{Event, StartCause, WindowEvent}, @@ -29,7 +31,6 @@ use crate::{ }, window::WindowId, }; -use objc::runtime::Object; lazy_static! { static ref HANDLER: Handler = Default::default(); @@ -339,21 +340,23 @@ impl AppState { let pool = NSAutoreleasePool::new(nil); - let windows: *const Object = msg_send![NSApp(), windows]; - let window: *const Object = msg_send![windows, objectAtIndex:0]; + let windows: id = msg_send![NSApp(), windows]; + let window: id = msg_send![windows, objectAtIndex:0]; assert_ne!(window, nil); - let title: *const Object = msg_send![window, title]; - assert_ne!(title, nil); - let postfix = NSString::alloc(nil).init_str("*"); - let some_unique_title: *const Object = - msg_send![title, stringByAppendingString: postfix]; - assert_ne!(some_unique_title, nil); - - // To stop event loop immediately, we need to send some UI event here. - let _: () = msg_send![window, setTitle: some_unique_title]; - // And restore it. - let _: () = msg_send![window, setTitle: title]; + let dummy_event: id = msg_send![class!(NSEvent), + otherEventWithType: NSApplicationDefined + location: NSPoint::new(0.0, 0.0) + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: nil + subtype: 0 + data1: 0 + data2: 0 + ]; + // To stop event loop immediately, we need to post some event here. + let _: () = msg_send![window, postEvent: dummy_event atStart: YES]; pool.drain(); }; From 1ddceeb0630994e4960493b1fe92bc72f8c5a54b Mon Sep 17 00:00:00 2001 From: Francesca Plebani Date: Fri, 10 Jan 2020 16:02:42 -0800 Subject: [PATCH 075/239] macOS: Unbundled window activation hack (#1318) * `ns_string_id_ref` convenience fn * Unbundled app activation hack * Greatly improved solution * Shortened names * Remove cruft I left here a year ago * Doc improvements * Use `AtomicBool` for `activationHackFlag` * Add CHANGELOG entry * Doc improvements * Fix `did_finish_launching` return type + delay more * More reliable activation checking * Fix merge goof * ...fix other merge goof Co-authored-by: Freya Gentz Co-authored-by: Bogaevsky --- CHANGELOG.md | 3 +- src/platform_impl/macos/activation_hack.rs | 208 +++++++++++++++++++++ src/platform_impl/macos/app.rs | 27 ++- src/platform_impl/macos/app_delegate.rs | 107 +++++------ src/platform_impl/macos/mod.rs | 1 + src/platform_impl/macos/monitor.rs | 7 +- src/platform_impl/macos/util/mod.rs | 18 +- src/platform_impl/macos/window.rs | 6 +- 8 files changed, 296 insertions(+), 81 deletions(-) create mode 100644 src/platform_impl/macos/activation_hack.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ab17da0717..dec9f1552e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. @@ -26,7 +27,7 @@ - On all platforms except mobile and WASM, implement `Window::set_minimized`. - On X11, fix `CursorEntered` event being generated for non-winit windows. - On macOS, fix crash when starting maximized without decorations. -- On macOS, fix application not to terminate on `run_return`. +- On macOS, fix application not terminating on `run_return`. - On Wayland, fix cursor icon updates on window borders when using CSD. - On Wayland, under mutter(GNOME Wayland), fix CSD being behind the status bar, when starting window in maximized mode. - On Windows, theme the title bar according to whether the system theme is "Light" or "Dark". diff --git a/src/platform_impl/macos/activation_hack.rs b/src/platform_impl/macos/activation_hack.rs new file mode 100644 index 0000000000..6cf1960cfc --- /dev/null +++ b/src/platform_impl/macos/activation_hack.rs @@ -0,0 +1,208 @@ +// Normally when you run or distribute a macOS app, it's bundled: it's in one +// of those fun little folders that you have to right click "Show Package +// Contents" on, and usually contains myriad delights including, but not +// limited to, plists, icons, and of course, your beloved executable. However, +// when you use `cargo run`, your app is unbundled - it's just a lonely, bare +// executable. +// +// Apple isn't especially fond of unbundled apps, which is to say, they seem to +// barely be supported. If you move the mouse while opening a winit window from +// an unbundled app, the window will fail to activate and be in a grayed-out +// uninteractable state. Switching to another app and back is the only way to +// get the winit window into a normal state. None of this happens if the app is +// bundled, i.e. when running via Xcode. +// +// To workaround this, we just switch focus to the Dock and then switch back to +// our app. We only do this for unbundled apps, and only when they fail to +// become active on their own. +// +// This solution was derived from this Godot PR: +// https://github.com/godotengine/godot/pull/17187 +// (which appears to be based on https://stackoverflow.com/a/7602677) +// The curious specialness of mouse motions is touched upon here: +// https://github.com/godotengine/godot/issues/8653#issuecomment-358130512 +// +// We omit the 2nd step of the solution used in Godot, since it appears to have +// no effect - I speculate that it's just technical debt picked up from the SO +// answer; the API used is fairly exotic, and was historically used for very +// old versions of macOS that didn't support `activateIgnoringOtherApps`, i.e. +// in previous versions of SDL: +// https://hg.libsdl.org/SDL/file/c0bcc39a3491/src/video/cocoa/SDL_cocoaevents.m#l322 +// +// The `performSelector` delays in the Godot solution are used for sequencing, +// since refocusing the app will fail if the call is made before it finishes +// unfocusing. The delays used there are much smaller than the ones in the +// original SO answer, presumably because they found the fastest delay that +// works reliably through trial and error. Instead of using delays, we just +// handle `applicationDidResignActive`; despite the app not activating reliably, +// that still triggers when we switch focus to the Dock. +// +// The Godot solution doesn't appear to skip the hack when an unbundled app +// activates normally. Checking for this is difficult, since if you call +// `isActive` too early, it will always be `NO`. Even though we receive +// `applicationDidResignActive` when switching focus to the Dock, we never +// receive a preceding `applicationDidBecomeActive` if the app fails to +// activate normally. I wasn't able to find a proper point in time to perform +// the `isActive` check, so we instead check for the cause of the quirk: if +// any mouse motion occurs prior to us receiving `applicationDidResignActive`, +// we assume the app failed to become active. +// +// Fun fact: this issue is still present in GLFW +// (https://github.com/glfw/glfw/issues/1515) +// +// A similar issue was found in SDL, but the resolution doesn't seem to work +// for us: https://bugzilla.libsdl.org/show_bug.cgi?id=3051 + +use super::util; +use cocoa::{ + appkit::{NSApp, NSApplicationActivateIgnoringOtherApps}, + base::id, + foundation::NSUInteger, +}; +use objc::runtime::{Object, Sel, BOOL, NO, YES}; +use std::{ + os::raw::c_void, + sync::atomic::{AtomicBool, Ordering}, +}; + +#[derive(Debug, Default)] +pub struct State { + // Indicates that the hack has either completed or been skipped. + activated: AtomicBool, + // Indicates that the mouse has moved at some point in time. + mouse_moved: AtomicBool, + // Indicates that the hack is in progress, and that we should refocus when + // the app resigns active. + needs_refocus: AtomicBool, +} + +impl State { + pub fn name() -> &'static str { + "activationHackState" + } + + pub fn new() -> *mut c_void { + let this = Box::new(Self::default()); + Box::into_raw(this) as *mut c_void + } + + pub unsafe fn free(this: *mut Self) { + Box::from_raw(this); + } + + pub unsafe fn get_ptr(obj: &Object) -> *mut Self { + let this: *mut c_void = *(*obj).get_ivar(Self::name()); + assert!(!this.is_null(), "`activationHackState` pointer was null"); + this as *mut Self + } + + pub unsafe fn set_activated(obj: &Object, value: bool) { + let this = Self::get_ptr(obj); + (*this).activated.store(value, Ordering::Release); + } + + unsafe fn get_activated(obj: &Object) -> bool { + let this = Self::get_ptr(obj); + (*this).activated.load(Ordering::Acquire) + } + + pub unsafe fn set_mouse_moved(obj: &Object, value: bool) { + let this = Self::get_ptr(obj); + (*this).mouse_moved.store(value, Ordering::Release); + } + + pub unsafe fn get_mouse_moved(obj: &Object) -> bool { + let this = Self::get_ptr(obj); + (*this).mouse_moved.load(Ordering::Acquire) + } + + pub unsafe fn set_needs_refocus(obj: &Object, value: bool) { + let this = Self::get_ptr(obj); + (*this).needs_refocus.store(value, Ordering::Release); + } + + unsafe fn get_needs_refocus(obj: &Object) -> bool { + let this = Self::get_ptr(obj); + (*this).needs_refocus.load(Ordering::Acquire) + } +} + +// This is the entry point for the hack - if the app is unbundled and a mouse +// movement occurs before the app activates, it will trigger the hack. Because +// mouse movements prior to activation are the cause of this quirk, they should +// be a reliable way to determine if the hack needs to be performed. +pub extern "C" fn mouse_moved(this: &Object, _: Sel, _: id) { + trace!("Triggered `activationHackMouseMoved`"); + unsafe { + if !State::get_activated(this) { + // We check if `CFBundleName` is undefined to determine if the + // app is unbundled. + if let None = util::app_name() { + info!("App detected as unbundled"); + unfocus(this); + } else { + info!("App detected as bundled"); + } + } + } + trace!("Completed `activationHackMouseMoved`"); +} + +// Switch focus to the dock. +unsafe fn unfocus(this: &Object) { + // We only perform the hack if the app failed to activate, since otherwise, + // there'd be a gross (but fast) flicker as it unfocused and then refocused. + // However, we only enter this function if we detect mouse movement prior + // to activation, so this should always be `NO`. + // + // Note that this check isn't necessarily reliable in detecting a violation + // of the invariant above, since it's not guaranteed that activation will + // resolve before this point. In other words, it can spuriously return `NO`. + // This is also why the mouse motion approach was chosen, since it's not + // obvious how to sequence this check - if someone knows how to, then that + // would almost surely be a cleaner approach. + let active: BOOL = msg_send![NSApp(), isActive]; + if active == YES { + error!("Unbundled app activation hack triggered on an app that's already active; this shouldn't happen!"); + } else { + info!("Performing unbundled app activation hack"); + let dock_bundle_id = util::ns_string_id_ref("com.apple.dock"); + let dock_array: id = msg_send![ + class!(NSRunningApplication), + runningApplicationsWithBundleIdentifier: *dock_bundle_id + ]; + let dock_array_len: NSUInteger = msg_send![dock_array, count]; + if dock_array_len == 0 { + error!("The Dock doesn't seem to be running, so switching focus to it is impossible"); + } else { + State::set_needs_refocus(this, true); + let dock: id = msg_send![dock_array, objectAtIndex: 0]; + // This will trigger `applicationDidResignActive`, which will in + // turn call `refocus`. + let status: BOOL = msg_send![ + dock, + activateWithOptions: NSApplicationActivateIgnoringOtherApps + ]; + if status == NO { + error!("Failed to switch focus to Dock"); + } + } + } +} + +// Switch focus back to our app, causing the user to rejoice! +pub unsafe fn refocus(this: &Object) { + if State::get_needs_refocus(this) { + State::set_needs_refocus(this, false); + let app: id = msg_send![class!(NSRunningApplication), currentApplication]; + // Simply calling `NSApp activateIgnoringOtherApps` doesn't work. The + // nuanced difference isn't clear to me, but hey, I tried. + let success: BOOL = msg_send![ + app, + activateWithOptions: NSApplicationActivateIgnoringOtherApps + ]; + if success == NO { + error!("Failed to refocus app"); + } + } +} diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs index fba48ae5ca..4cec95124c 100644 --- a/src/platform_impl/macos/app.rs +++ b/src/platform_impl/macos/app.rs @@ -2,17 +2,15 @@ use std::collections::VecDeque; use cocoa::{ appkit::{self, NSEvent}, - base::id, + base::{id, nil}, }; use objc::{ declare::ClassDecl, runtime::{Class, Object, Sel}, }; -use crate::{ - event::{DeviceEvent, ElementState, Event}, - platform_impl::platform::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}, -}; +use super::{activation_hack, app_state::AppState, event::EventWrapper, util, DEVICE_ID}; +use crate::event::{DeviceEvent, ElementState, Event}; pub struct AppClass(pub *const Class); unsafe impl Send for AppClass {} @@ -51,14 +49,14 @@ extern "C" fn send_event(this: &Object, _sel: Sel, event: id) { let key_window: id = msg_send![this, keyWindow]; let _: () = msg_send![key_window, sendEvent: event]; } else { - maybe_dispatch_device_event(event); + maybe_dispatch_device_event(this, event); let superclass = util::superclass(this); let _: () = msg_send![super(this, superclass), sendEvent: event]; } } } -unsafe fn maybe_dispatch_device_event(event: id) { +unsafe fn maybe_dispatch_device_event(this: &Object, event: id) { let event_type = event.eventType(); match event_type { appkit::NSMouseMoved @@ -100,6 +98,21 @@ unsafe fn maybe_dispatch_device_event(event: id) { } AppState::queue_events(events); + + // Notify the delegate when the first mouse move occurs. This is + // used for the unbundled app activation hack, which needs to know + // if any mouse motions occurred prior to the app activating. + let delegate: id = msg_send![this, delegate]; + assert_ne!(delegate, nil); + if !activation_hack::State::get_mouse_moved(&*delegate) { + activation_hack::State::set_mouse_moved(&*delegate, true); + let () = msg_send![ + delegate, + performSelector: sel!(activationHackMouseMoved:) + withObject: nil + afterDelay: 0.0 + ]; + } } appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => { let mut events = VecDeque::with_capacity(1); diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs index c0c3a121d7..9263cc121f 100644 --- a/src/platform_impl/macos/app_delegate.rs +++ b/src/platform_impl/macos/app_delegate.rs @@ -1,10 +1,10 @@ +use super::{activation_hack, app_state::AppState}; use cocoa::base::id; use objc::{ declare::ClassDecl, - runtime::{Class, Object, Sel, BOOL, YES}, + runtime::{Class, Object, Sel}, }; - -use crate::platform_impl::platform::app_state::AppState; +use std::os::raw::c_void; pub struct AppDelegateClass(pub *const Class); unsafe impl Send for AppDelegateClass {} @@ -15,90 +15,67 @@ lazy_static! { let superclass = class!(NSResponder); let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap(); + decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id); + decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel)); decl.add_method( sel!(applicationDidFinishLaunching:), - did_finish_launching as extern "C" fn(&Object, Sel, id) -> BOOL, + did_finish_launching as extern "C" fn(&Object, Sel, id), ); decl.add_method( sel!(applicationDidBecomeActive:), did_become_active as extern "C" fn(&Object, Sel, id), ); decl.add_method( - sel!(applicationWillResignActive:), - will_resign_active as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(applicationWillEnterForeground:), - will_enter_foreground as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(applicationDidEnterBackground:), - did_enter_background as extern "C" fn(&Object, Sel, id), + sel!(applicationDidResignActive:), + did_resign_active as extern "C" fn(&Object, Sel, id), ); + + decl.add_ivar::<*mut c_void>(activation_hack::State::name()); decl.add_method( - sel!(applicationWillTerminate:), - will_terminate as extern "C" fn(&Object, Sel, id), + sel!(activationHackMouseMoved:), + activation_hack::mouse_moved as extern "C" fn(&Object, Sel, id), ); AppDelegateClass(decl.register()) }; } -extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL { - trace!("Triggered `didFinishLaunching`"); - AppState::launched(); - trace!("Completed `didFinishLaunching`"); - YES -} - -extern "C" fn did_become_active(_: &Object, _: Sel, _: id) { - trace!("Triggered `didBecomeActive`"); - /*unsafe { - HANDLER.lock().unwrap().handle_nonuser_event(Event::Resumed) - }*/ - trace!("Completed `didBecomeActive`"); +extern "C" fn new(class: &Class, _: Sel) -> id { + unsafe { + let this: id = msg_send![class, alloc]; + let this: id = msg_send![this, init]; + (*this).set_ivar( + activation_hack::State::name(), + activation_hack::State::new(), + ); + this + } } -extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) { - trace!("Triggered `willResignActive`"); - /*unsafe { - HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended) - }*/ - trace!("Completed `willResignActive`"); +extern "C" fn dealloc(this: &Object, _: Sel) { + unsafe { + activation_hack::State::free(activation_hack::State::get_ptr(this)); + } } -extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) { - trace!("Triggered `willEnterForeground`"); - trace!("Completed `willEnterForeground`"); +extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) { + trace!("Triggered `applicationDidFinishLaunching`"); + AppState::launched(); + trace!("Completed `applicationDidFinishLaunching`"); } -extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) { - trace!("Triggered `didEnterBackground`"); - trace!("Completed `didEnterBackground`"); +extern "C" fn did_become_active(this: &Object, _: Sel, _: id) { + trace!("Triggered `applicationDidBecomeActive`"); + unsafe { + activation_hack::State::set_activated(this, true); + } + trace!("Completed `applicationDidBecomeActive`"); } -extern "C" fn will_terminate(_: &Object, _: Sel, _: id) { - trace!("Triggered `willTerminate`"); - /*unsafe { - let app: id = msg_send![class!(UIApplication), sharedApplication]; - let windows: id = msg_send![app, windows]; - let windows_enum: id = msg_send![windows, objectEnumerator]; - let mut events = Vec::new(); - loop { - let window: id = msg_send![windows_enum, nextObject]; - if window == nil { - break - } - let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)]; - if is_winit_window == YES { - events.push(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Destroyed, - }); - } - } - HANDLER.lock().unwrap().handle_nonuser_events(events); - HANDLER.lock().unwrap().terminated(); - }*/ - trace!("Completed `willTerminate`"); +extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) { + trace!("Triggered `applicationDidResignActive`"); + unsafe { + activation_hack::refocus(this); + } + trace!("Completed `applicationDidResignActive`"); } diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index c26385da89..8b5ee6a59a 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -1,5 +1,6 @@ #![cfg(target_os = "macos")] +mod activation_hack; mod app; mod app_delegate; mod app_state; diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index d9f0baa776..817d38ee57 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -1,15 +1,14 @@ use std::{collections::VecDeque, fmt}; -use super::ffi; +use super::{ffi, util}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform_impl::platform::util::IdRef, }; use cocoa::{ appkit::NSScreen, base::{id, nil}, - foundation::{NSString, NSUInteger}, + foundation::NSUInteger, }; use core_foundation::{ array::{CFArrayGetCount, CFArrayGetValueAtIndex}, @@ -303,7 +302,7 @@ impl MonitorHandle { let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0); let screens = NSScreen::screens(nil); let count: NSUInteger = msg_send![screens, count]; - let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); + let key = util::ns_string_id_ref("NSScreenNumber"); for i in 0..count { let screen = msg_send![screens, objectAtIndex: i as NSUInteger]; let device_description = NSScreen::deviceDescription(screen); diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 3a454cbba4..39a97c9832 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -8,7 +8,7 @@ use std::ops::{BitAnd, Deref}; use cocoa::{ appkit::{NSApp, NSWindowStyleMask}, base::{id, nil}, - foundation::{NSAutoreleasePool, NSRect, NSUInteger}, + foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger}, }; use core_graphics::display::CGDisplay; use objc::runtime::{Class, Object, Sel, BOOL, YES}; @@ -91,6 +91,22 @@ pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) } +pub unsafe fn ns_string_id_ref(s: &str) -> IdRef { + IdRef::new(NSString::alloc(nil).init_str(s)) +} + +pub unsafe fn app_name() -> Option { + let bundle: id = msg_send![class!(NSBundle), mainBundle]; + let dict: id = msg_send![bundle, infoDictionary]; + let key = ns_string_id_ref("CFBundleName"); + let app_name: id = msg_send![dict, objectForKey:*key]; + if app_name != nil { + Some(app_name) + } else { + None + } +} + pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class { let superclass: id = msg_send![this, superclass]; &*(superclass as *const _) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index d32da6580a..b77fc90776 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -36,7 +36,7 @@ use cocoa::{ NSWindow, NSWindowButton, NSWindowStyleMask, }, base::{id, nil}, - foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, + foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize}, }; use core_graphics::display::{CGDisplay, CGDisplayMode}; use objc::{ @@ -178,7 +178,7 @@ fn create_window( NO, )); let res = ns_window.non_nil().map(|ns_window| { - let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title)); + let title = util::ns_string_id_ref(&attrs.title); ns_window.setReleasedWhenClosed_(NO); ns_window.setTitle_(*title); ns_window.setAcceptsMouseMovedEvents_(YES); @@ -946,7 +946,7 @@ impl UnownedWindow { unsafe { let screen: id = msg_send![*self.ns_window, screen]; let desc = NSScreen::deviceDescription(screen); - let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); + let key = util::ns_string_id_ref("NSScreenNumber"); let value = NSDictionary::valueForKey_(desc, *key); let display_id = msg_send![value, unsignedIntegerValue]; RootMonitorHandle { From a6d180cefbc1b5046e324e033c93f529f416fdda Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 11 Jan 2020 11:45:52 +0300 Subject: [PATCH 076/239] On Wayland, fix coordinates in mouse events when scale factor isn't 1 (#1385) * On Wayland, fix coordinates in mouse events when scale factor isn't 1 * Refactor mouse_focus to be a surface instead of WindowId --- CHANGELOG.md | 1 + src/platform_impl/linux/wayland/pointer.rs | 28 +++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dec9f1552e..029bf12175 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. - **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends +- On Wayland, fix coordinates in mouse events when scale factor isn't 1 # 0.20.0 (2020-01-05) diff --git a/src/platform_impl/linux/wayland/pointer.rs b/src/platform_impl/linux/wayland/pointer.rs index f84264c384..2ba6c934d2 100644 --- a/src/platform_impl/linux/wayland/pointer.rs +++ b/src/platform_impl/linux/wayland/pointer.rs @@ -7,10 +7,13 @@ use crate::event::{ use super::{ event_loop::{CursorManager, EventsSink}, + make_wid, window::WindowStore, DeviceId, }; +use smithay_client_toolkit::surface; + use smithay_client_toolkit::reexports::client::protocol::{ wl_pointer::{self, Event as PtrEvent, WlPointer}, wl_seat, @@ -36,6 +39,7 @@ pub fn implement_pointer( cursor_manager: Arc>, ) -> WlPointer { seat.get_pointer(|pointer| { + // Currently focused winit surface let mut mouse_focus = None; let mut axis_buffer = None; let mut axis_discrete_buffer = None; @@ -53,8 +57,10 @@ pub fn implement_pointer( .. } => { let wid = store.find_wid(&surface); + if let Some(wid) = wid { - mouse_focus = Some(wid); + let scale_factor = surface::get_dpi_factor(&surface) as f64; + mouse_focus = Some(surface); // Reload cursor style only when we enter winit's surface. Calling // this function every time on `PtrEvent::Enter` could interfere with @@ -75,7 +81,8 @@ pub fn implement_pointer( device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - position: (surface_x, surface_y).into(), + position: (surface_x * scale_factor, surface_y * scale_factor) + .into(), modifiers: modifiers_tracker.lock().unwrap().clone(), }, wid, @@ -101,13 +108,16 @@ pub fn implement_pointer( surface_y, .. } => { - if let Some(wid) = mouse_focus { + if let Some(surface) = mouse_focus.as_ref() { + let scale_factor = surface::get_dpi_factor(&surface) as f64; + let wid = make_wid(surface); sink.send_window_event( WindowEvent::CursorMoved { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - position: (surface_x, surface_y).into(), + position: (surface_x * scale_factor, surface_y * scale_factor) + .into(), modifiers: modifiers_tracker.lock().unwrap().clone(), }, wid, @@ -115,7 +125,7 @@ pub fn implement_pointer( } } PtrEvent::Button { button, state, .. } => { - if let Some(wid) = mouse_focus { + if let Some(surface) = mouse_focus.as_ref() { let state = match state { wl_pointer::ButtonState::Pressed => ElementState::Pressed, wl_pointer::ButtonState::Released => ElementState::Released, @@ -137,12 +147,13 @@ pub fn implement_pointer( button, modifiers: modifiers_tracker.lock().unwrap().clone(), }, - wid, + make_wid(surface), ); } } PtrEvent::Axis { axis, value, .. } => { - if let Some(wid) = mouse_focus { + if let Some(surface) = mouse_focus.as_ref() { + let wid = make_wid(surface); if pointer.as_ref().version() < 5 { let (mut x, mut y) = (0.0, 0.0); // old seat compatibility @@ -184,7 +195,8 @@ pub fn implement_pointer( PtrEvent::Frame => { let axis_buffer = axis_buffer.take(); let axis_discrete_buffer = axis_discrete_buffer.take(); - if let Some(wid) = mouse_focus { + if let Some(surface) = mouse_focus.as_ref() { + let wid = make_wid(surface); if let Some((x, y)) = axis_discrete_buffer { sink.send_window_event( WindowEvent::MouseWheel { From dc302b0db44a347dc5ccb06489d5262b1bc70ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n?= Date: Sun, 12 Jan 2020 18:50:34 +0100 Subject: [PATCH 077/239] Return physical position in `CursorMoved` on macOS (#1378) --- CHANGELOG.md | 1 + src/platform_impl/macos/view.rs | 31 ++++++++++--------------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 029bf12175..a971693f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 4bc8bcc76e..c0b2adc545 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -17,6 +17,7 @@ use objc::{ }; use crate::{ + dpi::LogicalPosition, event::{ DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent, @@ -59,6 +60,12 @@ struct ViewState { tracking_rect: Option, } +impl ViewState { + fn get_scale_factor(&self) -> f64 { + (unsafe { NSWindow::backingScaleFactor(self.ns_window) }) as f64 + } +} + pub fn new_view(ns_window: id) -> (IdRef, Weak>) { let cursor_state = Default::default(); let cursor_access = Arc::downgrade(&cursor_state); @@ -885,12 +892,13 @@ fn mouse_motion(this: &Object, event: id) { let x = view_point.x as f64; let y = view_rect.size.height as f64 - view_point.y as f64; + let logical_position = LogicalPosition::new(x, y); let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::CursorMoved { device_id: DEVICE_ID, - position: (x, y).into(), + position: logical_position.to_physical(state.get_scale_factor()), modifiers: event_mods(event), }, }; @@ -928,27 +936,8 @@ extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) { }, }; - let move_event = { - let window_point = event.locationInWindow(); - let view_point: NSPoint = msg_send![this, - convertPoint:window_point - fromView:nil // convert from window coordinates - ]; - let view_rect: NSRect = msg_send![this, frame]; - let x = view_point.x as f64; - let y = (view_rect.size.height - view_point.y) as f64; - Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), - event: WindowEvent::CursorMoved { - device_id: DEVICE_ID, - position: (x, y).into(), - modifiers: event_mods(event), - }, - } - }; - AppState::queue_event(EventWrapper::StaticEvent(enter_event)); - AppState::queue_event(EventWrapper::StaticEvent(move_event)); + mouse_motion(this, event); } trace!("Completed `mouseEntered`"); } From c4d07952cb9c9f83d309744bf192c13437b38dfb Mon Sep 17 00:00:00 2001 From: Ryan G Date: Mon, 13 Jan 2020 14:14:25 -0500 Subject: [PATCH 078/239] Remove TODOs from the web backen (#1395) --- src/platform_impl/web/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 029761b957..bbd5760ec9 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -1,7 +1,3 @@ -// TODO: close events (port from old stdweb branch) -// TODO: pointer locking (stdweb PR required) -// TODO: fullscreen API (stdweb PR required) - mod device; mod error; mod event_loop; From ad7d4939a8be2e0d9436d43d0351e2f7599a4237 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Mon, 13 Jan 2020 20:15:44 +0100 Subject: [PATCH 079/239] doc: change remaining EventsCleared references to MainEventsCleared (#1390) --- src/platform_impl/ios/event_loop.rs | 4 ++-- src/platform_impl/macos/observer.rs | 2 +- src/platform_impl/windows/event_loop/runner.rs | 16 ++++++++-------- src/platform_impl/windows/window_state.rs | 2 +- src/window.rs | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index f9f28b5a14..61b389d43a 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -215,10 +215,10 @@ fn setup_control_flow_observers() { // Core Animation registers its `CFRunLoopObserver` that performs drawing operations in // `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end - // priority to be 0, in order to send EventsCleared before RedrawRequested. This value was + // priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was // chosen conservatively to guard against apple using different priorities for their redraw // observers in different OS's or on different devices. If it so happens that it's too - // conservative, the main symptom would be non-redraw events coming in after `EventsCleared`. + // conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`. // // The value of `0x1e8480` was determined by inspecting stack traces and the associated // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index edd8e9948d..aa7f5362c8 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -122,7 +122,7 @@ extern "C" fn control_flow_begin_handler( } // end is queued with the lowest priority to ensure it is processed after other observers -// without that, LoopDestroyed would get sent after EventsCleared +// without that, LoopDestroyed would get sent after MainEventsCleared extern "C" fn control_flow_end_handler( _: CFRunLoopObserverRef, activity: CFRunLoopActivity, diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index ce376eb28c..44a044d43a 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -273,7 +273,7 @@ enum RunnerState { /// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`. DeferredNewEvents(Instant), /// The event loop is handling the OS's events and sending them to the user's callback. - /// `NewEvents` has been sent, and `EventsCleared` hasn't. + /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. HandlingEvents, HandlingRedraw, } @@ -408,7 +408,7 @@ impl EventLoopRunner { self.call_event_handler(Event::NewEvents(start_cause)); } // This can be reached if the control flow is changed to poll during a `RedrawRequested` - // that was sent after `EventsCleared`. + // that was sent after `MainEventsCleared`. ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)), } } @@ -451,7 +451,7 @@ impl EventLoopRunner { fn main_events_cleared(&mut self) { match self.runner_state { - // If we were handling events, send the EventsCleared message. + // If we were handling events, send the MainEventsCleared message. RunnerState::HandlingEvents => { self.call_event_handler(Event::MainEventsCleared); self.runner_state = RunnerState::HandlingRedraw; @@ -466,14 +466,14 @@ impl EventLoopRunner { // branch handles those. RunnerState::DeferredNewEvents(wait_start) => { match self.control_flow { - // If we had deferred a Poll, send the Poll NewEvents and EventsCleared. + // If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared. ControlFlow::Poll => { self.call_event_handler(Event::NewEvents(StartCause::Poll)); self.call_event_handler(Event::MainEventsCleared); self.runner_state = RunnerState::HandlingRedraw; } // If we had deferred a WaitUntil and the resume time has since been reached, - // send the resume notification and EventsCleared event. + // send the resume notification and MainEventsCleared event. ControlFlow::WaitUntil(resume_time) => { if Instant::now() >= resume_time { self.call_event_handler(Event::NewEvents( @@ -496,7 +496,7 @@ impl EventLoopRunner { fn redraw_events_cleared(&mut self) { match self.runner_state { - // If we were handling events, send the EventsCleared message. + // If we were handling events, send the MainEventsCleared message. RunnerState::HandlingEvents => { self.call_event_handler(Event::MainEventsCleared); self.runner_state = RunnerState::HandlingRedraw; @@ -518,7 +518,7 @@ impl EventLoopRunner { // branch handles those. RunnerState::DeferredNewEvents(wait_start) => { match self.control_flow { - // If we had deferred a Poll, send the Poll NewEvents and EventsCleared. + // If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared. ControlFlow::Poll => { self.call_event_handler(Event::NewEvents(StartCause::Poll)); self.call_event_handler(Event::MainEventsCleared); @@ -526,7 +526,7 @@ impl EventLoopRunner { self.call_event_handler(Event::RedrawEventsCleared); } // If we had deferred a WaitUntil and the resume time has since been reached, - // send the resume notification and EventsCleared event. + // send the resume notification and MainEventsCleared event. ControlFlow::WaitUntil(resume_time) => { if Instant::now() >= resume_time { self.call_event_handler(Event::NewEvents( diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 42cfeb7d56..8150d1199a 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -30,7 +30,7 @@ pub struct WindowState { pub fullscreen: Option, /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple - /// times in `EventsCleared`. + /// times in `MainEventsCleared`. pub queued_out_of_band_redraw: bool, pub is_dark_mode: bool, pub high_surrogate: Option, diff --git a/src/window.rs b/src/window.rs index 0cb7c203c9..99644fee4b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -392,10 +392,10 @@ impl Window { /// This is the **strongly encouraged** method of redrawing windows, as it can integrate with /// OS-requested redraws (e.g. when a window gets resized). /// - /// This function can cause `RedrawRequested` events to be emitted after `Event::EventsCleared` + /// This function can cause `RedrawRequested` events to be emitted after `Event::MainEventsCleared` /// but before `Event::NewEvents` if called in the following circumstances: - /// * While processing `EventsCleared`. - /// * While processing a `RedrawRequested` event that was sent during `EventsCleared` or any + /// * While processing `MainEventsCleared`. + /// * While processing a `RedrawRequested` event that was sent during `MainEventsCleared` or any /// directly subsequent `RedrawRequested` event. /// /// ## Platform-specific From 9daa0738a9aeb60768d9b7d19f4e3deddd55958b Mon Sep 17 00:00:00 2001 From: hatoo Date: Wed, 15 Jan 2020 06:52:18 +0900 Subject: [PATCH 080/239] on MacOS, Fix not sending ReceivedCharacter event for some key combination (#1347) * MacOS FIX #1267 * Add CHANGELOG * Remove unnecessary trace! --- CHANGELOG.md | 2 ++ src/platform_impl/macos/view.rs | 9 ++------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a971693f45..b048eb3921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix not sending ReceivedCharacter event for specific keys combinations. - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. @@ -12,6 +13,7 @@ # 0.20.0 (2020-01-05) - On X11, fix `ModifiersChanged` emitting incorrect modifier change events + - **Breaking**: Overhaul how Winit handles DPI: + Window functions and events now return `PhysicalSize` instead of `LogicalSize`. + Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types. diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index c0b2adc545..741ecb4819 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -383,13 +383,8 @@ extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) { } } -extern "C" fn has_marked_text(this: &Object, _sel: Sel) -> BOOL { - unsafe { - trace!("Triggered `hasMarkedText`"); - let marked_text: id = *this.get_ivar("markedText"); - trace!("Completed `hasMarkedText`"); - (marked_text.length() > 0) as i8 - } +extern "C" fn has_marked_text(_this: &Object, _sel: Sel) -> BOOL { + YES } extern "C" fn marked_range(this: &Object, _sel: Sel) -> NSRange { From 1fe4a7a4ea16d98f57355c9b61af600576f41e41 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Wed, 15 Jan 2020 21:20:14 -0500 Subject: [PATCH 081/239] Add the ability to pass a prebuilt canvas (#1394) This allows Winit to take control of existing canvas elements in the DOM, which is useful for web applications with other content in the page. --- CHANGELOG.md | 1 + src/platform/web.rs | 33 ++++++++++++++++++++++++- src/platform_impl/web/stdweb/canvas.rs | 17 +++++++------ src/platform_impl/web/stdweb/mod.rs | 2 ++ src/platform_impl/web/web_sys/canvas.rs | 31 +++++++++++++---------- src/platform_impl/web/web_sys/mod.rs | 2 ++ src/platform_impl/web/window.rs | 10 +++++--- 7 files changed, 71 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b048eb3921..0eea9c8703 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - `WindowBuilder` now implements `Default`. - **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends - On Wayland, fix coordinates in mouse events when scale factor isn't 1 +- On Web, add the ability to provide a custom canvas # 0.20.0 (2020-01-05) diff --git a/src/platform/web.rs b/src/platform/web.rs index 5fca0c422b..6083f5cb30 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -3,7 +3,10 @@ //! The web target does not automatically insert the canvas element object into the web page, to //! allow end users to determine how the page should be laid out. Use the `WindowExtStdweb` or //! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the -//! Window. +//! Window. Alternatively, use the `WindowBuilderExtStdweb` or `WindowBuilderExtWebSys` to provide +//! your own canvas. + +use crate::window::WindowBuilder; #[cfg(feature = "stdweb")] use stdweb::web::html_element::CanvasElement; @@ -20,3 +23,31 @@ use web_sys::HtmlCanvasElement; pub trait WindowExtWebSys { fn canvas(&self) -> HtmlCanvasElement; } + +#[cfg(feature = "stdweb")] +pub trait WindowBuilderExtStdweb { + fn with_canvas(self, canvas: Option) -> Self; +} + +#[cfg(feature = "stdweb")] +impl WindowBuilderExtStdweb for WindowBuilder { + fn with_canvas(mut self, canvas: Option) -> Self { + self.platform_specific.canvas = canvas; + + self + } +} + +#[cfg(feature = "web-sys")] +pub trait WindowBuilderExtWebSys { + fn with_canvas(self, canvas: Option) -> Self; +} + +#[cfg(feature = "web-sys")] +impl WindowBuilderExtWebSys for WindowBuilder { + fn with_canvas(mut self, canvas: Option) -> Self { + self.platform_specific.canvas = canvas; + + self + } +} diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 35bc8044f1..a3581917dc 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -2,7 +2,7 @@ use super::event; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; -use crate::platform_impl::OsError; +use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; use std::cell::RefCell; use std::rc::Rc; @@ -43,12 +43,15 @@ impl Drop for Canvas { } impl Canvas { - pub fn create() -> Result { - let canvas: CanvasElement = document() - .create_element("canvas") - .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? - .try_into() - .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?; + pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result { + let canvas = match attr.canvas { + Some(canvas) => canvas, + None => document() + .create_element("canvas") + .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? + .try_into() + .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))?, + }; // A tabindex is needed in order to capture local keyboard events. // A "0" value means that the element should be focusable in diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 6b45e68306..9274ab9916 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -72,3 +72,5 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool { None => false, } } + +pub type RawCanvasType = CanvasElement; diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 1d7e47463c..970f87dadb 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -2,7 +2,7 @@ use super::event; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; -use crate::platform_impl::OsError; +use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; use std::cell::RefCell; use std::rc::Rc; @@ -35,18 +35,23 @@ impl Drop for Canvas { } impl Canvas { - pub fn create() -> Result { - let window = - web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?; - - let document = window - .document() - .ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?; - - let canvas: HtmlCanvasElement = document - .create_element("canvas") - .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? - .unchecked_into(); + pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result { + let canvas = match attr.canvas { + Some(canvas) => canvas, + None => { + let window = web_sys::window() + .ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?; + + let document = window + .document() + .ok_or(os_error!(OsError("Failed to obtain document".to_owned())))?; + + document + .create_element("canvas") + .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? + .unchecked_into() + } + }; // A tabindex is needed in order to capture local keyboard events. // A "0" value means that the element should be focusable in diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index fec8640228..cafbae1fb1 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -91,3 +91,5 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool { None => false, } } + +pub type RawCanvasType = HtmlCanvasElement; diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 8c1a1b9176..bd525056fa 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -23,13 +23,13 @@ impl Window { pub fn new( target: &EventLoopWindowTarget, attr: WindowAttributes, - _: PlatformSpecificBuilderAttributes, + platform_attr: PlatformSpecificBuilderAttributes, ) -> Result { let runner = target.runner.clone(); let id = target.generate_id(); - let mut canvas = backend::Canvas::create()?; + let mut canvas = backend::Canvas::create(platform_attr)?; let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); @@ -281,5 +281,7 @@ impl Id { } } -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PlatformSpecificBuilderAttributes; +#[derive(Default, Clone)] +pub struct PlatformSpecificBuilderAttributes { + pub(crate) canvas: Option, +} From d934f947046cd46bd282a8dc630df82dba7cc220 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 18 Jan 2020 17:49:02 +0000 Subject: [PATCH 082/239] Fix: deadlock when requesting redraw on X11 (#1408) --- src/platform_impl/linux/x11/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index f0275bf93b..7aea3b5e51 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -395,7 +395,6 @@ impl EventLoop { let mut xev = MaybeUninit::uninit(); let wt = get_xtarget(&self.target); - let mut pending_redraws = wt.pending_redraws.lock().unwrap(); while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } { let mut xev = unsafe { xev.assume_init() }; @@ -409,7 +408,7 @@ impl EventLoop { super::WindowId::X(wid), )) = event { - pending_redraws.insert(wid); + wt.pending_redraws.lock().unwrap().insert(wid); } else { callback(event, window_target, control_flow); } From e48262a7971a116301d5c78de5ef136d3f9f3572 Mon Sep 17 00:00:00 2001 From: Steven Sheldon Date: Sun, 19 Jan 2020 10:47:55 -0800 Subject: [PATCH 083/239] Simplify code by switching to higher-level dispatch APIs (#1409) * Switch to higher-level dispatch APIs * Inline all the functions * Switch to autoreleasepool from objc --- Cargo.toml | 2 +- src/platform_impl/macos/util/async.rs | 481 +++++++------------------- 2 files changed, 127 insertions(+), 356 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1948b921c..4dc41ed561 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ objc = "0.2.3" cocoa = "0.19.1" core-foundation = "0.6" core-graphics = "0.17.3" -dispatch = "0.1.4" +dispatch = "0.2.0" objc = "0.2.6" [target.'cfg(target_os = "macos")'.dependencies.core-video-sys] diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index 8aa8db9a27..977ba32807 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -1,20 +1,36 @@ use std::{ - os::raw::c_void, + ops::Deref, sync::{Mutex, Weak}, }; use cocoa::{ appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask}, base::{id, nil}, - foundation::{NSAutoreleasePool, NSPoint, NSSize, NSString}, + foundation::{NSPoint, NSSize, NSString}, }; -use dispatch::ffi::{dispatch_async_f, dispatch_get_main_queue, dispatch_sync_f}; +use dispatch::Queue; +use objc::rc::autoreleasepool; use crate::{ dpi::LogicalSize, platform_impl::platform::{ffi, util::IdRef, window::SharedState}, }; +// Unsafe wrapper type that allows us to dispatch things that aren't Send. +// This should *only* be used to dispatch to the main queue. +// While it is indeed not guaranteed that these types can safely be sent to +// other threads, we know that they're safe to use on the main thread. +struct MainThreadSafe(T); + +unsafe impl Send for MainThreadSafe {} + +impl Deref for MainThreadSafe { + type Target = T; + fn deref(&self) -> &T { + &self.0 + } +} + unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { ns_window.setStyleMask_(mask); // If we don't do this, key handling will break @@ -22,203 +38,55 @@ unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { ns_window.makeFirstResponder_(ns_view); } -struct SetStyleMaskData { - ns_window: id, - ns_view: id, - mask: NSWindowStyleMask, -} -impl SetStyleMaskData { - fn new_ptr(ns_window: id, ns_view: id, mask: NSWindowStyleMask) -> *mut Self { - Box::into_raw(Box::new(SetStyleMaskData { - ns_window, - ns_view, - mask, - })) - } -} -extern "C" fn set_style_mask_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetStyleMaskData; - { - let context = &*context_ptr; - set_style_mask(context.ns_window, context.ns_view, context.mask); - } - Box::from_raw(context_ptr); - } -} // Always use this function instead of trying to modify `styleMask` directly! // `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch. // Otherwise, this would vomit out errors about not being on the main thread // and fail to do anything. pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { - let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_style_mask_callback, - ); + let ns_window = MainThreadSafe(ns_window); + let ns_view = MainThreadSafe(ns_view); + Queue::main().exec_async(move || { + set_style_mask(*ns_window, *ns_view, mask); + }); } pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) { - let context = SetStyleMaskData::new_ptr(ns_window, ns_view, mask); if msg_send![class!(NSThread), isMainThread] { - set_style_mask_callback(context as *mut _); + set_style_mask(ns_window, ns_view, mask); } else { - dispatch_sync_f( - dispatch_get_main_queue(), - context as *mut _, - set_style_mask_callback, - ); + let ns_window = MainThreadSafe(ns_window); + let ns_view = MainThreadSafe(ns_view); + Queue::main().exec_sync(move || { + set_style_mask(*ns_window, *ns_view, mask); + }) } } -struct SetContentSizeData { - ns_window: id, - size: LogicalSize, -} -impl SetContentSizeData { - fn new_ptr(ns_window: id, size: LogicalSize) -> *mut Self { - Box::into_raw(Box::new(SetContentSizeData { ns_window, size })) - } -} -extern "C" fn set_content_size_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetContentSizeData; - { - let context = &*context_ptr; - NSWindow::setContentSize_( - context.ns_window, - NSSize::new( - context.size.width as CGFloat, - context.size.height as CGFloat, - ), - ); - } - Box::from_raw(context_ptr); - } -} // `setContentSize:` isn't thread-safe either, though it doesn't log any errors // and just fails silently. Anyway, GCD to the rescue! pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize) { - let context = SetContentSizeData::new_ptr(ns_window, size); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_content_size_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat)); + }); } -struct SetFrameTopLeftPointData { - ns_window: id, - point: NSPoint, -} -impl SetFrameTopLeftPointData { - fn new_ptr(ns_window: id, point: NSPoint) -> *mut Self { - Box::into_raw(Box::new(SetFrameTopLeftPointData { ns_window, point })) - } -} -extern "C" fn set_frame_top_left_point_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetFrameTopLeftPointData; - { - let context = &*context_ptr; - NSWindow::setFrameTopLeftPoint_(context.ns_window, context.point); - } - Box::from_raw(context_ptr); - } -} // `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy // to log errors. pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) { - let context = SetFrameTopLeftPointData::new_ptr(ns_window, point); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_frame_top_left_point_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.setFrameTopLeftPoint_(point); + }); } -struct SetLevelData { - ns_window: id, - level: ffi::NSWindowLevel, -} -impl SetLevelData { - fn new_ptr(ns_window: id, level: ffi::NSWindowLevel) -> *mut Self { - Box::into_raw(Box::new(SetLevelData { ns_window, level })) - } -} -extern "C" fn set_level_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetLevelData; - { - let context = &*context_ptr; - context.ns_window.setLevel_(context.level as _); - } - Box::from_raw(context_ptr); - } -} // `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) { - let context = SetLevelData::new_ptr(ns_window, level); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_level_callback, - ); -} - -struct ToggleFullScreenData { - ns_window: id, - ns_view: id, - not_fullscreen: bool, - shared_state: Weak>, + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.setLevel_(level as _); + }); } -impl ToggleFullScreenData { - fn new_ptr( - ns_window: id, - ns_view: id, - not_fullscreen: bool, - shared_state: Weak>, - ) -> *mut Self { - Box::into_raw(Box::new(ToggleFullScreenData { - ns_window, - ns_view, - not_fullscreen, - shared_state, - })) - } -} -extern "C" fn toggle_full_screen_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut ToggleFullScreenData; - { - let context = &*context_ptr; - // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we - // set a normal style temporarily. The previous state will be - // restored in `WindowDelegate::window_did_exit_fullscreen`. - if context.not_fullscreen { - let curr_mask = context.ns_window.styleMask(); - let required = NSWindowStyleMask::NSTitledWindowMask - | NSWindowStyleMask::NSResizableWindowMask; - if !curr_mask.contains(required) { - set_style_mask(context.ns_window, context.ns_view, required); - if let Some(shared_state) = context.shared_state.upgrade() { - trace!("Locked shared state in `toggle_full_screen_callback`"); - let mut shared_state_lock = shared_state.lock().unwrap(); - (*shared_state_lock).saved_style = Some(curr_mask); - trace!("Unlocked shared state in `toggle_full_screen_callback`"); - } - } - } - // Window level must be restored from `CGShieldingWindowLevel() - // + 1` back to normal in order for `toggleFullScreen` to do - // anything - context.ns_window.setLevel_(0); - context.ns_window.toggleFullScreen_(nil); - } - Box::from_raw(context_ptr); - } -} // `toggleFullScreen` is thread-safe, but our additional logic to account for // window styles isn't. pub unsafe fn toggle_full_screen_async( @@ -227,91 +95,42 @@ pub unsafe fn toggle_full_screen_async( not_fullscreen: bool, shared_state: Weak>, ) { - let context = ToggleFullScreenData::new_ptr(ns_window, ns_view, not_fullscreen, shared_state); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - toggle_full_screen_callback, - ); + let ns_window = MainThreadSafe(ns_window); + let ns_view = MainThreadSafe(ns_view); + let shared_state = MainThreadSafe(shared_state); + Queue::main().exec_async(move || { + // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we + // set a normal style temporarily. The previous state will be + // restored in `WindowDelegate::window_did_exit_fullscreen`. + if not_fullscreen { + let curr_mask = ns_window.styleMask(); + let required = + NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; + if !curr_mask.contains(required) { + set_style_mask(*ns_window, *ns_view, required); + if let Some(shared_state) = shared_state.upgrade() { + trace!("Locked shared state in `toggle_full_screen_callback`"); + let mut shared_state_lock = shared_state.lock().unwrap(); + (*shared_state_lock).saved_style = Some(curr_mask); + trace!("Unlocked shared state in `toggle_full_screen_callback`"); + } + } + } + // Window level must be restored from `CGShieldingWindowLevel() + // + 1` back to normal in order for `toggleFullScreen` to do + // anything + ns_window.setLevel_(0); + ns_window.toggleFullScreen_(nil); + }); } -extern "C" fn restore_display_mode_callback(screen: *mut c_void) { - unsafe { - let screen = Box::from_raw(screen as *mut u32); - ffi::CGRestorePermanentDisplayConfiguration(); - assert_eq!(ffi::CGDisplayRelease(*screen), ffi::kCGErrorSuccess); - } -} pub unsafe fn restore_display_mode_async(ns_screen: u32) { - dispatch_async_f( - dispatch_get_main_queue(), - Box::into_raw(Box::new(ns_screen)) as *mut _, - restore_display_mode_callback, - ); -} - -struct SetMaximizedData { - ns_window: id, - is_zoomed: bool, - maximized: bool, - shared_state: Weak>, -} -impl SetMaximizedData { - fn new_ptr( - ns_window: id, - is_zoomed: bool, - maximized: bool, - shared_state: Weak>, - ) -> *mut Self { - Box::into_raw(Box::new(SetMaximizedData { - ns_window, - is_zoomed, - maximized, - shared_state, - })) - } + Queue::main().exec_async(move || { + ffi::CGRestorePermanentDisplayConfiguration(); + assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess); + }); } -extern "C" fn set_maximized_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetMaximizedData; - { - let context = &*context_ptr; - - if let Some(shared_state) = context.shared_state.upgrade() { - trace!("Locked shared state in `set_maximized`"); - let mut shared_state_lock = shared_state.lock().unwrap(); - - // Save the standard frame sized if it is not zoomed - if !context.is_zoomed { - shared_state_lock.standard_frame = Some(NSWindow::frame(context.ns_window)); - } - - shared_state_lock.maximized = context.maximized; - let curr_mask = context.ns_window.styleMask(); - if shared_state_lock.fullscreen.is_some() { - // Handle it in window_did_exit_fullscreen - return; - } else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) { - // Just use the native zoom if resizable - context.ns_window.zoom_(nil); - } else { - // if it's not resizable, we set the frame directly - let new_rect = if context.maximized { - let screen = NSScreen::mainScreen(nil); - NSScreen::visibleFrame(screen) - } else { - shared_state_lock.saved_standard_frame() - }; - context.ns_window.setFrame_display_(new_rect, 0); - } - - trace!("Unlocked shared state in `set_maximized`"); - } - } - Box::from_raw(context_ptr); - } -} // `setMaximized` is not thread-safe pub unsafe fn set_maximized_async( ns_window: id, @@ -319,127 +138,79 @@ pub unsafe fn set_maximized_async( maximized: bool, shared_state: Weak>, ) { - let context = SetMaximizedData::new_ptr(ns_window, is_zoomed, maximized, shared_state); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_maximized_callback, - ); -} + let ns_window = MainThreadSafe(ns_window); + let shared_state = MainThreadSafe(shared_state); + Queue::main().exec_async(move || { + if let Some(shared_state) = shared_state.upgrade() { + trace!("Locked shared state in `set_maximized`"); + let mut shared_state_lock = shared_state.lock().unwrap(); + + // Save the standard frame sized if it is not zoomed + if !is_zoomed { + shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window)); + } -struct OrderOutData { - ns_window: id, -} -impl OrderOutData { - fn new_ptr(ns_window: id) -> *mut Self { - Box::into_raw(Box::new(OrderOutData { ns_window })) - } -} -extern "C" fn order_out_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut OrderOutData; - { - let context = &*context_ptr; - context.ns_window.orderOut_(nil); + shared_state_lock.maximized = maximized; + + let curr_mask = ns_window.styleMask(); + if shared_state_lock.fullscreen.is_some() { + // Handle it in window_did_exit_fullscreen + return; + } else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) { + // Just use the native zoom if resizable + ns_window.zoom_(nil); + } else { + // if it's not resizable, we set the frame directly + let new_rect = if maximized { + let screen = NSScreen::mainScreen(nil); + NSScreen::visibleFrame(screen) + } else { + shared_state_lock.saved_standard_frame() + }; + ns_window.setFrame_display_(new_rect, 0); + } + + trace!("Unlocked shared state in `set_maximized`"); } - Box::from_raw(context_ptr); - } + }); } + // `orderOut:` isn't thread-safe. Calling it from another thread actually works, // but with an odd delay. pub unsafe fn order_out_async(ns_window: id) { - let context = OrderOutData::new_ptr(ns_window); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - order_out_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.orderOut_(nil); + }); } -struct MakeKeyAndOrderFrontData { - ns_window: id, -} -impl MakeKeyAndOrderFrontData { - fn new_ptr(ns_window: id) -> *mut Self { - Box::into_raw(Box::new(MakeKeyAndOrderFrontData { ns_window })) - } -} -extern "C" fn make_key_and_order_front_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut MakeKeyAndOrderFrontData; - { - let context = &*context_ptr; - context.ns_window.makeKeyAndOrderFront_(nil); - } - Box::from_raw(context_ptr); - } -} // `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread // actually works, but with an odd delay. pub unsafe fn make_key_and_order_front_async(ns_window: id) { - let context = MakeKeyAndOrderFrontData::new_ptr(ns_window); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - make_key_and_order_front_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + ns_window.makeKeyAndOrderFront_(nil); + }); } -struct SetTitleData { - ns_window: id, - title: String, -} -impl SetTitleData { - fn new_ptr(ns_window: id, title: String) -> *mut Self { - Box::into_raw(Box::new(SetTitleData { ns_window, title })) - } -} -extern "C" fn set_title_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut SetTitleData; - { - let context = &*context_ptr; - let title = IdRef::new(NSString::alloc(nil).init_str(&context.title)); - context.ns_window.setTitle_(*title); - } - Box::from_raw(context_ptr); - } -} // `setTitle:` isn't thread-safe. Calling it from another thread invalidates the // window drag regions, which throws an exception when not done in the main // thread pub unsafe fn set_title_async(ns_window: id, title: String) { - let context = SetTitleData::new_ptr(ns_window, title); - dispatch_async_f( - dispatch_get_main_queue(), - context as *mut _, - set_title_callback, - ); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + let title = IdRef::new(NSString::alloc(nil).init_str(&title)); + ns_window.setTitle_(*title); + }); } -struct CloseData { - ns_window: id, -} -impl CloseData { - fn new_ptr(ns_window: id) -> *mut Self { - Box::into_raw(Box::new(CloseData { ns_window })) - } -} -extern "C" fn close_callback(context: *mut c_void) { - unsafe { - let context_ptr = context as *mut CloseData; - { - let context = &*context_ptr; - let pool = NSAutoreleasePool::new(nil); - context.ns_window.close(); - pool.drain(); - } - Box::from_raw(context_ptr); - } -} // `close:` is thread-safe, but we want the event to be triggered from the main // thread. Though, it's a good idea to look into that more... pub unsafe fn close_async(ns_window: id) { - let context = CloseData::new_ptr(ns_window); - dispatch_async_f(dispatch_get_main_queue(), context as *mut _, close_callback); + let ns_window = MainThreadSafe(ns_window); + Queue::main().exec_async(move || { + autoreleasepool(move || { + ns_window.close(); + }); + }); } From 3e3bb8a8f1b808424f2eaf262130f8830dedbaa2 Mon Sep 17 00:00:00 2001 From: David Yamnitsky Date: Sun, 19 Jan 2020 18:38:52 -0500 Subject: [PATCH 084/239] add hide_application on macos (#1364) Co-authored-by: Osspial Co-authored-by: Bogaevsky --- CHANGELOG.md | 1 + src/platform/macos.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eea9c8703..cd299bdc32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`. - On macOS, fix not sending ReceivedCharacter event for specific keys combinations. - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. diff --git a/src/platform/macos.rs b/src/platform/macos.rs index cfedaab23f..b1a298b365 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -4,6 +4,7 @@ use std::os::raw::c_void; use crate::{ dpi::LogicalSize, + event_loop::{EventLoop, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; @@ -209,3 +210,17 @@ impl MonitorHandleExtMacOS for MonitorHandle { self.inner.ns_screen().map(|s| s as *mut c_void) } } + +/// Additional methods on `EventLoopWindowTarget` that are specific to macOS. +pub trait EventLoopWindowTargetExtMacOS { + /// Hide the entire application. In most applications this is typically triggered with Command-H. + fn hide_application(&self); +} + +impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { + fn hide_application(&self) { + let cls = objc::runtime::Class::get("NSApplication").unwrap(); + let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] }; + unsafe { msg_send![app, hide: 0] } + } +} From 0ae78db6cb5568152f0e32ac3cf4e8cb43e28920 Mon Sep 17 00:00:00 2001 From: Osspial Date: Tue, 21 Jan 2020 14:43:36 -0500 Subject: [PATCH 085/239] Fix building on Windows 7 and 8 (#1398) * Fix building on Windows 7 and 8 * Format --- src/platform_impl/windows/event_loop.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 0d2b1497b3..0049b09c89 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1470,7 +1470,6 @@ unsafe extern "system" fn public_window_callback( let style = winuser::GetWindowLongW(window, winuser::GWL_STYLE) as _; let style_ex = winuser::GetWindowLongW(window, winuser::GWL_EXSTYLE) as _; - let b_menu = !winuser::GetMenu(window).is_null() as BOOL; // New size as suggested by Windows. let suggested_rect = *(lparam as *const RECT); @@ -1484,14 +1483,9 @@ unsafe extern "system" fn public_window_callback( // let margin_right: i32; // let margin_bottom: i32; { - let mut adjusted_rect = suggested_rect; - winuser::AdjustWindowRectExForDpi( - &mut adjusted_rect, - style, - b_menu, - style_ex, - new_dpi_x, - ); + let adjusted_rect = + util::adjust_window_rect_with_styles(window, style, style_ex, suggested_rect) + .unwrap_or(suggested_rect); margin_left = suggested_rect.left - adjusted_rect.left; margin_top = suggested_rect.top - adjusted_rect.top; // margin_right = adjusted_rect.right - suggested_rect.right; @@ -1556,13 +1550,13 @@ unsafe extern "system" fn public_window_callback( bottom: suggested_ul.1 + new_physical_inner_size.height as LONG, }; - winuser::AdjustWindowRectExForDpi( - &mut conservative_rect, + conservative_rect = util::adjust_window_rect_with_styles( + window, style, - b_menu, style_ex, - new_dpi_x, - ); + conservative_rect, + ) + .unwrap_or(conservative_rect); // If we're not dragging the window, offset the window so that the cursor's // relative horizontal position in the title bar is preserved. From 8856b6ecb7f9633a47ae51d8b8fd8dd0899ad4d9 Mon Sep 17 00:00:00 2001 From: Freya Gentz Date: Thu, 23 Jan 2020 12:42:15 -0700 Subject: [PATCH 086/239] Remove unused code in X11 backend. (#1416) Signed-off-by: Freya Gentz --- src/platform_impl/linux/x11/mod.rs | 12 ---- .../linux/x11/util/client_msg.rs | 58 ------------------- src/platform_impl/linux/x11/util/format.rs | 13 ----- src/platform_impl/linux/x11/util/geometry.rs | 36 ------------ src/platform_impl/linux/x11/util/hint.rs | 49 +--------------- src/platform_impl/linux/x11/util/mod.rs | 13 ----- .../linux/x11/util/window_property.rs | 1 + 7 files changed, 2 insertions(+), 180 deletions(-) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 7aea3b5e51..35c1987e29 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -489,21 +489,9 @@ impl<'a> Deref for DeviceInfo<'a> { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(ffi::Window); -impl WindowId { - pub unsafe fn dummy() -> Self { - WindowId(0) - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(c_int); -impl DeviceId { - pub unsafe fn dummy() -> Self { - DeviceId(0) - } -} - pub struct Window(Arc); impl Deref for Window { diff --git a/src/platform_impl/linux/x11/util/client_msg.rs b/src/platform_impl/linux/x11/util/client_msg.rs index 3fafcdf999..2f2b7edf9c 100644 --- a/src/platform_impl/linux/x11/util/client_msg.rs +++ b/src/platform_impl/linux/x11/util/client_msg.rs @@ -43,62 +43,4 @@ impl XConnection { }; self.send_event(target_window, event_mask, event) } - - // Prepare yourself for the ultimate in unsafety! - // You should favor `send_client_msg` whenever possible, but some protocols (i.e. startup notification) require you - // to send more than one message worth of data. - pub fn send_client_msg_multi( - &self, - window: c_ulong, // The window this is "about"; not necessarily this window - target_window: c_ulong, // The window we're sending to - message_type: ffi::Atom, - event_mask: Option, - data: &[T], - ) -> Flusher<'_> { - let format = T::FORMAT; - let size_of_t = mem::size_of::(); - debug_assert_eq!(size_of_t, format.get_actual_size()); - let mut event = ffi::XClientMessageEvent { - type_: ffi::ClientMessage, - display: self.display, - window, - message_type, - format: format as c_int, - data: ffi::ClientMessageData::new(), - // These fields are ignored by `XSendEvent` - serial: 0, - send_event: 0, - }; - - let t_per_payload = format.get_payload_size() / size_of_t; - assert!(t_per_payload > 0); - let payload_count = data.len() / t_per_payload; - let payload_remainder = data.len() % t_per_payload; - let payload_ptr = data.as_ptr() as *const ClientMsgPayload; - - let mut payload_index = 0; - while payload_index < payload_count { - let payload = unsafe { payload_ptr.offset(payload_index as isize) }; - payload_index += 1; - event.data = unsafe { mem::transmute(*payload) }; - self.send_event(target_window, event_mask, &event).queue(); - } - - if payload_remainder > 0 { - let mut payload: ClientMsgPayload = [0; 5]; - let t_payload = payload.as_mut_ptr() as *mut T; - let invalid_payload = unsafe { payload_ptr.offset(payload_index as isize) }; - let invalid_t_payload = invalid_payload as *const T; - let mut t_index = 0; - while t_index < payload_remainder { - let valid_t = unsafe { invalid_t_payload.offset(t_index as isize) }; - unsafe { (*t_payload.offset(t_index as isize)) = (*valid_t).clone() }; - t_index += 1; - } - event.data = unsafe { mem::transmute(payload) }; - self.send_event(target_window, event_mask, &event).queue(); - } - - Flusher::new(self) - } } diff --git a/src/platform_impl/linux/x11/util/format.rs b/src/platform_impl/linux/x11/util/format.rs index 6090c65d5d..1ab5e01f7e 100644 --- a/src/platform_impl/linux/x11/util/format.rs +++ b/src/platform_impl/linux/x11/util/format.rs @@ -21,10 +21,6 @@ impl Format { } } - pub fn is_same_size_as(&self) -> bool { - mem::size_of::() == self.get_actual_size() - } - pub fn get_actual_size(&self) -> usize { match self { &Format::Char => mem::size_of::(), @@ -32,15 +28,6 @@ impl Format { &Format::Long => mem::size_of::(), } } - - pub fn get_payload_size(&self) -> usize { - match self { - // Due to the wonders of X11, half the space goes unused if you're not using longs (on 64-bit). - &Format::Char => mem::size_of::() * 20, - &Format::Short => mem::size_of::() * 10, - &Format::Long => mem::size_of::() * 5, - } - } } pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd { diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs index 149418e320..d8f466fc2a 100644 --- a/src/platform_impl/linux/x11/util/geometry.rs +++ b/src/platform_impl/linux/x11/util/geometry.rs @@ -1,7 +1,6 @@ use std::cmp; use super::*; -use crate::dpi::{LogicalPosition, LogicalSize}; // Friendly neighborhood axis-aligned rectangle #[derive(Debug, Clone, PartialEq, Eq)] @@ -89,16 +88,6 @@ impl FrameExtents { pub fn from_border(border: c_ulong) -> Self { Self::new(border, border, border, border) } - - pub fn as_logical(&self, factor: f64) -> LogicalFrameExtents { - let logicalize = |value: c_ulong| value as f64 / factor; - LogicalFrameExtents { - left: logicalize(self.left), - right: logicalize(self.right), - top: logicalize(self.top), - bottom: logicalize(self.bottom), - } - } } #[derive(Debug, Clone)] @@ -135,20 +124,6 @@ impl FrameExtentsHeuristic { } } - pub fn inner_pos_to_outer_logical( - &self, - mut logical: LogicalPosition, - factor: f64, - ) -> LogicalPosition { - use self::FrameExtentsHeuristicPath::*; - if self.heuristic_path != UnsupportedBordered { - let frame_extents = self.frame_extents.as_logical(factor); - logical.x -= frame_extents.left; - logical.y -= frame_extents.top; - } - logical - } - pub fn inner_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) { ( width.saturating_add( @@ -163,17 +138,6 @@ impl FrameExtentsHeuristic { ), ) } - - pub fn inner_size_to_outer_logical( - &self, - mut logical: LogicalSize, - factor: f64, - ) -> LogicalSize { - let frame_extents = self.frame_extents.as_logical(factor); - logical.width += frame_extents.left + frame_extents.right; - logical.height += frame_extents.top + frame_extents.bottom; - logical - } } impl XConnection { diff --git a/src/platform_impl/linux/x11/util/hint.rs b/src/platform_impl/linux/x11/util/hint.rs index 94c7e33a72..809f3063b0 100644 --- a/src/platform_impl/linux/x11/util/hint.rs +++ b/src/platform_impl/linux/x11/util/hint.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use super::*; #[derive(Debug)] +#[allow(dead_code)] pub enum StateOperation { Remove = 0, // _NET_WM_STATE_REMOVE Add = 1, // _NET_WM_STATE_ADD @@ -189,22 +190,6 @@ impl<'a> NormalHints<'a> { } } - pub fn has_flag(&self, flag: c_long) -> bool { - has_flag(self.size_hints.flags, flag) - } - - fn getter(&self, flag: c_long, field1: &c_int, field2: &c_int) -> Option<(u32, u32)> { - if self.has_flag(flag) { - Some((*field1 as _, *field2 as _)) - } else { - None - } - } - - pub fn get_size(&self) -> Option<(u32, u32)> { - self.getter(ffi::PSize, &self.size_hints.width, &self.size_hints.height) - } - // WARNING: This hint is obsolete pub fn set_size(&mut self, size: Option<(u32, u32)>) { if let Some((width, height)) = size { @@ -216,14 +201,6 @@ impl<'a> NormalHints<'a> { } } - pub fn get_max_size(&self) -> Option<(u32, u32)> { - self.getter( - ffi::PMaxSize, - &self.size_hints.max_width, - &self.size_hints.max_height, - ) - } - pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) { if let Some((max_width, max_height)) = max_size { self.size_hints.flags |= ffi::PMaxSize; @@ -234,14 +211,6 @@ impl<'a> NormalHints<'a> { } } - pub fn get_min_size(&self) -> Option<(u32, u32)> { - self.getter( - ffi::PMinSize, - &self.size_hints.min_width, - &self.size_hints.min_height, - ) - } - pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) { if let Some((min_width, min_height)) = min_size { self.size_hints.flags |= ffi::PMinSize; @@ -252,14 +221,6 @@ impl<'a> NormalHints<'a> { } } - pub fn get_resize_increments(&self) -> Option<(u32, u32)> { - self.getter( - ffi::PResizeInc, - &self.size_hints.width_inc, - &self.size_hints.height_inc, - ) - } - pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) { if let Some((width_inc, height_inc)) = resize_increments { self.size_hints.flags |= ffi::PResizeInc; @@ -270,14 +231,6 @@ impl<'a> NormalHints<'a> { } } - pub fn get_base_size(&self) -> Option<(u32, u32)> { - self.getter( - ffi::PBaseSize, - &self.size_hints.base_width, - &self.size_hints.base_height, - ) - } - pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) { if let Some((base_width, base_height)) = base_size { self.size_hints.flags |= ffi::PBaseSize; diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 4e8542361e..4bd1420655 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -23,18 +23,12 @@ pub use self::{ use std::{ mem::{self, MaybeUninit}, - ops::BitAnd, os::raw::*, ptr, }; use super::{ffi, XConnection, XError}; -pub fn reinterpret<'a, A, B>(a: &'a A) -> &'a B { - let b_ptr = a as *const _ as *const B; - unsafe { &*b_ptr } -} - pub fn maybe_change(field: &mut Option, value: T) -> bool { let wrapped = Some(value); if *field != wrapped { @@ -45,13 +39,6 @@ pub fn maybe_change(field: &mut Option, value: T) -> bool { } } -pub fn has_flag(bitset: T, flag: T) -> bool -where - T: Copy + PartialEq + BitAnd, -{ - bitset & flag == flag -} - #[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."] pub struct Flusher<'a> { xconn: &'a XConnection, diff --git a/src/platform_impl/linux/x11/util/window_property.rs b/src/platform_impl/linux/x11/util/window_property.rs index 0b9e7a71e3..34977f36d2 100644 --- a/src/platform_impl/linux/x11/util/window_property.rs +++ b/src/platform_impl/linux/x11/util/window_property.rs @@ -26,6 +26,7 @@ impl GetPropertyError { const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone! #[derive(Debug)] +#[allow(dead_code)] pub enum PropMode { Replace = ffi::PropModeReplace as isize, Prepend = ffi::PropModePrepend as isize, From fd946feac4c2486c44aca91428c976ba69cc1392 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Sat, 25 Jan 2020 19:04:03 -0500 Subject: [PATCH 087/239] Web backend refactor and documentation (#1415) The current implementation of the event loop runner has some significant problems. It can't handle multiple events being emitted at once (for example, when a keyboard event causes a key input, a text input, and a modifier change.) It's also relatively easy to introduce bugs for the different possible control flow states. The new model separates intentionally emitting a NewEvents (poll completed, wait completed, init) and emitting a normal event, as well as providing a method for emitting multiple events in a single call. --- src/platform_impl/web/event_loop/runner.rs | 129 ++++++++++++++------- src/platform_impl/web/mod.rs | 19 +++ 2 files changed, 104 insertions(+), 44 deletions(-) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index b480054cb6..398fcaf6cb 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -8,6 +8,7 @@ use std::{ cell::RefCell, clone::Clone, collections::{HashSet, VecDeque}, + iter, rc::Rc, }; @@ -60,7 +61,7 @@ impl Shared { event_handler: Box, &mut root::ControlFlow)>, ) { self.0.runner.replace(Some(Runner::new(event_handler))); - self.send_event(Event::NewEvents(StartCause::Init)); + self.init(); let close_instance = self.clone(); backend::on_unload(move || close_instance.handle_unload()); @@ -79,55 +80,98 @@ impl Shared { self.0.redraw_pending.borrow_mut().insert(id); } - // Add an event to the event loop runner + pub fn init(&self) { + let start_cause = Event::NewEvents(StartCause::Init); + self.run_until_cleared(iter::once(start_cause)); + } + + // Run the polling logic for the Poll ControlFlow, which involves clearing the queue + pub fn poll(&self) { + let start_cause = Event::NewEvents(StartCause::Poll); + self.run_until_cleared(iter::once(start_cause)); + } + + // Run the logic for waking from a WaitUntil, which involves clearing the queue + // Generally there shouldn't be events built up when this is called + pub fn resume_time_reached(&self, start: Instant, requested_resume: Instant) { + let start_cause = Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume, + }); + self.run_until_cleared(iter::once(start_cause)); + } + + // Add an event to the event loop runner, from the user or an event handler // // It will determine if the event should be immediately sent to the user or buffered for later pub fn send_event(&self, event: Event<'static, T>) { + self.send_events(iter::once(event)); + } + + // Add a series of events to the event loop runner + // + // It will determine if the event should be immediately sent to the user or buffered for later + pub fn send_events(&self, events: impl Iterator>) { // If the event loop is closed, it should discard any new events if self.is_closed() { return; } - - // Determine if event handling is in process, and then release the borrow on the runner - let (start_cause, event_is_start) = match *self.0.runner.borrow() { - Some(ref runner) if !runner.is_busy => { - if let Event::NewEvents(cause) = event { - (cause, true) - } else { - ( - match runner.state { - State::Init => StartCause::Init, - State::Poll { .. } => StartCause::Poll, - State::Wait { start } => StartCause::WaitCancelled { - start, - requested_resume: None, - }, - State::WaitUntil { start, end, .. } => StartCause::WaitCancelled { - start, - requested_resume: Some(end), - }, - State::Exit => { - return; - } - }, - false, - ) - } + // If we can run the event processing right now, or need to queue this and wait for later + let mut process_immediately = true; + if let Some(ref runner) = &*self.0.runner.borrow() { + // If we're currently polling, queue this and wait for the poll() method to be called + if let State::Poll { .. } = runner.state { + process_immediately = false; } - _ => { - // Events are currently being handled, so queue this one and don't try to - // double-process the event queue - self.0.events.borrow_mut().push_back(event); - return; + // If the runner is busy, queue this and wait for it to process it later + if runner.is_busy { + process_immediately = false; } + } else { + // The runner still hasn't been attached: queue this event and wait for it to be + process_immediately = false; + } + if !process_immediately { + // Queue these events to look at later + self.0.events.borrow_mut().extend(events); + return; + } + // At this point, we know this is a fresh set of events + // Now we determine why new events are incoming, and handle the events + let start_cause = if let Some(runner) = &*self.0.runner.borrow() { + match runner.state { + State::Init => StartCause::Init, + State::Poll { .. } => StartCause::Poll, + State::Wait { start } => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + State::WaitUntil { start, end, .. } => StartCause::WaitCancelled { + start, + requested_resume: Some(end), + }, + State::Exit => { + // If we're in the exit state, don't do event processing + return; + } + } + } else { + unreachable!("The runner cannot process events when it is not attached"); }; + // Take the start event, then the events provided to this function, and run an iteration of + // the event loop + let start_event = Event::NewEvents(start_cause); + let events = iter::once(start_event).chain(events); + self.run_until_cleared(events); + } + + // Given the set of new events, run the event loop until the main events and redraw events are + // cleared + // + // This will also process any events that have been queued or that are queued during processing + fn run_until_cleared(&self, events: impl Iterator>) { let mut control = self.current_control_flow(); - // Handle starting a new batch of events - // - // The user is informed via Event::NewEvents that there is a batch of events to process - // However, there is only one of these per batch of events - self.handle_event(Event::NewEvents(start_cause), &mut control); - if !event_is_start { + for event in events { self.handle_event(event, &mut control); } self.handle_event(Event::MainEventsCleared, &mut control); @@ -196,10 +240,7 @@ impl Shared { root::ControlFlow::Poll => { let cloned = self.clone(); State::Poll { - timeout: backend::Timeout::new( - move || cloned.send_event(Event::NewEvents(StartCause::Poll)), - Duration::from_millis(0), - ), + timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)), } } root::ControlFlow::Wait => State::Wait { @@ -220,7 +261,7 @@ impl Shared { start, end, timeout: backend::Timeout::new( - move || cloned.send_event(Event::NewEvents(StartCause::Poll)), + move || cloned.resume_time_reached(start, end), delay, ), } diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index bbd5760ec9..ba022db6e7 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -1,3 +1,22 @@ +// Brief introduction to the internals of the web backend: +// Currently, the web backend supports both wasm-bindgen and stdweb as methods of binding to the +// environment. Because they are both supporting the same underlying APIs, the actual web bindings +// are cordoned off into backend abstractions, which present the thinnest unifying layer possible. +// +// When adding support for new events or interactions with the browser, first consult trusted +// documentation (such as MDN) to ensure it is well-standardised and supported across many browsers. +// Once you have decided on the relevant web APIs, add support to both backends. +// +// The backend is used by the rest of the module to implement Winit's business logic, which forms +// the rest of the code. 'device', 'error', 'monitor', and 'window' define web-specific structures +// for winit's cross-platform structures. They are all relatively simple translations. +// +// The event_loop module handles listening for and processing events. 'Proxy' implements +// EventLoopProxy and 'WindowTarget' implements EventLoopWindowTarget. WindowTarget also handles +// registering the event handlers. The 'Execution' struct in the 'runner' module handles taking +// incoming events (from the registered handlers) and ensuring they are passed to the user in a +// compliant way. + mod device; mod error; mod event_loop; From 66fe69edd9e81196b9338eeca2877a9eebda2e7f Mon Sep 17 00:00:00 2001 From: Murarth Date: Sun, 26 Jan 2020 13:55:27 -0700 Subject: [PATCH 088/239] Fix warnings on macos (#1419) --- src/platform/macos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index b1a298b365..b275c7538d 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -4,7 +4,7 @@ use std::os::raw::c_void; use crate::{ dpi::LogicalSize, - event_loop::{EventLoop, EventLoopWindowTarget}, + event_loop::EventLoopWindowTarget, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; From e2951041990983eb100dde664607de5330e50b13 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 27 Jan 2020 03:56:54 +0100 Subject: [PATCH 089/239] Remove Wayland theme intermediates (#1209) * Remove Wayland theme intermediates This removes the intermediate struct for passing a Wayland theme to allow the user direct implementation of the trait. By passing the trait directly, it is possible for downstream users to have more freedom with customization without relying on winit to offer these options as fields. It should also make maintenance easier, since winit already doesn't implement all the functions which are offered by the smithay client toolkit. * Reimplement SCTK's Theme and ButtonState * Fix style issues * Remove public signature * Format code * Add change log entry Co-authored-by: Murarth --- CHANGELOG.md | 1 + src/platform/unix.rs | 169 +++++++++++++++++++++++++------------------ 2 files changed, 98 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd299bdc32..78cfc83e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - **Breaking:** `WindowEvent::CursorMoved` changed to `f64` units, preserving high-precision data supplied by most backends - On Wayland, fix coordinates in mouse events when scale factor isn't 1 - On Web, add the ability to provide a custom canvas +- **Breaking:** On Wayland, the `WaylandTheme` struct has been replaced with a `Theme` trait, allowing for extra configuration # 0.20.0 (2020-01-05) diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 0484d71e2d..12711506bb 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -2,7 +2,7 @@ use std::{os::raw, ptr, sync::Arc}; -use smithay_client_toolkit::window::{ButtonState, Theme}; +use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme}; use crate::{ dpi::Size, @@ -23,74 +23,6 @@ pub use crate::platform_impl::x11; pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported}; -/// Theme for wayland client side decorations -/// -/// Colors must be in ARGB8888 format -pub struct WaylandTheme { - /// Primary color when the window is focused - pub primary_active: [u8; 4], - /// Primary color when the window is unfocused - pub primary_inactive: [u8; 4], - /// Secondary color when the window is focused - pub secondary_active: [u8; 4], - /// Secondary color when the window is unfocused - pub secondary_inactive: [u8; 4], - /// Close button color when hovered over - pub close_button_hovered: [u8; 4], - /// Close button color - pub close_button: [u8; 4], - /// Close button color when hovered over - pub maximize_button_hovered: [u8; 4], - /// Maximize button color - pub maximize_button: [u8; 4], - /// Minimize button color when hovered over - pub minimize_button_hovered: [u8; 4], - /// Minimize button color - pub minimize_button: [u8; 4], -} - -struct WaylandThemeObject(WaylandTheme); - -impl Theme for WaylandThemeObject { - fn get_primary_color(&self, active: bool) -> [u8; 4] { - if active { - self.0.primary_active - } else { - self.0.primary_inactive - } - } - - // Used for division line - fn get_secondary_color(&self, active: bool) -> [u8; 4] { - if active { - self.0.secondary_active - } else { - self.0.secondary_inactive - } - } - - fn get_close_button_color(&self, state: ButtonState) -> [u8; 4] { - match state { - ButtonState::Hovered => self.0.close_button_hovered, - _ => self.0.close_button, - } - } - - fn get_maximize_button_color(&self, state: ButtonState) -> [u8; 4] { - match state { - ButtonState::Hovered => self.0.maximize_button_hovered, - _ => self.0.maximize_button, - } - } - - fn get_minimize_button_color(&self, state: ButtonState) -> [u8; 4] { - match state { - ButtonState::Hovered => self.0.minimize_button_hovered, - _ => self.0.minimize_button, - } - } -} - /// Additional methods on `EventLoopWindowTarget` that are specific to Unix. pub trait EventLoopWindowTargetExtUnix { /// True if the `EventLoopWindowTarget` uses Wayland. @@ -275,7 +207,7 @@ pub trait WindowExtUnix { fn wayland_display(&self) -> Option<*mut raw::c_void>; /// Sets the color theme of the client side window decorations on wayland - fn set_wayland_theme(&self, theme: WaylandTheme); + fn set_wayland_theme(&self, theme: T); /// Check if the window is ready for drawing /// @@ -353,9 +285,9 @@ impl WindowExtUnix for Window { } #[inline] - fn set_wayland_theme(&self, theme: WaylandTheme) { + fn set_wayland_theme(&self, theme: T) { match self.window { - LinuxWindow::Wayland(ref w) => w.set_theme(WaylandThemeObject(theme)), + LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)), _ => {} } } @@ -461,3 +393,96 @@ impl MonitorHandleExtUnix for MonitorHandle { self.inner.native_identifier() } } + +/// Wrapper for implementing SCTK's theme trait. +struct WaylandTheme(T); + +pub trait Theme: Send + 'static { + /// Primary color of the scheme. + fn primary_color(&self, window_active: bool) -> [u8; 4]; + + /// Secondary color of the scheme. + fn secondary_color(&self, window_active: bool) -> [u8; 4]; + + /// Color for the close button. + fn close_button_color(&self, status: ButtonState) -> [u8; 4]; + + /// Icon color for the close button, defaults to the secondary color. + #[allow(unused_variables)] + fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] { + self.secondary_color(true) + } + + /// Background color for the maximize button. + fn maximize_button_color(&self, status: ButtonState) -> [u8; 4]; + + /// Icon color for the maximize button, defaults to the secondary color. + #[allow(unused_variables)] + fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] { + self.secondary_color(true) + } + + /// Background color for the minimize button. + fn minimize_button_color(&self, status: ButtonState) -> [u8; 4]; + + /// Icon color for the minimize button, defaults to the secondary color. + #[allow(unused_variables)] + fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] { + self.secondary_color(true) + } +} + +impl SCTKTheme for WaylandTheme { + fn get_primary_color(&self, active: bool) -> [u8; 4] { + self.0.primary_color(active) + } + + fn get_secondary_color(&self, active: bool) -> [u8; 4] { + self.0.secondary_color(active) + } + + fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] { + self.0.close_button_color(ButtonState::from_sctk(status)) + } + + fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { + self.0.close_button_color(ButtonState::from_sctk(status)) + } + + fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] { + self.0.maximize_button_color(ButtonState::from_sctk(status)) + } + + fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { + self.0 + .maximize_button_icon_color(ButtonState::from_sctk(status)) + } + + fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] { + self.0.minimize_button_color(ButtonState::from_sctk(status)) + } + + fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { + self.0 + .minimize_button_icon_color(ButtonState::from_sctk(status)) + } +} + +pub enum ButtonState { + /// Button is being hovered over by pointer. + Hovered, + /// Button is not being hovered over by pointer. + Idle, + /// Button is disabled. + Disabled, +} + +impl ButtonState { + fn from_sctk(button_state: SCTKButtonState) -> Self { + match button_state { + SCTKButtonState::Hovered => Self::Hovered, + SCTKButtonState::Idle => Self::Idle, + SCTKButtonState::Disabled => Self::Disabled, + } + } +} From 22dcc19898a8e05fc2b8e43233bb62477ecb285f Mon Sep 17 00:00:00 2001 From: hatoo Date: Sat, 1 Feb 2020 00:07:36 +0900 Subject: [PATCH 090/239] Fix set_minimized(true) works only with decorations on macOS (#1411) * On macOS, Fix set_minimized(true) works only with decorations * Add CHANGELOG --- CHANGELOG.md | 1 + src/platform_impl/macos/window.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78cfc83e0c..1150f3af05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix set_minimized(true) works only with decorations. - On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`. - On macOS, fix not sending ReceivedCharacter event for specific keys combinations. - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index b77fc90776..355fdecc27 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -150,10 +150,14 @@ fn create_window( let mut masks = if !attrs.decorations && !screen.is_some() { // Resizable UnownedWindow without a titlebar or borders // if decorations is set to false, ignore pl_attrs - NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask + NSWindowStyleMask::NSBorderlessWindowMask + | NSWindowStyleMask::NSResizableWindowMask + | NSWindowStyleMask::NSMiniaturizableWindowMask } else if pl_attrs.titlebar_hidden { // if the titlebar is hidden, ignore other pl_attrs - NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask + NSWindowStyleMask::NSBorderlessWindowMask + | NSWindowStyleMask::NSResizableWindowMask + | NSWindowStyleMask::NSMiniaturizableWindowMask } else { // default case, resizable window with titlebar and titlebar buttons NSWindowStyleMask::NSClosableWindowMask From 2f8aa5c52aff52e693384fd9e91b365262f635cc Mon Sep 17 00:00:00 2001 From: Murarth Date: Mon, 3 Feb 2020 17:42:52 -0700 Subject: [PATCH 091/239] Remove armv7-apple-ios target from CI (#1433) --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d552c4c1d7..40728a1410 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,6 @@ jobs: - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: x86_64-apple-ios, os: macos-latest, } - - { target: armv7-apple-ios, os: macos-latest, } - { target: aarch64-apple-ios, os: macos-latest, } # We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web # doesn't currently work on Linux. From c1eb7f96291c248f69dc605342bda229ad2007c3 Mon Sep 17 00:00:00 2001 From: David Craven Date: Wed, 5 Feb 2020 00:46:19 +0100 Subject: [PATCH 092/239] Fix deadlock wayland. (#1438) --- src/platform_impl/linux/wayland/window.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index f241f503b4..03b58b3807 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -481,9 +481,10 @@ impl WindowStore { for window in &mut self.windows { let opt_arc = window.frame.upgrade(); let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); + let mut size = { *window.size.lock().unwrap() }; f(WindowStoreForEach { newsize: window.newsize.take(), - size: &mut *(window.size.lock().unwrap()), + size: &mut size, prev_dpi: window.current_dpi, new_dpi: window.new_dpi, closed: window.closed, @@ -492,6 +493,7 @@ impl WindowStore { wid: make_wid(&window.surface), frame: opt_mutex_lock.as_mut().map(|m| &mut **m), }); + *window.size.lock().unwrap() = size; if let Some(dpi) = window.new_dpi.take() { window.current_dpi = dpi; } From 28f0eb598d8a309f528caf540751a28ec3c68ace Mon Sep 17 00:00:00 2001 From: Freya Gentz Date: Tue, 4 Feb 2020 19:07:31 -0700 Subject: [PATCH 093/239] Release 0.21.0 (#1440) * Update CHANGELOG.md * Update README.md * Update Cargo.toml * Update Cargo.toml * Update README.md * Update CHANGELOG.md --- CHANGELOG.md | 3 +++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1150f3af05..2271ec5380 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +# 0.21.0 (2020-02-04) + +- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows. - On macOS, fix set_minimized(true) works only with decorations. - On macOS, add `hide_application` to `EventLoopWindowTarget` via a new `EventLoopWindowTargetExtMacOS` trait. `hide_application` will hide the entire application by calling `-[NSApplication hide: nil]`. - On macOS, fix not sending ReceivedCharacter event for specific keys combinations. diff --git a/Cargo.toml b/Cargo.toml index 4dc41ed561..47443b6f6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.20.0" +version = "0.21.0" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index 38bf850245..1a3bdecfb2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ```toml [dependencies] -winit = "0.20.0" +winit = "0.21.0" ``` ## [Documentation](https://docs.rs/winit) From 4eddd1e5bc336e12ba06110f2ccadd64cadfd845 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sat, 8 Feb 2020 11:25:08 +0300 Subject: [PATCH 094/239] On Wayland, fix coordinates in touch events when scale factor isn't 1 (#1439) * On Wayland, fix coordinates in touch events when scale factor isn't 1 * Explicitly state that Wayland is using LogicalPosition internally * Fix CHANGELOG --- CHANGELOG.md | 2 + src/platform_impl/linux/wayland/pointer.rs | 17 +++++--- src/platform_impl/linux/wayland/touch.rs | 45 +++++++++++++++------- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2271ec5380..6c39ecdf45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- On Wayland, fix coordinates in touch events when scale factor isn't 1 + # 0.21.0 (2020-02-04) - On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows. diff --git a/src/platform_impl/linux/wayland/pointer.rs b/src/platform_impl/linux/wayland/pointer.rs index 2ba6c934d2..5f93099f4a 100644 --- a/src/platform_impl/linux/wayland/pointer.rs +++ b/src/platform_impl/linux/wayland/pointer.rs @@ -1,5 +1,6 @@ use std::sync::{Arc, Mutex}; +use crate::dpi::LogicalPosition; use crate::event::{ DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, @@ -76,13 +77,16 @@ pub fn implement_pointer( }, wid, ); + + let position = LogicalPosition::new(surface_x, surface_y) + .to_physical(scale_factor); + sink.send_window_event( WindowEvent::CursorMoved { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - position: (surface_x * scale_factor, surface_y * scale_factor) - .into(), + position, modifiers: modifiers_tracker.lock().unwrap().clone(), }, wid, @@ -109,15 +113,18 @@ pub fn implement_pointer( .. } => { if let Some(surface) = mouse_focus.as_ref() { - let scale_factor = surface::get_dpi_factor(&surface) as f64; let wid = make_wid(surface); + + let scale_factor = surface::get_dpi_factor(&surface) as f64; + let position = LogicalPosition::new(surface_x, surface_y) + .to_physical(scale_factor); + sink.send_window_event( WindowEvent::CursorMoved { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - position: (surface_x * scale_factor, surface_y * scale_factor) - .into(), + position, modifiers: modifiers_tracker.lock().unwrap().clone(), }, wid, diff --git a/src/platform_impl/linux/wayland/touch.rs b/src/platform_impl/linux/wayland/touch.rs index 803ea62492..ce4e32c284 100644 --- a/src/platform_impl/linux/wayland/touch.rs +++ b/src/platform_impl/linux/wayland/touch.rs @@ -1,17 +1,22 @@ use std::sync::{Arc, Mutex}; +use crate::dpi::LogicalPosition; use crate::event::{TouchPhase, WindowEvent}; -use super::{event_loop::EventsSink, window::WindowStore, DeviceId, WindowId}; +use super::{event_loop::EventsSink, make_wid, window::WindowStore, DeviceId}; + +use smithay_client_toolkit::surface; use smithay_client_toolkit::reexports::client::protocol::{ wl_seat, + wl_surface::WlSurface, wl_touch::{Event as TouchEvent, WlTouch}, }; +// location is in logical coordinates. struct TouchPoint { - wid: WindowId, - location: (f64, f64), + surface: WlSurface, + position: LogicalPosition, id: i32, } @@ -31,21 +36,24 @@ pub(crate) fn implement_touch( } => { let wid = store.find_wid(&surface); if let Some(wid) = wid { + let scale_factor = surface::get_dpi_factor(&surface) as f64; + let position = LogicalPosition::new(x, y); + sink.send_window_event( WindowEvent::Touch(crate::event::Touch { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), phase: TouchPhase::Started, - location: (x, y).into(), + location: position.to_physical(scale_factor), force: None, // TODO id: id as u64, }), wid, ); pending_ids.push(TouchPoint { - wid, - location: (x, y), + surface, + position, id, }); } @@ -54,52 +62,63 @@ pub(crate) fn implement_touch( let idx = pending_ids.iter().position(|p| p.id == id); if let Some(idx) = idx { let pt = pending_ids.remove(idx); + + let scale_factor = surface::get_dpi_factor(&pt.surface) as f64; + let location = pt.position.to_physical(scale_factor); + sink.send_window_event( WindowEvent::Touch(crate::event::Touch { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), phase: TouchPhase::Ended, - location: pt.location.into(), + location, force: None, // TODO id: id as u64, }), - pt.wid, + make_wid(&pt.surface), ); } } TouchEvent::Motion { id, x, y, .. } => { let pt = pending_ids.iter_mut().find(|p| p.id == id); if let Some(pt) = pt { - pt.location = (x, y); + pt.position = LogicalPosition::new(x, y); + + let scale_factor = surface::get_dpi_factor(&pt.surface) as f64; + let location = pt.position.to_physical(scale_factor); + sink.send_window_event( WindowEvent::Touch(crate::event::Touch { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), phase: TouchPhase::Moved, - location: (x, y).into(), + location, force: None, // TODO id: id as u64, }), - pt.wid, + make_wid(&pt.surface), ); } } TouchEvent::Frame => (), TouchEvent::Cancel => { for pt in pending_ids.drain(..) { + let scale_factor = surface::get_dpi_factor(&pt.surface) as f64; + let location = pt.position.to_physical(scale_factor); + sink.send_window_event( WindowEvent::Touch(crate::event::Touch { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), phase: TouchPhase::Cancelled, - location: pt.location.into(), + location, force: None, // TODO id: pt.id as u64, }), - pt.wid, + make_wid(&pt.surface), ); } } From 96df85896134895b4538a6a24a9f96d8b3343343 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 9 Feb 2020 05:36:45 +0300 Subject: [PATCH 095/239] On Wayland, fix color from `close_button_icon_color` not applying (#1444) --- CHANGELOG.md | 3 ++- src/platform/unix.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c39ecdf45..f5711e5a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased -- On Wayland, fix coordinates in touch events when scale factor isn't 1 +- On Wayland, fix coordinates in touch events when scale factor isn't 1. +- On Wayland, fix color from `close_button_icon_color` not applying. # 0.21.0 (2020-02-04) diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 12711506bb..6ab2d7dff4 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -446,7 +446,8 @@ impl SCTKTheme for WaylandTheme { } fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0.close_button_color(ButtonState::from_sctk(status)) + self.0 + .close_button_icon_color(ButtonState::from_sctk(status)) } fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] { From a1b65f7080fda20a9ab9fdc6f925d38e94df01af Mon Sep 17 00:00:00 2001 From: Julien Sanchez Date: Mon, 10 Feb 2020 06:37:06 +0100 Subject: [PATCH 096/239] Ignore locale if unsupported by X11 backend (#1445) This restores default portable 'C' locale when target locale is unsupported by X11 backend (Xlib). When target locale is unsupported by X11, some locale-dependent Xlib functions like `XSetLocaleModifiers` fail or have no effect triggering later failures and panics. When target locale is not valid, `setLocale` should normally leave the locale unchanged (`setLocale` returns 'C'). However, in some situations, locale is accepted by `setLocale` (`setLocale` returns the new locale) but the accepted locale is unsupported by Xlib (`XSupportsLocale` returns `false`). Fix #636 --- CHANGELOG.md | 1 + .../linux/x11/ime/input_method.rs | 3 ++- src/platform_impl/linux/x11/mod.rs | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5711e5a94..d6775c2e31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On Wayland, fix coordinates in touch events when scale factor isn't 1. - On Wayland, fix color from `close_button_icon_color` not applying. +- Ignore locale if unsupported by X11 backend # 0.21.0 (2020-02-04) diff --git a/src/platform_impl/linux/x11/ime/input_method.rs b/src/platform_impl/linux/x11/ime/input_method.rs index 42c4033efb..142c150199 100644 --- a/src/platform_impl/linux/x11/ime/input_method.rs +++ b/src/platform_impl/linux/x11/ime/input_method.rs @@ -21,7 +21,8 @@ unsafe fn open_im(xconn: &Arc, locale_modifiers: &CStr) -> Option EventLoop { // Input methods will open successfully without setting the locale, but it won't be // possible to actually commit pre-edit sequences. unsafe { + // Remember default locale to restore it if target locale is unsupported + // by Xlib + let default_locale = setlocale(LC_CTYPE, ptr::null()); setlocale(LC_CTYPE, b"\0".as_ptr() as *const _); + + // Check if set locale is supported by Xlib. + // If not, calls to some Xlib functions like `XSetLocaleModifiers` + // will fail. + let locale_supported = (xconn.xlib.XSupportsLocale)() == 1; + if !locale_supported { + let unsupported_locale = setlocale(LC_CTYPE, ptr::null()); + warn!( + "Unsupported locale \"{}\". Restoring default locale \"{}\".", + CStr::from_ptr(unsupported_locale).to_string_lossy(), + CStr::from_ptr(default_locale).to_string_lossy() + ); + // Restore default locale + setlocale(LC_CTYPE, default_locale); + } } let ime = RefCell::new({ let result = Ime::new(Arc::clone(&xconn)); From 5f52d7c9d0f2892758f50f400f8c22ee0adcae78 Mon Sep 17 00:00:00 2001 From: hatoo Date: Wed, 12 Feb 2020 17:27:11 +0900 Subject: [PATCH 097/239] On macOS, Fix `set_simple_screen` to remember frame excluding title bar (#1430) * On macOS, Fix `set_simple_screen` to remember frame excluding title bar * Add CHANGELOG --- CHANGELOG.md | 1 + src/platform_impl/macos/window.rs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6775c2e31..d3bcf516de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix `set_simple_screen` to remember frame excluding title bar. - On Wayland, fix coordinates in touch events when scale factor isn't 1. - On Wayland, fix color from `close_button_icon_color` not applying. - Ignore locale if unsupported by X11 backend diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 355fdecc27..b800d4d4b1 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1028,7 +1028,11 @@ impl WindowExtMacOS for UnownedWindow { if fullscreen { // Remember the original window's settings - shared_state_lock.standard_frame = Some(NSWindow::frame(*self.ns_window)); + // Exclude title bar + shared_state_lock.standard_frame = Some(NSWindow::contentRectForFrameRect_( + *self.ns_window, + NSWindow::frame(*self.ns_window), + )); shared_state_lock.saved_style = Some(self.ns_window.styleMask()); shared_state_lock.save_presentation_opts = Some(app.presentationOptions_()); From 83b60beba6ef53a10e3fa63eb0716372268ba199 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Thu, 13 Feb 2020 05:48:58 +0300 Subject: [PATCH 098/239] on Wayland, Add HiDPI cursor support (#1454) Fixes #727. --- CHANGELOG.md | 1 + Cargo.toml | 2 +- src/platform_impl/linux/wayland/event_loop.rs | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3bcf516de..5876b52909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - On Wayland, fix coordinates in touch events when scale factor isn't 1. - On Wayland, fix color from `close_button_icon_color` not applying. - Ignore locale if unsupported by X11 backend +- On Wayland, Add HiDPI cursor support # 0.21.0 (2020-02-04) diff --git a/Cargo.toml b/Cargo.toml index 47443b6f6f..b537dca432 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,7 +77,7 @@ features = [ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] } mio = "0.6" mio-extras = "2.0" -smithay-client-toolkit = "0.6" +smithay-client-toolkit = "^0.6.6" x11-dl = "2.18.3" percent-encoding = "2.0" diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 1ceedf19af..4a1e195937 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -90,6 +90,7 @@ pub struct CursorManager { locked_pointers: Vec, cursor_visible: bool, current_cursor: CursorIcon, + scale_factor: u32, } impl CursorManager { @@ -101,6 +102,7 @@ impl CursorManager { locked_pointers: Vec::new(), cursor_visible: true, current_cursor: CursorIcon::default(), + scale_factor: 1, } } @@ -145,6 +147,11 @@ impl CursorManager { } } + pub fn update_scale_factor(&mut self, scale: u32) { + self.scale_factor = scale; + self.reload_cursor_style(); + } + fn set_cursor_icon_impl(&mut self, cursor: CursorIcon) { let cursor = match cursor { CursorIcon::Alias => "link", @@ -193,7 +200,7 @@ impl CursorManager { for pointer in self.pointers.iter() { // Ignore erros, since we don't want to fail hard in case we can't find a proper cursor // in a given theme. - let _ = pointer.set_cursor(cursor, None); + let _ = pointer.set_cursor_with_scale(cursor, self.scale_factor, None); } } @@ -728,6 +735,13 @@ impl EventLoop { } if let Some(dpi) = window.new_dpi { + // Update cursor scale factor + { + self.cursor_manager + .lock() + .unwrap() + .update_scale_factor(dpi as u32); + }; let dpi = dpi as f64; let logical_size = LogicalSize::::from(*window.size); let mut new_inner_size = logical_size.to_physical(dpi); @@ -742,6 +756,8 @@ impl EventLoop { let (w, h) = new_inner_size.to_logical::(dpi).into(); frame.resize(w, h); + // Refresh frame to rescale decorations + frame.refresh(); *window.size = (w, h); } } From f0093d3c54b3e03a154c14f24555ed7df1c166e7 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Thu, 13 Feb 2020 20:41:41 +0100 Subject: [PATCH 099/239] rename dpi_factor to scale_factor where appropriate (#1463) fixes https://github.com/rust-windowing/winit/issues/1457 --- src/dpi.rs | 71 +++++++++++----------- src/platform_impl/android/mod.rs | 12 ++-- src/platform_impl/ios/view.rs | 16 ++--- src/platform_impl/ios/window.rs | 28 ++++----- src/platform_impl/linux/x11/window.rs | 36 +++++------ src/platform_impl/macos/window.rs | 50 +++++++-------- src/platform_impl/macos/window_delegate.rs | 8 +-- src/platform_impl/windows/event_loop.rs | 20 +++--- src/platform_impl/windows/window.rs | 20 +++--- src/platform_impl/windows/window_state.rs | 8 +-- 10 files changed, 136 insertions(+), 133 deletions(-) diff --git a/src/dpi.rs b/src/dpi.rs index 2057d01283..8a56ae6e06 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -151,8 +151,8 @@ impl Pixel for f64 { /// anywhere other than winit, it's recommended to validate them using this function before passing them to winit; /// otherwise, you risk panics. #[inline] -pub fn validate_scale_factor(dpi_factor: f64) -> bool { - dpi_factor.is_sign_positive() && dpi_factor.is_normal() +pub fn validate_scale_factor(scale_factor: f64) -> bool { + scale_factor.is_sign_positive() && scale_factor.is_normal() } /// A position represented in logical pixels. @@ -178,16 +178,16 @@ impl LogicalPosition

{ #[inline] pub fn from_physical>, X: Pixel>( physical: T, - dpi_factor: f64, + scale_factor: f64, ) -> Self { - physical.into().to_logical(dpi_factor) + physical.into().to_logical(scale_factor) } #[inline] - pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition { - assert!(validate_scale_factor(dpi_factor)); - let x = self.x.into() * dpi_factor; - let y = self.y.into() * dpi_factor; + pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition { + assert!(validate_scale_factor(scale_factor)); + let x = self.x.into() * scale_factor; + let y = self.y.into() * scale_factor; PhysicalPosition::new(x, y).cast() } @@ -243,16 +243,16 @@ impl PhysicalPosition

{ #[inline] pub fn from_logical>, X: Pixel>( logical: T, - dpi_factor: f64, + scale_factor: f64, ) -> Self { - logical.into().to_physical(dpi_factor) + logical.into().to_physical(scale_factor) } #[inline] - pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition { - assert!(validate_scale_factor(dpi_factor)); - let x = self.x.into() / dpi_factor; - let y = self.y.into() / dpi_factor; + pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition { + assert!(validate_scale_factor(scale_factor)); + let x = self.x.into() / scale_factor; + let y = self.y.into() / scale_factor; LogicalPosition::new(x, y).cast() } @@ -306,15 +306,18 @@ impl

LogicalSize

{ impl LogicalSize

{ #[inline] - pub fn from_physical>, X: Pixel>(physical: T, dpi_factor: f64) -> Self { - physical.into().to_logical(dpi_factor) + pub fn from_physical>, X: Pixel>( + physical: T, + scale_factor: f64, + ) -> Self { + physical.into().to_logical(scale_factor) } #[inline] - pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize { - assert!(validate_scale_factor(dpi_factor)); - let width = self.width.into() * dpi_factor; - let height = self.height.into() * dpi_factor; + pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize { + assert!(validate_scale_factor(scale_factor)); + let width = self.width.into() * scale_factor; + let height = self.height.into() * scale_factor; PhysicalSize::new(width, height).cast() } @@ -368,15 +371,15 @@ impl

PhysicalSize

{ impl PhysicalSize

{ #[inline] - pub fn from_logical>, X: Pixel>(logical: T, dpi_factor: f64) -> Self { - logical.into().to_physical(dpi_factor) + pub fn from_logical>, X: Pixel>(logical: T, scale_factor: f64) -> Self { + logical.into().to_physical(scale_factor) } #[inline] - pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize { - assert!(validate_scale_factor(dpi_factor)); - let width = self.width.into() / dpi_factor; - let height = self.height.into() / dpi_factor; + pub fn to_logical(&self, scale_factor: f64) -> LogicalSize { + assert!(validate_scale_factor(scale_factor)); + let width = self.width.into() / scale_factor; + let height = self.height.into() / scale_factor; LogicalSize::new(width, height).cast() } @@ -426,17 +429,17 @@ impl Size { size.into() } - pub fn to_logical(&self, dpi_factor: f64) -> LogicalSize

{ + pub fn to_logical(&self, scale_factor: f64) -> LogicalSize

{ match *self { - Size::Physical(size) => size.to_logical(dpi_factor), + Size::Physical(size) => size.to_logical(scale_factor), Size::Logical(size) => size.cast(), } } - pub fn to_physical(&self, dpi_factor: f64) -> PhysicalSize

{ + pub fn to_physical(&self, scale_factor: f64) -> PhysicalSize

{ match *self { Size::Physical(size) => size.cast(), - Size::Logical(size) => size.to_physical(dpi_factor), + Size::Logical(size) => size.to_physical(scale_factor), } } } @@ -468,17 +471,17 @@ impl Position { position.into() } - pub fn to_logical(&self, dpi_factor: f64) -> LogicalPosition

{ + pub fn to_logical(&self, scale_factor: f64) -> LogicalPosition

{ match *self { - Position::Physical(position) => position.to_logical(dpi_factor), + Position::Physical(position) => position.to_logical(scale_factor), Position::Logical(position) => position.cast(), } } - pub fn to_physical(&self, dpi_factor: f64) -> PhysicalPosition

{ + pub fn to_physical(&self, scale_factor: f64) -> PhysicalPosition

{ match *self { Position::Physical(position) => position.cast(), - Position::Logical(position) => position.to_physical(dpi_factor), + Position::Logical(position) => position.to_physical(scale_factor), } } } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index a4cf29faf0..ef0639d1b7 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -61,10 +61,10 @@ impl EventLoop { while let Ok(event) = self.event_rx.try_recv() { let e = match event { android_glue::Event::EventMotion(motion) => { - let dpi_factor = MonitorHandle.scale_factor(); + let scale_factor = MonitorHandle.scale_factor(); let location = LogicalPosition::from_physical( (motion.x as f64, motion.y as f64), - dpi_factor, + scale_factor, ); Some(Event::WindowEvent { window_id: RootWindowId(WindowId), @@ -102,9 +102,9 @@ impl EventLoop { if native_window.is_null() { None } else { - let dpi_factor = MonitorHandle.scale_factor(); + let scale_factor = MonitorHandle.scale_factor(); let physical_size = MonitorHandle.size(); - let size = LogicalSize::from_physical(physical_size, dpi_factor); + let size = LogicalSize::from_physical(physical_size, scale_factor); Some(Event::WindowEvent { window_id: RootWindowId(WindowId), event: WindowEvent::Resized(size), @@ -319,9 +319,9 @@ impl Window { if self.native_window.is_null() { None } else { - let dpi_factor = self.scale_factor(); + let scale_factor = self.scale_factor(); let physical_size = self.current_monitor().size(); - Some(LogicalSize::from_physical(physical_size, dpi_factor)) + Some(LogicalSize::from_physical(physical_size, scale_factor)) } } diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 33ecac37a4..77f3fc5995 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -127,12 +127,12 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { let screen_space: id = msg_send![screen, coordinateSpace]; let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; - let dpi_factor: CGFloat = msg_send![screen, scale]; + let scale_factor: CGFloat = msg_send![screen, scale]; let size = crate::dpi::LogicalSize { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, } - .to_physical(dpi_factor.into()); + .to_physical(scale_factor.into()); app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::Resized(size), @@ -162,15 +162,15 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { // `setContentScaleFactor` may be called with a value of 0, which means "reset the // content scale factor to a device-specific default value", so we can't use the // parameter here. We can query the actual factor using the getter - let dpi_factor: CGFloat = msg_send![object, contentScaleFactor]; + let scale_factor: CGFloat = msg_send![object, contentScaleFactor]; assert!( - !dpi_factor.is_nan() - && dpi_factor.is_finite() - && dpi_factor.is_sign_positive() - && dpi_factor > 0.0, + !scale_factor.is_nan() + && scale_factor.is_finite() + && scale_factor.is_sign_positive() + && scale_factor > 0.0, "invalid scale_factor set on UIView", ); - let scale_factor: f64 = dpi_factor.into(); + let scale_factor: f64 = scale_factor.into(); let bounds: CGRect = msg_send![object, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index a5d3e89337..ec943116db 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -83,8 +83,8 @@ impl Inner { x: safe_area.origin.x as f64, y: safe_area.origin.y as f64, }; - let dpi_factor = self.scale_factor(); - Ok(position.to_physical(dpi_factor)) + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) } } @@ -95,15 +95,15 @@ impl Inner { x: screen_frame.origin.x as f64, y: screen_frame.origin.y as f64, }; - let dpi_factor = self.scale_factor(); - Ok(position.to_physical(dpi_factor)) + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) } } pub fn set_outer_position(&self, physical_position: Position) { unsafe { - let dpi_factor = self.scale_factor(); - let position = physical_position.to_logical::(dpi_factor); + let scale_factor = self.scale_factor(); + let position = physical_position.to_logical::(scale_factor); let screen_frame = self.screen_frame(); let new_screen_frame = CGRect { origin: CGPoint { @@ -119,25 +119,25 @@ impl Inner { pub fn inner_size(&self) -> PhysicalSize { unsafe { - let dpi_factor = self.scale_factor(); + let scale_factor = self.scale_factor(); let safe_area = self.safe_area_screen_space(); let size = LogicalSize { width: safe_area.size.width as f64, height: safe_area.size.height as f64, }; - size.to_physical(dpi_factor) + size.to_physical(scale_factor) } } pub fn outer_size(&self) -> PhysicalSize { unsafe { - let dpi_factor = self.scale_factor(); + let scale_factor = self.scale_factor(); let screen_frame = self.screen_frame(); let size = LogicalSize { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, }; - size.to_physical(dpi_factor) + size.to_physical(scale_factor) } } @@ -355,8 +355,8 @@ impl Window { let frame = match window_attributes.inner_size { Some(dim) => { - let dpi_factor = msg_send![screen, scale]; - let size = dim.to_logical::(dpi_factor); + let scale_factor = msg_send![screen, scale]; + let size = dim.to_logical::(scale_factor); CGRect { origin: screen_bounds.origin, size: CGSize { @@ -400,8 +400,8 @@ impl Window { // Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized` // event on window creation if the DPI factor != 1.0 - let dpi_factor: CGFloat = msg_send![view, contentScaleFactor]; - let scale_factor: f64 = dpi_factor.into(); + let scale_factor: CGFloat = msg_send![view, contentScaleFactor]; + let scale_factor: f64 = scale_factor.into(); if scale_factor != 1.0 { let bounds: CGRect = msg_send![view, bounds]; let screen: id = msg_send![window, screen]; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index d8bb99e83f..f178ba3aa5 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -136,23 +136,23 @@ impl UnownedWindow { }) .unwrap_or_else(|| monitors.swap_remove(0)) }; - let dpi_factor = guessed_monitor.scale_factor(); + let scale_factor = guessed_monitor.scale_factor(); - info!("Guessed window scale factor: {}", dpi_factor); + info!("Guessed window scale factor: {}", scale_factor); let max_inner_size: Option<(u32, u32)> = window_attrs .max_inner_size - .map(|size| size.to_physical::(dpi_factor).into()); + .map(|size| size.to_physical::(scale_factor).into()); let min_inner_size: Option<(u32, u32)> = window_attrs .min_inner_size - .map(|size| size.to_physical::(dpi_factor).into()); + .map(|size| size.to_physical::(scale_factor).into()); let dimensions = { // x11 only applies constraints when the window is actively resized // by the user, so we have to manually apply the initial constraints let mut dimensions: (u32, u32) = window_attrs .inner_size - .map(|size| size.to_physical::(dpi_factor)) + .map(|size| size.to_physical::(scale_factor)) .or_else(|| Some((800, 600).into())) .map(Into::into) .unwrap(); @@ -324,10 +324,10 @@ impl UnownedWindow { { let mut min_inner_size = window_attrs .min_inner_size - .map(|size| size.to_physical::(dpi_factor)); + .map(|size| size.to_physical::(scale_factor)); let mut max_inner_size = window_attrs .max_inner_size - .map(|size| size.to_physical::(dpi_factor)); + .map(|size| size.to_physical::(scale_factor)); if !window_attrs.resizable { if util::wm_name_is_one_of(&["Xfwm4"]) { @@ -351,12 +351,12 @@ impl UnownedWindow { normal_hints.set_resize_increments( pl_attribs .resize_increments - .map(|size| size.to_physical::(dpi_factor).into()), + .map(|size| size.to_physical::(scale_factor).into()), ); normal_hints.set_base_size( pl_attribs .base_size - .map(|size| size.to_physical::(dpi_factor).into()), + .map(|size| size.to_physical::(scale_factor).into()), ); xconn.set_normal_hints(window.xwindow, normal_hints).queue(); } @@ -1053,8 +1053,8 @@ impl UnownedWindow { #[inline] pub fn set_inner_size(&self, size: Size) { - let dpi_factor = self.scale_factor(); - let (width, height) = size.to_physical::(dpi_factor).into(); + let scale_factor = self.scale_factor(); + let (width, height) = size.to_physical::(scale_factor).into(); self.set_inner_size_physical(width, height); } @@ -1097,16 +1097,16 @@ impl UnownedWindow { pub(crate) fn adjust_for_dpi( &self, - old_dpi_factor: f64, - new_dpi_factor: f64, + old_scale_factor: f64, + new_scale_factor: f64, width: u32, height: u32, shared_state: &SharedState, ) -> (u32, u32) { - let scale_factor = new_dpi_factor / old_dpi_factor; + let scale_factor = new_scale_factor / old_scale_factor; self.update_normal_hints(|normal_hints| { let dpi_adjuster = - |size: Size| -> (u32, u32) { size.to_physical::(new_dpi_factor).into() }; + |size: Size| -> (u32, u32) { size.to_physical::(new_scale_factor).into() }; let max_size = shared_state.max_inner_size.map(&dpi_adjuster); let min_size = shared_state.min_inner_size.map(&dpi_adjuster); let resize_increments = shared_state.resize_increments.map(&dpi_adjuster); @@ -1146,12 +1146,12 @@ impl UnownedWindow { self.set_maximizable_inner(resizable).queue(); - let dpi_factor = self.scale_factor(); + let scale_factor = self.scale_factor(); let min_inner_size = min_size - .map(|size| size.to_physical::(dpi_factor)) + .map(|size| size.to_physical::(scale_factor)) .map(Into::into); let max_inner_size = max_size - .map(|size| size.to_physical::(dpi_factor)) + .map(|size| size.to_physical::(scale_factor)) .map(Into::into); self.update_normal_hints(|normal_hints| { normal_hints.set_min_size(min_inner_size); diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index b800d4d4b1..2d692949ed 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -340,7 +340,7 @@ impl UnownedWindow { let input_context = unsafe { util::create_input_context(*ns_view) }; - let dpi_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 }; + let scale_factor = unsafe { NSWindow::backingScaleFactor(*ns_window) as f64 }; unsafe { if win_attribs.transparent { @@ -350,11 +350,11 @@ impl UnownedWindow { ns_app.activateIgnoringOtherApps_(YES); win_attribs.min_inner_size.map(|dim| { - let logical_dim = dim.to_logical(dpi_factor); + let logical_dim = dim.to_logical(scale_factor); set_min_inner_size(*ns_window, logical_dim) }); win_attribs.max_inner_size.map(|dim| { - let logical_dim = dim.to_logical(dpi_factor); + let logical_dim = dim.to_logical(scale_factor); set_max_inner_size(*ns_window, logical_dim) }); @@ -378,7 +378,7 @@ impl UnownedWindow { let decorations = win_attribs.decorations; let inner_rect = win_attribs .inner_size - .map(|size| size.to_physical(dpi_factor)); + .map(|size| size.to_physical(scale_factor)); let window = Arc::new(UnownedWindow { ns_view, @@ -450,8 +450,8 @@ impl UnownedWindow { frame_rect.origin.x as f64, util::bottom_left_to_top_left(frame_rect), ); - let dpi_factor = self.scale_factor(); - Ok(position.to_physical(dpi_factor)) + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) } pub fn inner_position(&self) -> Result, NotSupportedError> { @@ -462,13 +462,13 @@ impl UnownedWindow { content_rect.origin.x as f64, util::bottom_left_to_top_left(content_rect), ); - let dpi_factor = self.scale_factor(); - Ok(position.to_physical(dpi_factor)) + let scale_factor = self.scale_factor(); + Ok(position.to_physical(scale_factor)) } pub fn set_outer_position(&self, position: Position) { - let dpi_factor = self.scale_factor(); - let position = position.to_logical(dpi_factor); + let scale_factor = self.scale_factor(); + let position = position.to_logical(scale_factor); let dummy = NSRect::new( NSPoint::new( position.x, @@ -488,8 +488,8 @@ impl UnownedWindow { let view_frame = unsafe { NSView::frame(*self.ns_view) }; let logical: LogicalSize = (view_frame.size.width as f64, view_frame.size.height as f64).into(); - let dpi_factor = self.scale_factor(); - logical.to_physical(dpi_factor) + let scale_factor = self.scale_factor(); + logical.to_physical(scale_factor) } #[inline] @@ -497,15 +497,15 @@ impl UnownedWindow { let view_frame = unsafe { NSWindow::frame(*self.ns_window) }; let logical: LogicalSize = (view_frame.size.width as f64, view_frame.size.height as f64).into(); - let dpi_factor = self.scale_factor(); - logical.to_physical(dpi_factor) + let scale_factor = self.scale_factor(); + logical.to_physical(scale_factor) } #[inline] pub fn set_inner_size(&self, size: Size) { unsafe { - let dpi_factor = self.scale_factor(); - util::set_content_size_async(*self.ns_window, size.to_logical(dpi_factor)); + let scale_factor = self.scale_factor(); + util::set_content_size_async(*self.ns_window, size.to_logical(scale_factor)); } } @@ -515,8 +515,8 @@ impl UnownedWindow { width: 0.0, height: 0.0, })); - let dpi_factor = self.scale_factor(); - set_min_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor)); + let scale_factor = self.scale_factor(); + set_min_inner_size(*self.ns_window, dimensions.to_logical(scale_factor)); } } @@ -526,8 +526,8 @@ impl UnownedWindow { width: std::f32::MAX as f64, height: std::f32::MAX as f64, })); - let dpi_factor = self.scale_factor(); - set_max_inner_size(*self.ns_window, dimensions.to_logical(dpi_factor)); + let scale_factor = self.scale_factor(); + set_max_inner_size(*self.ns_window, dimensions.to_logical(scale_factor)); } } @@ -594,9 +594,9 @@ impl UnownedWindow { #[inline] pub fn set_cursor_position(&self, cursor_position: Position) -> Result<(), ExternalError> { let physical_window_position = self.inner_position().unwrap(); - let dpi_factor = self.scale_factor(); - let window_position = physical_window_position.to_logical::(dpi_factor); - let logical_cursor_position = cursor_position.to_logical::(dpi_factor); + let scale_factor = self.scale_factor(); + let window_position = physical_window_position.to_logical::(scale_factor); + let logical_cursor_position = cursor_position.to_logical::(scale_factor); let point = appkit::CGPoint { x: logical_cursor_position.x + window_position.x, y: logical_cursor_position.y + window_position.y, @@ -933,8 +933,8 @@ impl UnownedWindow { #[inline] pub fn set_ime_position(&self, spot: Position) { - let dpi_factor = self.scale_factor(); - let logical_spot = spot.to_logical(dpi_factor); + let scale_factor = self.scale_factor(); + let logical_spot = spot.to_logical(scale_factor); unsafe { view::set_ime_position( *self.ns_view, diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 7766893782..f04d7c73eb 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -43,7 +43,7 @@ pub struct WindowDelegateState { previous_position: Option<(f64, f64)>, // Used to prevent redundant events. - previous_dpi_factor: f64, + previous_scale_factor: f64, } impl WindowDelegateState { @@ -55,7 +55,7 @@ impl WindowDelegateState { window: Arc::downgrade(&window), initial_fullscreen, previous_position: None, - previous_dpi_factor: scale_factor, + previous_scale_factor: scale_factor, }; if scale_factor != 1.0 { @@ -82,11 +82,11 @@ impl WindowDelegateState { pub fn emit_static_scale_factor_changed_event(&mut self) { let scale_factor = self.get_scale_factor(); - if scale_factor == self.previous_dpi_factor { + if scale_factor == self.previous_scale_factor { return (); }; - self.previous_dpi_factor = scale_factor; + self.previous_scale_factor = scale_factor; let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { ns_window: IdRef::retain(*self.ns_window), suggested_size: self.view_size(), diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 0049b09c89..7e1d929edc 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1422,7 +1422,7 @@ unsafe extern "system" fn public_window_callback( if window_state.min_size.is_some() || window_state.max_size.is_some() { if let Some(min_size) = window_state.min_size { - let min_size = min_size.to_physical(window_state.dpi_factor); + let min_size = min_size.to_physical(window_state.scale_factor); let (width, height): (u32, u32) = util::adjust_size(window, min_size).into(); (*mmi).ptMinTrackSize = POINT { x: width as i32, @@ -1430,7 +1430,7 @@ unsafe extern "system" fn public_window_callback( }; } if let Some(max_size) = window_state.max_size { - let max_size = max_size.to_physical(window_state.dpi_factor); + let max_size = max_size.to_physical(window_state.scale_factor); let (width, height): (u32, u32) = util::adjust_size(window, max_size).into(); (*mmi).ptMaxTrackSize = POINT { x: width as i32, @@ -1452,15 +1452,15 @@ unsafe extern "system" fn public_window_callback( // application since they are the same". // https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx let new_dpi_x = u32::from(LOWORD(wparam as DWORD)); - let new_dpi_factor = dpi_to_scale_factor(new_dpi_x); - let old_dpi_factor: f64; + let new_scale_factor = dpi_to_scale_factor(new_dpi_x); + let old_scale_factor: f64; let allow_resize = { let mut window_state = subclass_input.window_state.lock(); - old_dpi_factor = window_state.dpi_factor; - window_state.dpi_factor = new_dpi_factor; + old_scale_factor = window_state.scale_factor; + window_state.scale_factor = new_scale_factor; - if new_dpi_factor == old_dpi_factor { + if new_scale_factor == old_scale_factor { return 0; } @@ -1516,15 +1516,15 @@ unsafe extern "system" fn public_window_callback( // We calculate our own size because the default suggested rect doesn't do a great job // of preserving the window's logical size. true => old_physical_inner_size - .to_logical::(old_dpi_factor) - .to_physical::(new_dpi_factor), + .to_logical::(old_scale_factor) + .to_physical::(new_scale_factor), false => old_physical_inner_size, }; let _ = subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ScaleFactorChanged { - scale_factor: new_dpi_factor, + scale_factor: new_scale_factor, new_inner_size: &mut new_physical_inner_size, }, }); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index f3e7851a12..7feb631c5b 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -217,8 +217,8 @@ impl Window { #[inline] pub fn set_inner_size(&self, size: Size) { - let dpi_factor = self.scale_factor(); - let (width, height) = size.to_physical::(dpi_factor).into(); + let scale_factor = self.scale_factor(); + let (width, height) = size.to_physical::(scale_factor).into(); let window_state = Arc::clone(&self.window_state); let window = self.window.clone(); @@ -325,13 +325,13 @@ impl Window { #[inline] pub fn scale_factor(&self) -> f64 { - self.window_state.lock().dpi_factor + self.window_state.lock().scale_factor } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - let dpi_factor = self.scale_factor(); - let (x, y) = position.to_physical::(dpi_factor).into(); + let scale_factor = self.scale_factor(); + let (x, y) = position.to_physical::(scale_factor).into(); let mut point = POINT { x, y }; unsafe { @@ -402,7 +402,7 @@ impl Window { let client_rect = util::get_client_rect(window.0).unwrap(); window_state_lock.saved_window = Some(SavedWindow { client_rect, - dpi_factor: window_state_lock.dpi_factor, + scale_factor: window_state_lock.scale_factor, }); } _ => (), @@ -513,10 +513,10 @@ impl Window { let mut window_state_lock = window_state.lock(); if let Some(SavedWindow { client_rect, - dpi_factor, + scale_factor, }) = window_state_lock.saved_window.take() { - window_state_lock.dpi_factor = dpi_factor; + window_state_lock.scale_factor = scale_factor; drop(window_state_lock); let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap(); @@ -712,7 +712,7 @@ unsafe fn init( } let dpi = hwnd_dpi(real_window.0); - let dpi_factor = dpi_to_scale_factor(dpi); + let scale_factor = dpi_to_scale_factor(dpi); // making the window transparent if attributes.transparent && !pl_attribs.no_redirection_bitmap { @@ -758,7 +758,7 @@ unsafe fn init( &attributes, window_icon, taskbar_icon, - dpi_factor, + scale_factor, dark_mode, ); let window_state = Arc::new(Mutex::new(window_state)); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 8150d1199a..c2f8ee36fa 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -26,7 +26,7 @@ pub struct WindowState { pub taskbar_icon: Option, pub saved_window: Option, - pub dpi_factor: f64, + pub scale_factor: f64, pub fullscreen: Option, /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple @@ -40,7 +40,7 @@ pub struct WindowState { #[derive(Clone)] pub struct SavedWindow { pub client_rect: RECT, - pub dpi_factor: f64, + pub scale_factor: f64, } #[derive(Clone)] @@ -98,7 +98,7 @@ impl WindowState { attributes: &WindowAttributes, window_icon: Option, taskbar_icon: Option, - dpi_factor: f64, + scale_factor: f64, is_dark_mode: bool, ) -> WindowState { WindowState { @@ -115,7 +115,7 @@ impl WindowState { taskbar_icon, saved_window: None, - dpi_factor, + scale_factor, fullscreen: None, queued_out_of_band_redraw: false, From 505f312d5f1ef303e219c3293cf64c8cdb573cd6 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Thu, 13 Feb 2020 23:20:32 +0100 Subject: [PATCH 100/239] Add new example that demonstrates the different control flow schemes (#1460) User can switch between Wait, WaitUntil and Poll modes with key '1', '2' and '3' respectivly. User can toggle request_redraw calls with the 'R' key. Helpful for testing all control flow modes and use of request_redraw. --- examples/control_flow.rs | 113 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 examples/control_flow.rs diff --git a/examples/control_flow.rs b/examples/control_flow.rs new file mode 100644 index 0000000000..a76f9f4f53 --- /dev/null +++ b/examples/control_flow.rs @@ -0,0 +1,113 @@ +use std::{thread, time}; + +use winit::{ + event::{Event, KeyboardInput, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Mode { + Wait, + WaitUntil, + Poll, +} + +const WAIT_TIME: time::Duration = time::Duration::from_millis(100); +const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); + +fn main() { + simple_logger::init().unwrap(); + + println!("Press '1' to switch to Wait mode."); + println!("Press '2' to switch to WaitUntil mode."); + println!("Press '3' to switch to Poll mode."); + println!("Press 'R' to toggle request_redraw() calls."); + println!("Press 'Esc' to close the window."); + + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .with_title("Press 1, 2, 3 to change control flow mode. Press R to toggle redraw requests.") + .build(&event_loop) + .unwrap(); + + let mut mode = Mode::Wait; + let mut request_redraw = false; + let mut wait_cancelled = false; + let mut close_requested = false; + + event_loop.run(move |event, _, control_flow| { + use winit::event::{ElementState, StartCause, VirtualKeyCode}; + println!("{:?}", event); + match event { + Event::NewEvents(start_cause) => { + wait_cancelled = mode == Mode::WaitUntil; + match start_cause { + StartCause::ResumeTimeReached { + start: _, + requested_resume: _, + } => { + wait_cancelled = false; + } + _ => (), + } + } + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => { + close_requested = true; + } + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(virtual_code), + state: ElementState::Pressed, + .. + }, + .. + } => match virtual_code { + VirtualKeyCode::Key1 => { + mode = Mode::Wait; + println!("\nmode: {:?}\n", mode); + } + VirtualKeyCode::Key2 => { + mode = Mode::WaitUntil; + println!("\nmode: {:?}\n", mode); + } + VirtualKeyCode::Key3 => { + mode = Mode::Poll; + println!("\nmode: {:?}\n", mode); + } + VirtualKeyCode::R => { + request_redraw = !request_redraw; + println!("\nrequest_redraw: {}\n", request_redraw); + } + VirtualKeyCode::Escape => { + close_requested = true; + } + _ => (), + }, + _ => (), + }, + Event::MainEventsCleared => { + if request_redraw && !wait_cancelled && !close_requested { + window.request_redraw(); + } + if close_requested { + *control_flow = ControlFlow::Exit; + } + } + Event::RedrawRequested(_window_id) => {} + Event::RedrawEventsCleared => { + *control_flow = match mode { + Mode::Wait => ControlFlow::Wait, + Mode::WaitUntil => ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME), + Mode::Poll => { + thread::sleep(POLL_SLEEP_TIME); + ControlFlow::Poll + } + }; + } + _ => (), + } + }); +} From bc29931434a6c6c445f382fce9731311ebfbbf7f Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sat, 15 Feb 2020 19:38:29 +0100 Subject: [PATCH 101/239] Add an example that calls request_redraw() from a thread (#1467) reproduces https://github.com/rust-windowing/winit/issues/1466 --- examples/request_redraw_threaded.rs | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 examples/request_redraw_threaded.rs diff --git a/examples/request_redraw_threaded.rs b/examples/request_redraw_threaded.rs new file mode 100644 index 0000000000..341612ca64 --- /dev/null +++ b/examples/request_redraw_threaded.rs @@ -0,0 +1,39 @@ +use std::{thread, time}; + +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +fn main() { + simple_logger::init().unwrap(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + thread::spawn(move || loop { + thread::sleep(time::Duration::from_secs(1)); + window.request_redraw(); + }); + + event_loop.run(move |event, _, control_flow| { + println!("{:?}", event); + + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + _ => (), + }, + Event::RedrawRequested(_) => { + println!("\nredrawing!\n"); + } + _ => (), + } + }); +} From e88e8bc1942865098b98419f6f7f07f73fc26179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n?= Date: Sun, 16 Feb 2020 18:53:02 +0100 Subject: [PATCH 102/239] Map `UserEvent` properly in `Event::to_static` (#1468) --- CHANGELOG.md | 1 + src/event.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5876b52909..ed9baa58b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On Wayland, fix color from `close_button_icon_color` not applying. - Ignore locale if unsupported by X11 backend - On Wayland, Add HiDPI cursor support +- Fix `Event::to_static` returning `None` for user events. # 0.21.0 (2020-02-04) diff --git a/src/event.rs b/src/event.rs index 10ab621709..494a4a1d47 100644 --- a/src/event.rs +++ b/src/event.rs @@ -139,7 +139,7 @@ impl<'a, T> Event<'a, T> { WindowEvent { window_id, event } => event .to_static() .map(|event| WindowEvent { window_id, event }), - UserEvent(_) => None, + UserEvent(event) => Some(UserEvent(event)), DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }), NewEvents(cause) => Some(NewEvents(cause)), MainEventsCleared => Some(MainEventsCleared), From d1073dcecb4590a9764435f5e5bf01e3e7c5fd91 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 17 Feb 2020 20:25:27 +0100 Subject: [PATCH 103/239] Implement ThemeChanged for web target. (#1462) * Implement ThemeChanged for web target. * Add TODO upstream to stdweb. Co-authored-by: Ryan G --- CHANGELOG.md | 1 + Cargo.toml | 2 ++ FEATURES.md | 3 ++ src/platform/web.rs | 6 ++++ .../web/event_loop/window_target.rs | 15 +++++++++- src/platform_impl/web/stdweb/canvas.rs | 17 +++++++++++ src/platform_impl/web/stdweb/mod.rs | 10 +++++++ src/platform_impl/web/web_sys/canvas.rs | 29 ++++++++++++++++++- src/platform_impl/web/web_sys/mod.rs | 11 +++++++ 9 files changed, 92 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9baa58b1..92924ce5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On Wayland, fix color from `close_button_icon_color` not applying. - Ignore locale if unsupported by X11 backend - On Wayland, Add HiDPI cursor support +- On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change. - Fix `Event::to_static` returning `None` for user events. # 0.21.0 (2020-02-04) diff --git a/Cargo.toml b/Cargo.toml index b537dca432..f0ac5fdd11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,8 @@ features = [ 'HtmlCanvasElement', 'HtmlElement', 'KeyboardEvent', + 'MediaQueryList', + 'MediaQueryListEvent', 'MouseEvent', 'Node', 'PointerEvent', diff --git a/FEATURES.md b/FEATURES.md index 3d43958807..0918d358f5 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -149,6 +149,9 @@ If your PR makes notable changes to Winit's features, please update this section * Getting the device idiom * Getting the preferred video mode +### Web +* Get if systems preferred color scheme is "dark" + ## Usability * `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial) diff --git a/src/platform/web.rs b/src/platform/web.rs index 6083f5cb30..c5ceca90bb 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -14,6 +14,9 @@ use stdweb::web::html_element::CanvasElement; #[cfg(feature = "stdweb")] pub trait WindowExtStdweb { fn canvas(&self) -> CanvasElement; + + /// Whether the browser reports the preferred color scheme to be "dark". + fn is_dark_mode(&self) -> bool; } #[cfg(feature = "web-sys")] @@ -22,6 +25,9 @@ use web_sys::HtmlCanvasElement; #[cfg(feature = "web-sys")] pub trait WindowExtWebSys { fn canvas(&self) -> HtmlCanvasElement; + + /// Whether the browser reports the preferred color scheme to be "dark". + fn is_dark_mode(&self) -> bool; } #[cfg(feature = "stdweb")] diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 2395f34678..db485a16d3 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -2,7 +2,7 @@ use super::{backend, device, proxy::Proxy, runner, window}; use crate::dpi::{PhysicalSize, Size}; use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; -use crate::window::WindowId; +use crate::window::{Theme, WindowId}; use std::clone::Clone; pub struct WindowTarget { @@ -199,5 +199,18 @@ impl WindowTarget { }); runner.request_redraw(WindowId(id)); }); + + let runner = self.runner.clone(); + canvas.on_dark_mode(move |is_dark_mode| { + let theme = if is_dark_mode { + Theme::Dark + } else { + Theme::Light + }; + runner.send_event(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::ThemeChanged(theme), + }); + }); } } diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index a3581917dc..f6ed4866b9 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -6,6 +6,7 @@ use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; use std::cell::RefCell; use std::rc::Rc; +use stdweb::js; use stdweb::traits::IPointerEvent; use stdweb::unstable::TryInto; use stdweb::web::event::{ @@ -240,6 +241,22 @@ impl Canvas { self.on_fullscreen_change = Some(self.add_event(move |_: FullscreenChangeEvent| handler())); } + pub fn on_dark_mode(&mut self, handler: F) + where + F: 'static + FnMut(bool), + { + // TODO: upstream to stdweb + js! { + var handler = @{handler}; + + if (window.matchMedia) { + window.matchMedia("(prefers-color-scheme: dark)").addListener(function(e) { + handler(event.matches) + }); + } + } + } + fn add_event(&self, mut handler: F) -> EventListenerHandle where E: ConcreteEvent, diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 9274ab9916..a95dfc7d36 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -10,6 +10,7 @@ use crate::platform::web::WindowExtStdweb; use crate::window::Window; use stdweb::js; +use stdweb::unstable::TryInto; use stdweb::web::event::BeforeUnloadEvent; use stdweb::web::window; use stdweb::web::IEventTarget; @@ -31,6 +32,15 @@ impl WindowExtStdweb for Window { fn canvas(&self) -> CanvasElement { self.window.canvas().raw().clone() } + + fn is_dark_mode(&self) -> bool { + // TODO: upstream to stdweb + let is_dark_mode = js! { + return (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) + }; + + is_dark_mode.try_into().expect("should return a bool") + } } pub fn window_size() -> LogicalSize { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 970f87dadb..a2755b95d2 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -8,7 +8,10 @@ use std::cell::RefCell; use std::rc::Rc; use wasm_bindgen::{closure::Closure, JsCast}; -use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent}; +use web_sys::{ + Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, PointerEvent, + WheelEvent, +}; pub struct Canvas { /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. @@ -26,6 +29,7 @@ pub struct Canvas { on_mouse_wheel: Option>, on_fullscreen_change: Option>, wants_fullscreen: Rc>, + on_dark_mode: Option>, } impl Drop for Canvas { @@ -77,6 +81,7 @@ impl Canvas { on_mouse_wheel: None, on_fullscreen_change: None, wants_fullscreen: Rc::new(RefCell::new(false)), + on_dark_mode: None, }) } @@ -251,6 +256,28 @@ impl Canvas { Some(self.add_event("fullscreenchange", move |_: Event| handler())); } + pub fn on_dark_mode(&mut self, mut handler: F) + where + F: 'static + FnMut(bool), + { + let window = web_sys::window().expect("Failed to obtain window"); + + self.on_dark_mode = window + .match_media("(prefers-color-scheme: dark)") + .ok() + .flatten() + .and_then(|media| { + let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| { + handler(event.matches()) + }) as Box); + + media + .add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref())) + .map(|_| closure) + .ok() + }); + } + fn add_event(&self, event_name: &str, mut handler: F) -> Closure where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index cafbae1fb1..c41cd069f2 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -38,6 +38,17 @@ impl WindowExtWebSys for Window { fn canvas(&self) -> HtmlCanvasElement { self.window.canvas().raw().clone() } + + fn is_dark_mode(&self) -> bool { + let window = web_sys::window().expect("Failed to obtain window"); + + window + .match_media("(prefers-color-scheme: dark)") + .ok() + .flatten() + .map(|media| media.matches()) + .unwrap_or(false) + } } pub fn window_size() -> LogicalSize { From 76d0dd7ec36dc129f39dc201d490f6cd1dd457c4 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 19 Feb 2020 02:58:48 +0300 Subject: [PATCH 104/239] On Wayland, Hide CSD for fullscreen windows (#1473) --- CHANGELOG.md | 1 + src/platform_impl/linux/wayland/event_loop.rs | 12 +++++- src/platform_impl/linux/wayland/window.rs | 37 ++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92924ce5db..523dda5311 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - On Wayland, Add HiDPI cursor support - On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change. - Fix `Event::to_static` returning `None` for user events. +- On Wayland, Hide CSD for fullscreen windows. # 0.21.0 (2020-02-04) diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 4a1e195937..47c5aea634 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -39,7 +39,10 @@ use crate::{ window::{CursorIcon, WindowId as RootWindowId}, }; -use super::{window::WindowStore, DeviceId, WindowId}; +use super::{ + window::{DecorationsAction, WindowStore}, + DeviceId, WindowId, +}; use smithay_client_toolkit::{ output::OutputMgr, @@ -713,6 +716,13 @@ impl EventLoop { crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid)); if let Some(frame) = window.frame { if let Some((w, h)) = window.newsize { + // Update decorations state + match window.decorations_action { + Some(DecorationsAction::Hide) => frame.set_decorate(false), + Some(DecorationsAction::Show) => frame.set_decorate(true), + None => (), + } + // mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case // it overlaps mutter's `bounding box`, so we can't avoid this resize call, // which calls `set_geometry` under the hood, for now. diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 03b58b3807..7c30a79bf5 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -41,6 +41,13 @@ pub struct Window { need_refresh: Arc>, fullscreen: Arc>, cursor_grab_changed: Arc>>, // Update grab state + decorated: Arc>, +} + +#[derive(Clone, Copy, Debug)] +pub enum DecorationsAction { + Hide, + Show, } impl Window { @@ -69,6 +76,9 @@ impl Window { let window_store = evlp.store.clone(); + let decorated = Arc::new(Mutex::new(attributes.decorations)); + let pending_decorations_action = Arc::new(Mutex::new(None)); + let my_surface = surface.clone(); let mut frame = SWindow::::init_from_env( &evlp.env, @@ -83,7 +93,23 @@ impl Window { if window.surface.as_ref().equals(&my_surface.as_ref()) { window.newsize = new_size; *(window.need_refresh.lock().unwrap()) = true; - *(window.fullscreen.lock().unwrap()) = is_fullscreen; + { + // Get whether we're in fullscreen + let mut fullscreen = window.fullscreen.lock().unwrap(); + // Fullscreen state was changed, so update decorations + if *fullscreen != is_fullscreen { + let decorated = { *window.decorated.lock().unwrap() }; + if decorated { + *window.pending_decorations_action.lock().unwrap() = + if is_fullscreen { + Some(DecorationsAction::Hide) + } else { + Some(DecorationsAction::Show) + }; + } + } + *fullscreen = is_fullscreen; + } *(window.need_frame_refresh.lock().unwrap()) = true; return; } @@ -174,6 +200,8 @@ impl Window { frame: Arc::downgrade(&frame), current_dpi: 1, new_dpi: None, + decorated: decorated.clone(), + pending_decorations_action: pending_decorations_action.clone(), }); evlp.evq.borrow_mut().sync_roundtrip().unwrap(); @@ -189,6 +217,7 @@ impl Window { cursor_manager, fullscreen, cursor_grab_changed, + decorated, }) } @@ -277,6 +306,7 @@ impl Window { } pub fn set_decorations(&self, decorate: bool) { + *(self.decorated.lock().unwrap()) = decorate; self.frame.lock().unwrap().set_decorate(decorate); *(self.need_frame_refresh.lock().unwrap()) = true; } @@ -409,6 +439,8 @@ struct InternalWindow { frame: Weak>>, current_dpi: i32, new_dpi: Option, + decorated: Arc>, + pending_decorations_action: Arc>>, } pub struct WindowStore { @@ -425,6 +457,7 @@ pub struct WindowStoreForEach<'a> { pub surface: &'a wl_surface::WlSurface, pub wid: WindowId, pub frame: Option<&'a mut SWindow>, + pub decorations_action: Option, } impl WindowStore { @@ -482,6 +515,7 @@ impl WindowStore { let opt_arc = window.frame.upgrade(); let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); let mut size = { *window.size.lock().unwrap() }; + let decorations_action = { window.pending_decorations_action.lock().unwrap().take() }; f(WindowStoreForEach { newsize: window.newsize.take(), size: &mut size, @@ -492,6 +526,7 @@ impl WindowStore { surface: &window.surface, wid: make_wid(&window.surface), frame: opt_mutex_lock.as_mut().map(|m| &mut **m), + decorations_action, }); *window.size.lock().unwrap() = size; if let Some(dpi) = window.new_dpi.take() { From 522a6e329873720899241ca5516aae7138b351c3 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Wed, 19 Feb 2020 01:27:47 +0100 Subject: [PATCH 105/239] fix issues in wait_until_time_or_msg function (#1423) also removed unused return value --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 523dda5311..a69df9d491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On Windows, fix minor timing issue in wait_until_time_or_msg - On macOS, fix `set_simple_screen` to remember frame excluding title bar. - On Wayland, fix coordinates in touch events when scale factor isn't 1. - On Wayland, fix color from `close_button_icon_color` not applying. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 7e1d929edc..890e2f04f3 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -320,11 +320,9 @@ fn main_thread_id() -> DWORD { unsafe { MAIN_THREAD_ID } } -// Returns true if the wait time was reached, and false if a message must be processed. -unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool { - let mut msg = mem::zeroed(); +unsafe fn wait_until_time_or_msg(wait_until: Instant) { let now = Instant::now(); - if now <= wait_until { + if now < wait_until { // MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract 1 millisecond // from the requested time and spinlock for the remainder to compensate for that. let resume_reason = winuser::MsgWaitForMultipleObjectsEx( @@ -336,16 +334,16 @@ unsafe fn wait_until_time_or_msg(wait_until: Instant) -> bool { ); if resume_reason == winerror::WAIT_TIMEOUT { + let mut msg = mem::zeroed(); while Instant::now() < wait_until { if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { - return false; + break; } } } } - - return true; } + // Implementation taken from https://github.com/rust-lang/rust/blob/db5476571d9b27c862b95c1e64764b0ac8980e23/src/libstd/sys/windows/mod.rs fn dur2timeout(dur: Duration) -> DWORD { // Note that a duration is a (u64, u32) (seconds, nanoseconds) pair, and the From 9999f533291f3964721fa7a4677fc425b0463bb7 Mon Sep 17 00:00:00 2001 From: Murarth Date: Wed, 19 Feb 2020 10:39:00 -0700 Subject: [PATCH 106/239] X11: Fix deadlock when an error occurs during startup (#1475) --- src/platform_impl/linux/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index dfb18341fd..9411730324 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -584,13 +584,12 @@ impl EventLoop { } pub fn new_x11_any_thread() -> Result, XNotSupported> { - X11_BACKEND - .lock() - .as_ref() - .map(Arc::clone) - .map(x11::EventLoop::new) - .map(EventLoop::X) - .map_err(|err| err.clone()) + let xconn = match X11_BACKEND.lock().as_ref() { + Ok(xconn) => xconn.clone(), + Err(err) => return Err(err.clone()), + }; + + Ok(EventLoop::X(x11::EventLoop::new(xconn))) } #[inline] From 2b14ec23d54384d574a23dca4d73dc44a072bd46 Mon Sep 17 00:00:00 2001 From: Murarth Date: Tue, 25 Feb 2020 09:10:31 -0700 Subject: [PATCH 107/239] Fix GitHub Actions (#1479) * Replaces `actions/checkout@v1` with `actions/checkout@v2` to get a bug fix --- .github/workflows/ci.yml | 4 ++-- .github/workflows/publish.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40728a1410..459844569c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: Check_Formatting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: hecrj/setup-rust-action@v1 with: rust-version: stable @@ -54,7 +54,7 @@ jobs: runs-on: ${{ matrix.platform.os }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 # Used to cache cargo-web - name: Cache cargo folder uses: actions/cache@v1 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index eac13a0a99..b7c059e6f6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,7 +9,7 @@ jobs: Publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: hecrj/setup-rust-action@v1 with: rust-version: stable From ece2e70a53fd469341e89e2bbc293910882d6d6c Mon Sep 17 00:00:00 2001 From: HeroicKatora Date: Wed, 4 Mar 2020 00:13:53 +0100 Subject: [PATCH 108/239] Update image to 0.23 (#1485) Also makes use of a few ergonomics improvements that were introduced or optimized in the more recent version. --- Cargo.toml | 2 +- examples/window_icon.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f0ac5fdd11..3ea410c913 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ raw-window-handle = "0.3" bitflags = "1" [dev-dependencies] -image = "0.21" +image = "0.23" simple_logger = "1" [target.'cfg(target_os = "android")'.dependencies.android_glue] diff --git a/examples/window_icon.rs b/examples/window_icon.rs index b7c84679bb..734debf4d4 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -45,13 +45,11 @@ fn main() { fn load_icon(path: &Path) -> Icon { let (icon_rgba, icon_width, icon_height) = { - let image = image::open(path).expect("Failed to open icon path"); - use image::{GenericImageView, Pixel}; + let image = image::open(path) + .expect("Failed to open icon path") + .into_rgba(); let (width, height) = image.dimensions(); - let mut rgba = Vec::with_capacity((width * height) as usize * 4); - for (_, _, pixel) in image.pixels() { - rgba.extend_from_slice(&pixel.to_rgba().data); - } + let rgba = image.into_raw(); (rgba, width, height) }; Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon") From b8326f6452550908fd79b76084450a688b0cc41e Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Fri, 6 Mar 2020 18:48:54 +0100 Subject: [PATCH 109/239] In control_flow example, don't schedule a new WaitUntil if wait was cancelled (#1482) --- examples/control_flow.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/control_flow.rs b/examples/control_flow.rs index a76f9f4f53..13b5bcf832 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -100,7 +100,13 @@ fn main() { Event::RedrawEventsCleared => { *control_flow = match mode { Mode::Wait => ControlFlow::Wait, - Mode::WaitUntil => ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME), + Mode::WaitUntil => { + if wait_cancelled { + *control_flow + } else { + ControlFlow::WaitUntil(time::Instant::now() + WAIT_TIME) + } + } Mode::Poll => { thread::sleep(POLL_SLEEP_TIME); ControlFlow::Poll From 71bd6e73caac91b3be46d42545e40918a8d2ca70 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Fri, 6 Mar 2020 22:15:49 +0100 Subject: [PATCH 110/239] windows: ignore spurious mouse move messages (#1435) Fixes https://github.com/rust-windowing/winit/issues/1428 --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 28 +++++++++++++++-------- src/platform_impl/windows/window_state.rs | 4 +++- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a69df9d491..fa1d6bfb80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change. - Fix `Event::to_static` returning `None` for user events. - On Wayland, Hide CSD for fullscreen windows. +- On Windows, ignore spurious mouse move messages. # 0.21.0 (2020-02-04) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 890e2f04f3..38181089e0 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -856,15 +856,25 @@ unsafe extern "system" fn public_window_callback( let x = windowsx::GET_X_LPARAM(lparam) as f64; let y = windowsx::GET_Y_LPARAM(lparam) as f64; let position = PhysicalPosition::new(x, y); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: CursorMoved { - device_id: DEVICE_ID, - position, - modifiers: event::get_key_mods(), - }, - }); + let cursor_moved; + { + // handle spurious WM_MOUSEMOVE messages + // see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343 + // and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html + let mut w = subclass_input.window_state.lock(); + cursor_moved = w.mouse.last_position != Some(position); + w.mouse.last_position = Some(position); + } + if cursor_moved { + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: CursorMoved { + device_id: DEVICE_ID, + position, + modifiers: event::get_key_mods(), + }, + }); + } 0 } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index c2f8ee36fa..76485f46a4 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,5 +1,5 @@ use crate::{ - dpi::Size, + dpi::{PhysicalPosition, Size}, platform_impl::platform::{event_loop, icon::WinIcon, util}, window::{CursorIcon, Fullscreen, WindowAttributes}, }; @@ -48,6 +48,7 @@ pub struct MouseProperties { pub cursor: CursorIcon, pub buttons_down: u32, cursor_flags: CursorFlags, + pub last_position: Option>, } bitflags! { @@ -106,6 +107,7 @@ impl WindowState { cursor: CursorIcon::default(), buttons_down: 0, cursor_flags: CursorFlags::empty(), + last_position: None, }, min_size: attributes.min_inner_size, From e707052f66045a096a391e8d23cc41cba100db5f Mon Sep 17 00:00:00 2001 From: Murarth Date: Fri, 6 Mar 2020 15:43:55 -0700 Subject: [PATCH 111/239] Move `ModifiersChanged` variant to `WindowEvent` (#1381) * Move `ModifiersChanged` variant to `WindowEvent` * macos: Fix flags_changed for ModifiersChanged variant move I haven't look too deep at what this does internally, but at least cargo-check is fully happy now. :) Signed-off-by: Kristofer Rye * macos: Fire a ModifiersChanged event on window_did_resign_key From debugging, I determined that macOS' emission of a flagsChanged around window switching is inconsistent. It is fair to assume, I think, that when the user switches windows, they do not expect their former modifiers state to remain effective; so I think it's best to clear that state by sending a ModifiersChanged(ModifiersState::empty()). Signed-off-by: Kristofer Rye * windows: Fix build I don't know enough about the code to implement the fix as it is done on this branch, but this commit at least fixes the build. Signed-off-by: Kristofer Rye * windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS Very similar to the changes made in [1], as focus is lost, send an event to the window indicating that the modifiers have been released. It's unclear to me (without a Windows device to test this on) whether this is necessary, but it certainly ensures that unfocused windows will have at least received this event, which is an improvement. [1]: f79f21641a31da3e4039d41be89047cdcc6028f7 Signed-off-by: Kristofer Rye * macos: Add a hook to update stale modifiers Sometimes, `ViewState` and `event` might have different values for their stored `modifiers` flags. These are internally stored as a bitmask in the latter and an enum in the former. We can check to see if they differ, and if they do, automatically dispatch an event to update consumers of modifier state as well as the stored `state.modifiers`. That's what the hook does. This hook is then called in the key_down, mouse_entered, mouse_exited, mouse_click, scroll_wheel, and pressure_change_with_event callbacks, which each will contain updated modifiers. Signed-off-by: Kristofer Rye * Only call event_mods once when determining whether to update state Signed-off-by: Kristofer Rye * flags_changed: Memoize window_id collection Signed-off-by: Kristofer Rye * window_did_resign_key: Remove synthetic ModifiersChanged event We no longer need to emit this event, since we are checking the state of our modifiers before emitting most other events. Signed-off-by: Kristofer Rye * mouse_motion: Add a call to update_potentially_stale_modifiers Now, cover all events (that I can think of, at least) where stale modifiers might affect how user programs behave. Effectively, every human-interface event (keypress, mouse click, keydown, etc.) will cause a ModifiersChanged event to be fired if something has changed. Signed-off-by: Kristofer Rye * key_up: Add a call to update_potentially_stale_modifiers We also want to make sure modifiers state is synchronized here, too. Signed-off-by: Kristofer Rye * mouse_motion: Remove update_potentially_stale_modifiers invocation Signed-off-by: Kristofer Rye * Retry CI * ViewState: Promote visibility of modifiers to the macos impl This is so that we can interact with the ViewState directly from the WindowDelegate. Signed-off-by: Kristofer Rye * window_delegate: Synthetically set modifiers state to empty on resignKey This logic is implemented similarly on other platforms, so we wish to regain parity here. Originally this behavior was implemented to always fire an event with ModifiersState::empty(), but that was not the best as it was not necessarily correct and could be a duplicate event. This solution is perhaps the most elegant possible to implement the desired behavior of sending a synthetic empty modifiers event when a window loses focus, trading some safety for interoperation between the NSWindowDelegate and the NSView (as the objc runtime must now be consulted in order to acquire access to the ViewState which is "owned" by the NSView). Signed-off-by: Kristofer Rye * Check for modifiers change in window events * Fix modifier changed on macOS Since the `mouse_entered` function was generating a mouse motion, which updates the modifier state, a modifiers changed event was incorrectly generated. The updating of the modifier state has also been changed to make sure it consistently happens before events that have a modifier state attached to it, without happening on any other event. This of course means that no `CursorMoved` event is generated anymore when the user enters the window without it being focused, however I'd say that is consistent with how winit should behave. * Fix unused variable warning * Move changelog entry into `Unreleased` section Co-authored-by: Freya Gentz Co-authored-by: Kristofer Rye Co-authored-by: Christian Duerr --- CHANGELOG.md | 1 + examples/cursor_grab.rs | 2 +- src/event.rs | 26 ++-- src/platform_impl/linux/wayland/keyboard.rs | 23 +++- .../linux/x11/event_processor.rs | 116 +++++++++++------- src/platform_impl/linux/x11/mod.rs | 1 + src/platform_impl/macos/view.rs | 42 +++++-- src/platform_impl/macos/window_delegate.rs | 26 +++- src/platform_impl/windows/event_loop.rs | 111 +++++++++-------- src/platform_impl/windows/window_state.rs | 3 + 10 files changed, 229 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa1d6bfb80..6bd417c95e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Fix `Event::to_static` returning `None` for user events. - On Wayland, Hide CSD for fullscreen windows. - On Windows, ignore spurious mouse move messages. +- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`. # 0.21.0 (2020-02-04) diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 5ed193ecbe..317577e3c5 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -38,6 +38,7 @@ fn main() { _ => (), } } + WindowEvent::ModifiersChanged(m) => modifiers = m, _ => (), }, Event::DeviceEvent { event, .. } => match event { @@ -46,7 +47,6 @@ fn main() { ElementState::Pressed => println!("mouse button {} pressed", button), ElementState::Released => println!("mouse button {} released", button), }, - DeviceEvent::ModifiersChanged(m) => modifiers = m, _ => (), }, _ => (), diff --git a/src/event.rs b/src/event.rs index 494a4a1d47..e7e7be9e31 100644 --- a/src/event.rs +++ b/src/event.rs @@ -235,6 +235,13 @@ pub enum WindowEvent<'a> { is_synthetic: bool, }, + /// The keyboard modifiers have changed. + /// + /// Platform-specific behavior: + /// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an + /// issue, and it should get fixed - but it's the current state of the API. + ModifiersChanged(ModifiersState), + /// The cursor has moved on the window. CursorMoved { device_id: DeviceId, @@ -243,7 +250,7 @@ pub enum WindowEvent<'a> { /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. position: PhysicalPosition, - #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -258,7 +265,7 @@ pub enum WindowEvent<'a> { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, - #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -267,7 +274,7 @@ pub enum WindowEvent<'a> { device_id: DeviceId, state: ElementState, button: MouseButton, - #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -341,6 +348,7 @@ impl<'a> WindowEvent<'a> { input, is_synthetic, }), + ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)), #[allow(deprecated)] CursorMoved { device_id, @@ -464,16 +472,6 @@ pub enum DeviceEvent { Key(KeyboardInput), - /// The keyboard modifiers have changed. - /// - /// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from - /// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere. - /// - /// Platform-specific behavior: - /// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an - /// issue, and it should get fixed - but it's the current state of the API. - ModifiersChanged(ModifiersState), - Text { codepoint: char, }, @@ -502,7 +500,7 @@ pub struct KeyboardInput { /// /// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from /// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere. - #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] pub modifiers: ModifiersState, } diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index ed7aa20a15..8f911888f5 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -8,9 +8,7 @@ use smithay_client_toolkit::{ reexports::client::protocol::{wl_keyboard, wl_seat}, }; -use crate::event::{ - DeviceEvent, ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent, -}; +use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}; pub fn init_keyboard( seat: &wl_seat::WlSeat, @@ -33,9 +31,24 @@ pub fn init_keyboard( let wid = make_wid(&surface); my_sink.send_window_event(WindowEvent::Focused(true), wid); *target.lock().unwrap() = Some(wid); + + let modifiers = *modifiers_tracker.lock().unwrap(); + + if !modifiers.is_empty() { + my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid); + } } KbEvent::Leave { surface, .. } => { let wid = make_wid(&surface); + let modifiers = *modifiers_tracker.lock().unwrap(); + + if !modifiers.is_empty() { + my_sink.send_window_event( + WindowEvent::ModifiersChanged(ModifiersState::empty()), + wid, + ); + } + my_sink.send_window_event(WindowEvent::Focused(false), wid); *target.lock().unwrap() = None; } @@ -88,7 +101,9 @@ pub fn init_keyboard( *modifiers_tracker.lock().unwrap() = modifiers; - my_sink.send_device_event(DeviceEvent::ModifiersChanged(modifiers), DeviceId); + if let Some(wid) = *target.lock().unwrap() { + my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid); + } } } }, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 424bb89c35..976c17b62a 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -32,6 +32,8 @@ pub(super) struct EventProcessor { // Number of touch events currently in progress pub(super) num_touch: u32, pub(super) first_touch: Option, + // Currently focused window belonging to this process + pub(super) active_window: Option, } impl EventProcessor { @@ -136,11 +138,12 @@ impl EventProcessor { if let Some(modifiers) = self.device_mod_state.update_state(&state, modifier) { - let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); - callback(Event::DeviceEvent { - device_id, - event: DeviceEvent::ModifiersChanged(modifiers), - }); + if let Some(window_id) = self.active_window { + callback(Event::WindowEvent { + window_id: mkwid(window_id), + event: WindowEvent::ModifiersChanged(modifiers), + }); + } } } } @@ -872,44 +875,58 @@ impl EventProcessor { ffi::XI_FocusIn => { let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; - let window_id = mkwid(xev.event); - wt.ime .borrow_mut() .focus(xev.event) .expect("Failed to focus input context"); - callback(Event::WindowEvent { - window_id, - event: Focused(true), - }); - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); + self.device_mod_state.update_state(&modifiers, None); - // The deviceid for this event is for a keyboard instead of a pointer, - // so we have to do a little extra work. - let pointer_id = self - .devices - .borrow() - .get(&DeviceId(xev.deviceid)) - .map(|device| device.attachment) - .unwrap_or(2); + if self.active_window != Some(xev.event) { + self.active_window = Some(xev.event); - let position = PhysicalPosition::new(xev.event_x, xev.event_y); + let window_id = mkwid(xev.event); + let position = PhysicalPosition::new(xev.event_x, xev.event_y); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id: mkdid(pointer_id), - position, - modifiers, - }, - }); + callback(Event::WindowEvent { + window_id, + event: Focused(true), + }); + + if !modifiers.is_empty() { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(modifiers), + }); + } - // Issue key press events for all pressed keys - self.handle_pressed_keys(window_id, ElementState::Pressed, &mut callback); + // The deviceid for this event is for a keyboard instead of a pointer, + // so we have to do a little extra work. + let pointer_id = self + .devices + .borrow() + .get(&DeviceId(xev.deviceid)) + .map(|device| device.attachment) + .unwrap_or(2); + + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id: mkdid(pointer_id), + position, + modifiers, + }, + }); + + // Issue key press events for all pressed keys + self.handle_pressed_keys( + window_id, + ElementState::Pressed, + &mut callback, + ); + } } ffi::XI_FocusOut => { let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; @@ -921,15 +938,26 @@ impl EventProcessor { .unfocus(xev.event) .expect("Failed to unfocus input context"); - let window_id = mkwid(xev.event); + if self.active_window.take() == Some(xev.event) { + let window_id = mkwid(xev.event); - // Issue key release events for all pressed keys - self.handle_pressed_keys(window_id, ElementState::Released, &mut callback); + // Issue key release events for all pressed keys + self.handle_pressed_keys( + window_id, + ElementState::Released, + &mut callback, + ); - callback(Event::WindowEvent { - window_id, - event: Focused(false), - }) + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(ModifiersState::empty()), + }); + + callback(Event::WindowEvent { + window_id, + event: Focused(false), + }) + } } ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { @@ -1084,10 +1112,12 @@ impl EventProcessor { let new_modifiers = self.device_mod_state.modifiers(); if modifiers != new_modifiers { - callback(Event::DeviceEvent { - device_id, - event: DeviceEvent::ModifiersChanged(new_modifiers), - }); + if let Some(window_id) = self.active_window { + callback(Event::WindowEvent { + window_id: mkwid(window_id), + event: WindowEvent::ModifiersChanged(new_modifiers), + }); + } } } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index f58b4207e3..f9e0ea569b 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -225,6 +225,7 @@ impl EventLoop { device_mod_state: Default::default(), num_touch: 0, first_touch: None, + active_window: None, }; // Register for device hotplug events diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 741ecb4819..2307d3e849 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -50,13 +50,13 @@ impl Default for CursorState { } } -struct ViewState { +pub(super) struct ViewState { ns_window: id, pub cursor_state: Arc>, ime_spot: Option<(f64, f64)>, raw_characters: Option, is_key_down: bool, - modifiers: ModifiersState, + pub(super) modifiers: ModifiersState, tracking_rect: Option, } @@ -618,6 +618,19 @@ fn retrieve_keycode(event: id) -> Option { }) } +// Update `state.modifiers` if `event` has something different +fn update_potentially_stale_modifiers(state: &mut ViewState, event: id) { + let event_modifiers = event_mods(event); + if state.modifiers != event_modifiers { + state.modifiers = event_modifiers; + + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::ModifiersChanged(state.modifiers), + })); + } +} + extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { trace!("Triggered `keyDown`"); unsafe { @@ -633,6 +646,8 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { let is_repeat = msg_send![event, isARepeat]; + update_potentially_stale_modifiers(state, event); + #[allow(deprecated)] let window_event = Event::WindowEvent { window_id, @@ -686,6 +701,8 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) { let scancode = get_scancode(event) as u32; let virtual_keycode = retrieve_keycode(event); + update_potentially_stale_modifiers(state, event); + #[allow(deprecated)] let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), @@ -750,16 +767,18 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) { events.push_back(window_event); } + let window_id = WindowId(get_window_id(state.ns_window)); + for event in events { AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), + window_id, event, })); } - AppState::queue_event(EventWrapper::StaticEvent(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::ModifiersChanged(state.modifiers), + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(state.modifiers), })); } trace!("Completed `flagsChanged`"); @@ -801,6 +820,8 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { let event: id = msg_send![NSApp(), currentEvent]; + update_potentially_stale_modifiers(state, event); + #[allow(deprecated)] let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), @@ -826,6 +847,8 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); + update_potentially_stale_modifiers(state, event); + let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::MouseInput { @@ -889,6 +912,8 @@ fn mouse_motion(this: &Object, event: id) { let y = view_rect.size.height as f64 - view_point.y as f64; let logical_position = LogicalPosition::new(x, y); + update_potentially_stale_modifiers(state, event); + let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::CursorMoved { @@ -918,7 +943,7 @@ extern "C" fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) { mouse_motion(this, event); } -extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) { +extern "C" fn mouse_entered(this: &Object, _sel: Sel, _event: id) { trace!("Triggered `mouseEntered`"); unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); @@ -932,7 +957,6 @@ extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) { }; AppState::queue_event(EventWrapper::StaticEvent(enter_event)); - mouse_motion(this, event); } trace!("Completed `mouseEntered`"); } @@ -982,6 +1006,8 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); + update_potentially_stale_modifiers(state, event); + let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::MouseWheel { diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index f04d7c73eb..a89e99fcaf 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -16,11 +16,12 @@ use objc::{ use crate::{ dpi::LogicalSize, - event::{Event, WindowEvent}, + event::{Event, ModifiersState, WindowEvent}, platform_impl::platform::{ app_state::AppState, event::{EventProxy, EventWrapper}, util::{self, IdRef}, + view::ViewState, window::{get_window_id, UnownedWindow}, }, window::{Fullscreen, WindowId}, @@ -319,6 +320,29 @@ extern "C" fn window_did_become_key(this: &Object, _: Sel, _: id) { extern "C" fn window_did_resign_key(this: &Object, _: Sel, _: id) { trace!("Triggered `windowDidResignKey:`"); with_state(this, |state| { + // It happens rather often, e.g. when the user is Cmd+Tabbing, that the + // NSWindowDelegate will receive a didResignKey event despite no event + // being received when the modifiers are released. This is because + // flagsChanged events are received by the NSView instead of the + // NSWindowDelegate, and as a result a tracked modifiers state can quite + // easily fall out of synchrony with reality. This requires us to emit + // a synthetic ModifiersChanged event when we lose focus. + // + // Here we (very unsafely) acquire the winitState (a ViewState) from the + // Object referenced by state.ns_view (an IdRef, which is dereferenced + // to an id) + let view_state: &mut ViewState = unsafe { + let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref"); + let state_ptr: *mut c_void = *ns_view.get_ivar("winitState"); + &mut *(state_ptr as *mut ViewState) + }; + + // Both update the state and emit a ModifiersChanged event. + if !view_state.modifiers.is_empty() { + view_state.modifiers = ModifiersState::empty(); + state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers)); + } + state.emit_event(WindowEvent::Focused(false)); }); trace!("Completed `windowDidResignKey:`"); diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 38181089e0..a443a6ce7e 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -50,9 +50,7 @@ use crate::{ dark_mode::try_dark_mode, dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, drop_handler::FileDropHandler, - event::{ - self, handle_extended_keys, process_key_params, vkey_to_winit_vkey, ModifiersStateSide, - }, + event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, monitor, raw_input, util, window_state::{CursorFlags, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, @@ -108,7 +106,6 @@ impl SubclassInput { struct ThreadMsgTargetSubclassInput { event_loop_runner: EventLoopRunnerShared, user_event_receiver: Receiver, - modifiers_state: ModifiersStateSide, } impl ThreadMsgTargetSubclassInput { @@ -553,7 +550,6 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> let subclass_input = ThreadMsgTargetSubclassInput { event_loop_runner, user_event_receiver: rx, - modifiers_state: ModifiersStateSide::default(), }; let input_ptr = Box::into_raw(Box::new(subclass_input)); let subclass_result = commctrl::SetWindowSubclass( @@ -606,6 +602,24 @@ fn normalize_pointer_pressure(pressure: u32) -> Option { } } +/// Emit a `ModifiersChanged` event whenever modifiers have changed. +fn update_modifiers(window: HWND, subclass_input: &SubclassInput) { + use crate::event::WindowEvent::ModifiersChanged; + + let modifiers = event::get_key_mods(); + let mut window_state = subclass_input.window_state.lock(); + if window_state.modifiers_state != modifiers { + window_state.modifiers_state = modifiers; + + unsafe { + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ModifiersChanged(modifiers), + }); + } + } +} + /// Any window whose callback is configured to this function will have its events propagated /// through the events loop of the thread the window was created in. // @@ -866,6 +880,8 @@ unsafe extern "system" fn public_window_callback( w.mouse.last_position = Some(position); } if cursor_moved { + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { @@ -905,6 +921,8 @@ unsafe extern "system" fn public_window_callback( let value = value as i32; let value = value as f32 / winuser::WHEEL_DELTA as f32; + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { @@ -925,6 +943,8 @@ unsafe extern "system" fn public_window_callback( let value = value as i32; let value = value as f32 / winuser::WHEEL_DELTA as f32; + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { @@ -944,6 +964,8 @@ unsafe extern "system" fn public_window_callback( commctrl::DefSubclassProc(window, msg, wparam, lparam) } else { if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { + update_modifiers(window, subclass_input); + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -974,6 +996,8 @@ unsafe extern "system" fn public_window_callback( winuser::WM_KEYUP | winuser::WM_SYSKEYUP => { use crate::event::ElementState::Released; if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { + update_modifiers(window, subclass_input); + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -997,6 +1021,8 @@ unsafe extern "system" fn public_window_callback( capture_mouse(window, &mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1016,6 +1042,8 @@ unsafe extern "system" fn public_window_callback( release_mouse(&mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1035,6 +1063,8 @@ unsafe extern "system" fn public_window_callback( capture_mouse(window, &mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1054,6 +1084,8 @@ unsafe extern "system" fn public_window_callback( release_mouse(&mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1073,6 +1105,8 @@ unsafe extern "system" fn public_window_callback( capture_mouse(window, &mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1092,6 +1126,8 @@ unsafe extern "system" fn public_window_callback( release_mouse(&mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1112,6 +1148,8 @@ unsafe extern "system" fn public_window_callback( capture_mouse(window, &mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1132,6 +1170,8 @@ unsafe extern "system" fn public_window_callback( release_mouse(&mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1340,6 +1380,8 @@ unsafe extern "system" fn public_window_callback( winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC); let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + update_modifiers(window, subclass_input); + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1365,7 +1407,11 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_KILLFOCUS => { - use crate::event::{ElementState::Released, WindowEvent::Focused}; + use crate::event::{ + ElementState::Released, + ModifiersState, + WindowEvent::{Focused, ModifiersChanged}, + }; for windows_keycode in event::get_pressed_keys() { let scancode = winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC); @@ -1387,6 +1433,12 @@ unsafe extern "system" fn public_window_callback( }) } + subclass_input.window_state.lock().modifiers_state = ModifiersState::empty(); + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ModifiersChanged(ModifiersState::empty()), + }); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Focused(false), @@ -1795,10 +1847,9 @@ unsafe extern "system" fn thread_event_target_callback( winuser::WM_INPUT => { use crate::event::{ - DeviceEvent::{Button, Key, ModifiersChanged, Motion, MouseMotion, MouseWheel}, + DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, ElementState::{Pressed, Released}, MouseScrollDelta::LineDelta, - VirtualKeyCode, }; if let Some(data) = raw_input::get_raw_input_data(lparam as _) { @@ -1877,48 +1928,6 @@ unsafe extern "system" fn thread_event_target_callback( { let virtual_keycode = vkey_to_winit_vkey(vkey); - // If we ever change the DeviceEvent API to only emit events when a - // window is focused, we'll need to emit synthetic `ModifiersChanged` - // events when Winit windows lose focus so that these don't drift out - // of sync with the actual modifier state. - let old_modifiers_state = - subclass_input.modifiers_state.filter_out_altgr().into(); - match virtual_keycode { - Some(VirtualKeyCode::LShift) => subclass_input - .modifiers_state - .set(ModifiersStateSide::LSHIFT, pressed), - Some(VirtualKeyCode::RShift) => subclass_input - .modifiers_state - .set(ModifiersStateSide::RSHIFT, pressed), - Some(VirtualKeyCode::LControl) => subclass_input - .modifiers_state - .set(ModifiersStateSide::LCTRL, pressed), - Some(VirtualKeyCode::RControl) => subclass_input - .modifiers_state - .set(ModifiersStateSide::RCTRL, pressed), - Some(VirtualKeyCode::LAlt) => subclass_input - .modifiers_state - .set(ModifiersStateSide::LALT, pressed), - Some(VirtualKeyCode::RAlt) => subclass_input - .modifiers_state - .set(ModifiersStateSide::RALT, pressed), - Some(VirtualKeyCode::LWin) => subclass_input - .modifiers_state - .set(ModifiersStateSide::LLOGO, pressed), - Some(VirtualKeyCode::RWin) => subclass_input - .modifiers_state - .set(ModifiersStateSide::RLOGO, pressed), - _ => (), - } - let new_modifiers_state = - subclass_input.modifiers_state.filter_out_altgr().into(); - if new_modifiers_state != old_modifiers_state { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: ModifiersChanged(new_modifiers_state), - }); - } - #[allow(deprecated)] subclass_input.send_event(Event::DeviceEvent { device_id, @@ -1926,7 +1935,7 @@ unsafe extern "system" fn thread_event_target_callback( scancode, state, virtual_keycode, - modifiers: new_modifiers_state, + modifiers: event::get_key_mods(), }), }); } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 76485f46a4..4be0c796aa 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,5 +1,6 @@ use crate::{ dpi::{PhysicalPosition, Size}, + event::ModifiersState, platform_impl::platform::{event_loop, icon::WinIcon, util}, window::{CursorIcon, Fullscreen, WindowAttributes}, }; @@ -28,6 +29,7 @@ pub struct WindowState { pub saved_window: Option, pub scale_factor: f64, + pub modifiers_state: ModifiersState, pub fullscreen: Option, /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple /// times in `MainEventsCleared`. @@ -119,6 +121,7 @@ impl WindowState { saved_window: None, scale_factor, + modifiers_state: ModifiersState::default(), fullscreen: None, queued_out_of_band_redraw: false, is_dark_mode, From cbb60d29a21401461e23aa86d71c733076046476 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 7 Mar 2020 18:56:33 +0000 Subject: [PATCH 112/239] Remove assertions from Windows dark mode code (#1459) * Remove assertions from Windows dark mode code In general, winit should never assert on anything unless it means that it is impossible to continue the execution of the program. There are several assertions in the Windows dark mode code where this is not the case. Based on surface level inspection, all existing assertions could be easily replaced with just simple conditional checks, allowing the execution of the program to proceed with sane default values. Fixes #1458. * Add changelog entry * Format code * Pass dark mode by mutable reference * Format code * Return bool instead of mutable reference * Fix dark mode success reply Co-Authored-By: daxpedda * Fix dark mode success reply * Replace magic integers with constants Co-authored-by: daxpedda --- CHANGELOG.md | 1 + src/platform_impl/windows/dark_mode.rs | 30 +++++++++++--------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bd417c95e..75d927c6e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - On Wayland, Hide CSD for fullscreen windows. - On Windows, ignore spurious mouse move messages. - **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`. +- On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode # 0.21.0 (2020-02-04) diff --git a/src/platform_impl/windows/dark_mode.rs b/src/platform_impl/windows/dark_mode.rs index c50c4f21af..3c7c16eebf 100644 --- a/src/platform_impl/windows/dark_mode.rs +++ b/src/platform_impl/windows/dark_mode.rs @@ -6,9 +6,10 @@ use std::os::windows::ffi::OsStrExt; use winapi::{ shared::{ basetsd::SIZE_T, - minwindef::{BOOL, DWORD, UINT, ULONG, WORD}, + minwindef::{BOOL, DWORD, FALSE, UINT, ULONG, WORD}, ntdef::{LPSTR, NTSTATUS, NT_SUCCESS, PVOID, WCHAR}, windef::HWND, + winerror::S_OK, }, um::{libloaderapi, uxtheme, winuser}, }; @@ -44,9 +45,8 @@ lazy_static! { }; let status = (rtl_get_version)(&mut vi as _); - assert!(NT_SUCCESS(status)); - if vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 { + if NT_SUCCESS(status) && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 { Some(vi.dwBuildNumber) } else { None @@ -82,22 +82,15 @@ pub fn try_dark_mode(hwnd: HWND) -> bool { LIGHT_THEME_NAME.as_ptr() }; - unsafe { - assert_eq!( - 0, - uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) - ); - - set_dark_mode_for_window(hwnd, is_dark_mode) - } + let status = unsafe { uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) }; - is_dark_mode + status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) } else { false } } -fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) { +fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { // Uses Windows undocumented API SetWindowCompositionAttribute, // as seen in win32-darkmode example linked at top of file. @@ -132,11 +125,12 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) { cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _, }; - assert_eq!( - 1, - set_window_composition_attribute(hwnd, &mut data as *mut _) - ); + let status = set_window_composition_attribute(hwnd, &mut data as *mut _); + + status != FALSE } + } else { + false } } @@ -205,7 +199,7 @@ fn is_high_contrast() -> bool { ) }; - (ok > 0) && ((HCF_HIGHCONTRASTON & hc.dwFlags) == 1) + ok != FALSE && (HCF_HIGHCONTRASTON & hc.dwFlags) == 1 } fn widestring(src: &'static str) -> Vec { From 2f27f64cdb69978151e6b8e941658b7e742b1135 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sat, 7 Mar 2020 20:04:24 +0100 Subject: [PATCH 113/239] On Windows, fix request_redraw() related panics (#1461) * On Windows, fix request_redraw() related panics These panics were introduced by https://github.com/rust-windowing/winit/commit/6a330a2894873d29fbbfdeebfc1a215577213996 Fixes https://github.com/rust-windowing/winit/issues/1391 Fixes https://github.com/rust-windowing/winit/issues/1400 Fixes https://github.com/rust-windowing/winit/issues/1466 Probably fixes other related issues See https://github.com/rust-windowing/winit/issues/1429 * On Windows, replace all calls to UpdateWindow by calls to InvalidateRgn This avoids directly sending a WM_PAINT message, which might cause buffering of RedrawRequested events. We don't want to buffer RedrawRequested events because: - we wan't to handle RedrawRequested during processing of WM_PAINT messages - state transitionning is broken when handling buffered RedrawRequested events Fixes https://github.com/rust-windowing/winit/issues/1469 * On Windows, panic if we are trying to buffer a RedrawRequested event * On Windows, move modal loop jumpstart to set_modal_loop() method This fixes a panic. Note that the WM_PAINT event is now sent to the modal_redraw_method which is more correct and avoids an unecessary redraw of the window. Relates to but does does not fix https://github.com/rust-windowing/winit/issues/1484 * On Window, filter by paint messages when draining paint messages This seems to prevent PeekMessage from dispatching unrelated sent messages * Change recently added panic/assert calls with warn calls This makes the code less panicky... And actually, winit's Windoww callbacks should not panic because the panic will unwind into Windows code. It is currently undefined behavior to unwind from Rust code into foreign code. See https://doc.rust-lang.org/std/panic/fn.catch_unwind.html * add comments to clarify WM_PAINT handling in non modal loop * made redraw_events_cleared more explicit and more comments --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 154 +++++------ .../windows/event_loop/runner.rs | 258 ++++++------------ src/platform_impl/windows/util.rs | 2 +- src/platform_impl/windows/window.rs | 6 +- src/platform_impl/windows/window_state.rs | 6 +- 6 files changed, 154 insertions(+), 273 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75d927c6e8..e86e6d4433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On Windows, fix minor timing issue in wait_until_time_or_msg +- On Windows, rework handling of request_redraw() to address panics. - On macOS, fix `set_simple_screen` to remember frame excluding title bar. - On Wayland, fix coordinates in touch events when scale factor isn't 1. - On Wayland, fix color from `close_button_icon_color` not applying. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index a443a6ce7e..26a2d321f2 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -220,67 +220,66 @@ impl EventLoop { runner.new_events(); loop { if !unread_message_exists { - if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) { + if 0 == winuser::PeekMessageW( + &mut msg, + ptr::null_mut(), + 0, + 0, + winuser::PM_REMOVE, + ) { break; } } - if msg.message == winuser::WM_PAINT { - unread_message_exists = true; - break; - } winuser::TranslateMessage(&mut msg); winuser::DispatchMessageW(&mut msg); unread_message_exists = false; + + if msg.message == winuser::WM_PAINT { + // An "external" redraw was requested. + // Note that the WM_PAINT has been dispatched and + // has caused the event loop to emit the MainEventsCleared event. + // See EventLoopRunner::process_event(). + // The call to main_events_cleared() below will do nothing. + break; + } } + // Make sure we emit the MainEventsCleared event if no WM_PAINT message was received. runner.main_events_cleared(); + // Drain eventual WM_PAINT messages sent if user called request_redraw() + // during handling of MainEventsCleared. loop { - if !unread_message_exists { - if 0 == winuser::PeekMessageW( - &mut msg, - ptr::null_mut(), - winuser::WM_PAINT, - winuser::WM_PAINT, - 1, - ) { - break; - } + if 0 == winuser::PeekMessageW( + &mut msg, + ptr::null_mut(), + winuser::WM_PAINT, + winuser::WM_PAINT, + winuser::PM_QS_PAINT | winuser::PM_REMOVE, + ) { + break; } winuser::TranslateMessage(&mut msg); winuser::DispatchMessageW(&mut msg); - - unread_message_exists = false; - } - if runner.redraw_events_cleared().events_buffered() { - if runner.control_flow() == ControlFlow::Exit { - break 'main; - } - continue; } - - if !unread_message_exists { - let control_flow = runner.control_flow(); - match control_flow { - ControlFlow::Exit => break 'main, - ControlFlow::Wait => { - if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { - break 'main; - } - unread_message_exists = true; - } - ControlFlow::WaitUntil(resume_time) => { - wait_until_time_or_msg(resume_time); + runner.redraw_events_cleared(); + match runner.control_flow() { + ControlFlow::Exit => break 'main, + ControlFlow::Wait => { + if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { + break 'main; } - ControlFlow::Poll => (), + unread_message_exists = true; } + ControlFlow::WaitUntil(resume_time) => { + wait_until_time_or_msg(resume_time); + } + ControlFlow::Poll => (), } } } - unsafe { - runner.call_event_handler(Event::LoopDestroyed); - } + runner.destroy_loop(); runner.destroy_runner(); } @@ -652,13 +651,6 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_NCLBUTTONDOWN => { - // jumpstart the modal loop - winuser::RedrawWindow( - window, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); if wparam == winuser::HTCAPTION as _ { winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0); } @@ -688,7 +680,6 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_PAINT => { - subclass_input.event_loop_runner.main_events_cleared(); subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); commctrl::DefSubclassProc(window, msg, wparam, lparam) } @@ -1780,50 +1771,39 @@ unsafe extern "system" fn thread_event_target_callback( }; let in_modal_loop = subclass_input.event_loop_runner.in_modal_loop(); if in_modal_loop { + let runner = &subclass_input.event_loop_runner; + runner.main_events_cleared(); + // Drain eventual WM_PAINT messages sent if user called request_redraw() + // during handling of MainEventsCleared. let mut msg = mem::zeroed(); - if 0 == winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { - if msg.message != 0 && msg.message != winuser::WM_PAINT { - queue_call_again(); - return 0; + loop { + if 0 == winuser::PeekMessageW( + &mut msg, + ptr::null_mut(), + winuser::WM_PAINT, + winuser::WM_PAINT, + winuser::PM_QS_PAINT | winuser::PM_REMOVE, + ) { + break; } - subclass_input.event_loop_runner.main_events_cleared(); - loop { - if 0 == winuser::PeekMessageW( - &mut msg, - ptr::null_mut(), - winuser::WM_PAINT, - winuser::WM_PAINT, - 1, - ) { - break; - } - - if msg.hwnd != window { - winuser::TranslateMessage(&mut msg); - winuser::DispatchMessageW(&mut msg); - } + if msg.hwnd != window { + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); } } - - // we don't borrow until here because TODO SAFETY - let runner = &subclass_input.event_loop_runner; - if runner.redraw_events_cleared().events_buffered() { - queue_call_again(); - runner.new_events(); - } else { - match runner.control_flow() { - // Waiting is handled by the modal loop. - ControlFlow::Exit | ControlFlow::Wait => runner.new_events(), - ControlFlow::WaitUntil(resume_time) => { - wait_until_time_or_msg(resume_time); - runner.new_events(); - queue_call_again(); - } - ControlFlow::Poll => { - runner.new_events(); - queue_call_again(); - } + runner.redraw_events_cleared(); + match runner.control_flow() { + // Waiting is handled by the modal loop. + ControlFlow::Exit | ControlFlow::Wait => runner.new_events(), + ControlFlow::WaitUntil(resume_time) => { + wait_until_time_or_msg(resume_time); + runner.new_events(); + queue_call_again(); + } + ControlFlow::Poll => { + runner.new_events(); + queue_call_again(); } } } diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index 44a044d43a..e5c062b035 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -6,7 +6,7 @@ use crate::{ dpi::PhysicalSize, event::{Event, StartCause, WindowEvent}, event_loop::ControlFlow, - platform_impl::platform::{event_loop::EventLoop, util}, + platform_impl::platform::event_loop::{util, EventLoop}, window::WindowId, }; @@ -14,8 +14,8 @@ pub(crate) type EventLoopRunnerShared = Rc>; pub(crate) struct ELRShared { runner: RefCell>>, buffer: RefCell>>, - redraw_buffer: Rc>>, } + struct EventLoopRunner { control_flow: ControlFlow, runner_state: RunnerState, @@ -23,8 +23,8 @@ struct EventLoopRunner { in_modal_loop: bool, event_handler: Box, &mut ControlFlow)>, panic_error: Option, - redraw_buffer: Rc>>, } + pub type PanicError = Box; pub enum BufferedEvent { @@ -32,22 +32,6 @@ pub enum BufferedEvent { ScaleFactorChanged(WindowId, f64, PhysicalSize), } -#[must_use] -#[derive(Debug, Clone, Copy)] -pub enum AreEventsBuffered { - EventsBuffered, - ReadyToSleep, -} - -impl AreEventsBuffered { - pub fn events_buffered(&self) -> bool { - match self { - Self::EventsBuffered => true, - Self::ReadyToSleep => false, - } - } -} - impl BufferedEvent { pub fn from_event(event: Event<'_, T>) -> BufferedEvent { match event { @@ -89,7 +73,6 @@ impl ELRShared { ELRShared { runner: RefCell::new(None), buffer: RefCell::new(VecDeque::new()), - redraw_buffer: Default::default(), } } @@ -97,16 +80,11 @@ impl ELRShared { where F: FnMut(Event<'_, T>, &mut ControlFlow), { - let mut runner = EventLoopRunner::new(event_loop, self.redraw_buffer.clone(), f); + let mut runner = EventLoopRunner::new(event_loop, f); { let mut runner_ref = self.runner.borrow_mut(); - loop { - let event = self.buffer.borrow_mut().pop_front(); - match event { - Some(e) => e.dispatch_event(|e| runner.process_event(e)), - None => break, - } - } + // Dispatch any events that were buffered during the creation of the window + self.dispatch_buffered_events(&mut runner); *runner_ref = Some(runner); } } @@ -119,80 +97,46 @@ impl ELRShared { let mut runner_ref = self.runner.borrow_mut(); if let Some(ref mut runner) = *runner_ref { runner.new_events(); - loop { - let buffered_event_opt = self.buffer.borrow_mut().pop_front(); - match buffered_event_opt { - Some(e) => e.dispatch_event(|e| runner.process_event(e)), - None => break, - } - } + // Dispatch any events that were buffered during the call `new_events` + self.dispatch_buffered_events(runner); } } - pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) { - let handling_redraw = self - .runner - .borrow() - .as_ref() - .map(|r| RunnerState::HandlingRedraw == r.runner_state) - .unwrap_or(false); - let mut send = None; - if handling_redraw { + pub(crate) fn send_event(&self, event: Event<'_, T>) { + if let Err(event) = self.send_event_unbuffered(event) { + // If the runner is already borrowed, we're in the middle of an event loop invocation. + // Add the event to a buffer to be processed later. if let Event::RedrawRequested(_) = event { - send = Some(event); - } else { - self.buffer_event(event); - } - } else { - send = Some(event); - } - if let Some(event) = send { - if let Err(event) = self.send_event_unbuffered(event) { - // If the runner is already borrowed, we're in the middle of an event loop invocation. Add - // the event to a buffer to be processed later. - self.buffer_event(event); + panic!("buffering RedrawRequested event"); } + self.buffer + .borrow_mut() + .push_back(BufferedEvent::from_event(event)); } } - unsafe fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> { + fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> { if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { if let Some(ref mut runner) = *runner_ref { runner.process_event(event); - - let handling_redraw = if let RunnerState::HandlingRedraw = runner.runner_state { - true - } else { - false - }; - - if !handling_redraw { - // Dispatch any events that were buffered during the call to `process_event`. - loop { - // We do this instead of using a `while let` loop because if we use a `while let` - // loop the reference returned `borrow_mut()` doesn't get dropped until the end - // of the loop's body and attempts to add events to the event buffer while in - // `process_event` will fail. - let buffered_event_opt = self.buffer.borrow_mut().pop_front(); - match buffered_event_opt { - Some(e) => e.dispatch_event(|e| runner.process_event(e)), - None => break, - } - } - } - + // Dispatch any events that were buffered during the call to `process_event`. + self.dispatch_buffered_events(runner); return Ok(()); } } - Err(event) } - pub(crate) unsafe fn call_event_handler(&self, event: Event<'static, T>) { - if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { - if let Some(ref mut runner) = *runner_ref { - runner.call_event_handler(event); - return; + fn dispatch_buffered_events(&self, runner: &mut EventLoopRunner) { + // We do this instead of using a `while let` loop because if we use a `while let` + // loop the reference returned `borrow_mut()` doesn't get dropped until the end + // of the loop's body and attempts to add events to the event buffer while in + // `process_event` will fail. + loop { + let buffered_event_opt = self.buffer.borrow_mut().pop_front(); + match buffered_event_opt { + Some(e) => e.dispatch_event(|e| runner.process_event(e)), + None => break, } } } @@ -201,17 +145,27 @@ impl ELRShared { let mut runner_ref = self.runner.borrow_mut(); if let Some(ref mut runner) = *runner_ref { runner.main_events_cleared(); + if !self.buffer.borrow().is_empty() { + warn!("Buffered events while dispatching MainEventsCleared"); + } } } - pub(crate) fn redraw_events_cleared(&self) -> AreEventsBuffered { + pub(crate) fn redraw_events_cleared(&self) { let mut runner_ref = self.runner.borrow_mut(); if let Some(ref mut runner) = *runner_ref { runner.redraw_events_cleared(); + if !self.buffer.borrow().is_empty() { + warn!("Buffered events while dispatching RedrawEventsCleared"); + } } - match self.buffer.borrow().len() { - 0 => AreEventsBuffered::ReadyToSleep, - _ => AreEventsBuffered::EventsBuffered, + } + + pub(crate) fn destroy_loop(&self) { + if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { + if let Some(ref mut runner) = *runner_ref { + runner.call_event_handler(Event::LoopDestroyed); + } } } @@ -228,6 +182,17 @@ impl ELRShared { let mut runner_ref = self.runner.borrow_mut(); if let Some(ref mut runner) = *runner_ref { runner.in_modal_loop = in_modal_loop; + if in_modal_loop { + // jumpstart the modal loop + unsafe { + winuser::RedrawWindow( + runner.modal_redraw_window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } + } } } @@ -248,18 +213,6 @@ impl ELRShared { ControlFlow::Exit } } - - fn buffer_event(&self, event: Event<'_, T>) { - match event { - Event::RedrawRequested(window_id) => { - self.redraw_buffer.borrow_mut().push_back(window_id) - } - _ => self - .buffer - .borrow_mut() - .push_back(BufferedEvent::from_event(event)), - } - } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -275,15 +228,13 @@ enum RunnerState { /// The event loop is handling the OS's events and sending them to the user's callback. /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. HandlingEvents, + /// The event loop is handling the redraw events and sending them to the user's callback. + /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. HandlingRedraw, } impl EventLoopRunner { - unsafe fn new( - event_loop: &EventLoop, - redraw_buffer: Rc>>, - f: F, - ) -> EventLoopRunner + unsafe fn new(event_loop: &EventLoop, f: F) -> EventLoopRunner where F: FnMut(Event<'_, T>, &mut ControlFlow), { @@ -297,7 +248,6 @@ impl EventLoopRunner { Box, &mut ControlFlow)>, >(Box::new(f)), panic_error: None, - redraw_buffer, } } @@ -323,6 +273,8 @@ impl EventLoopRunner { } // When `NewEvents` gets sent after an idle depends on the control flow... + // Some `NewEvents` are deferred because not all Windows messages trigger an event_loop event. + // So we defer the `NewEvents` to when we actually process an event. RunnerState::Idle(wait_start) => { match self.control_flow { // If we're polling, send `NewEvents` and immediately move into event processing. @@ -411,24 +363,21 @@ impl EventLoopRunner { // that was sent after `MainEventsCleared`. ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)), } + self.runner_state = RunnerState::HandlingEvents; } match (self.runner_state, &event) { - (RunnerState::HandlingRedraw, Event::RedrawRequested(_)) => { - self.call_event_handler(event) - } - (RunnerState::New, Event::RedrawRequested(_)) - | (RunnerState::Idle(..), Event::RedrawRequested(_)) => { - self.new_events(); - self.main_events_cleared(); - self.call_event_handler(event); + (RunnerState::HandlingEvents, Event::RedrawRequested(window_id)) => { + self.call_event_handler(Event::MainEventsCleared); + self.runner_state = RunnerState::HandlingRedraw; + self.call_event_handler(Event::RedrawRequested(*window_id)); } - (_, Event::RedrawRequested(_)) => { - panic!("redraw event in non-redraw phase"); + (RunnerState::HandlingRedraw, Event::RedrawRequested(window_id)) => { + self.call_event_handler(Event::RedrawRequested(*window_id)); } (RunnerState::HandlingRedraw, _) => { - panic!( - "Non-redraw event dispatched durning redraw phase: {:?}", + warn!( + "non-redraw event in redraw phase: {:?}", event.map_nonuser_event::<()>().ok() ); } @@ -439,16 +388,6 @@ impl EventLoopRunner { } } - fn flush_redraws(&mut self) { - loop { - let redraw_window_opt = self.redraw_buffer.borrow_mut().pop_front(); - match redraw_window_opt { - Some(window_id) => self.process_event(Event::RedrawRequested(window_id)), - None => break, - } - } - } - fn main_events_cleared(&mut self) { match self.runner_state { // If we were handling events, send the MainEventsCleared message. @@ -457,7 +396,9 @@ impl EventLoopRunner { self.runner_state = RunnerState::HandlingRedraw; } - RunnerState::HandlingRedraw => (), + // We already cleared the main events, we don't have to do anything. + // This happens when process_events() processed a RedrawRequested event. + RunnerState::HandlingRedraw => {} // If we *weren't* handling events, we don't have to do anything. RunnerState::New | RunnerState::Idle(..) => (), @@ -469,6 +410,7 @@ impl EventLoopRunner { // If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared. ControlFlow::Poll => { self.call_event_handler(Event::NewEvents(StartCause::Poll)); + self.runner_state = RunnerState::HandlingEvents; self.call_event_handler(Event::MainEventsCleared); self.runner_state = RunnerState::HandlingRedraw; } @@ -482,6 +424,7 @@ impl EventLoopRunner { requested_resume: resume_time, }, )); + self.runner_state = RunnerState::HandlingEvents; self.call_event_handler(Event::MainEventsCleared); self.runner_state = RunnerState::HandlingRedraw; } @@ -496,57 +439,18 @@ impl EventLoopRunner { fn redraw_events_cleared(&mut self) { match self.runner_state { - // If we were handling events, send the MainEventsCleared message. - RunnerState::HandlingEvents => { - self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; - self.flush_redraws(); - self.call_event_handler(Event::RedrawEventsCleared); - self.runner_state = RunnerState::Idle(Instant::now()); - } - + // If we were handling redraws, send the RedrawEventsCleared message. RunnerState::HandlingRedraw => { - self.flush_redraws(); self.call_event_handler(Event::RedrawEventsCleared); self.runner_state = RunnerState::Idle(Instant::now()); } - - // If we *weren't* handling events, we don't have to do anything. - RunnerState::New | RunnerState::Idle(..) => (), - - // Some control flows require a NewEvents call even if no events were received. This - // branch handles those. - RunnerState::DeferredNewEvents(wait_start) => { - match self.control_flow { - // If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared. - ControlFlow::Poll => { - self.call_event_handler(Event::NewEvents(StartCause::Poll)); - self.call_event_handler(Event::MainEventsCleared); - self.flush_redraws(); - self.call_event_handler(Event::RedrawEventsCleared); - } - // If we had deferred a WaitUntil and the resume time has since been reached, - // send the resume notification and MainEventsCleared event. - ControlFlow::WaitUntil(resume_time) => { - if Instant::now() >= resume_time { - self.call_event_handler(Event::NewEvents( - StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - }, - )); - self.call_event_handler(Event::MainEventsCleared); - self.flush_redraws(); - self.call_event_handler(Event::RedrawEventsCleared); - } - } - // If we deferred a wait and no events were received, the user doesn't have to - // get an event. - ControlFlow::Wait | ControlFlow::Exit => (), - } - // Mark that we've entered an idle state. - self.runner_state = RunnerState::Idle(wait_start) - } + // No event was processed, we don't have to do anything. + RunnerState::DeferredNewEvents(_) => (), + // Should not happen. + _ => warn!( + "unexpected state in redraw_events_cleared: {:?}", + self.runner_state + ), } } diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index a5c60f9bf2..5faaae3e68 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -116,7 +116,7 @@ pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) { | winuser::SWP_NOMOVE | winuser::SWP_NOACTIVATE, ); - winuser::UpdateWindow(window); + winuser::InvalidateRgn(window, ptr::null_mut(), 0); } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 7feb631c5b..0f9a09c7c8 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -187,7 +187,7 @@ impl Window { | winuser::SWP_NOSIZE | winuser::SWP_NOACTIVATE, ); - winuser::UpdateWindow(self.window.0); + winuser::InvalidateRgn(self.window.0, ptr::null_mut(), 0); } } @@ -506,7 +506,7 @@ impl Window { size.1 as i32, winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER, ); - winuser::UpdateWindow(window.0); + winuser::InvalidateRgn(window.0, ptr::null_mut(), 0); } } None => { @@ -532,7 +532,7 @@ impl Window { | winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE, ); - winuser::UpdateWindow(window.0); + winuser::InvalidateRgn(window.0, ptr::null_mut(), 0); } } } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 4be0c796aa..9a5414a61b 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -31,9 +31,6 @@ pub struct WindowState { pub modifiers_state: ModifiersState, pub fullscreen: Option, - /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple - /// times in `MainEventsCleared`. - pub queued_out_of_band_redraw: bool, pub is_dark_mode: bool, pub high_surrogate: Option, window_flags: WindowFlags, @@ -123,7 +120,6 @@ impl WindowState { modifiers_state: ModifiersState::default(), fullscreen: None, - queued_out_of_band_redraw: false, is_dark_mode, high_surrogate: None, window_flags: WindowFlags::empty(), @@ -273,7 +269,7 @@ impl WindowFlags { | winuser::SWP_NOSIZE | winuser::SWP_NOACTIVATE, ); - winuser::UpdateWindow(window); + winuser::InvalidateRgn(window, ptr::null_mut(), 0); } } From 098fd5d602ee3b20372048db35aa3de432884aaa Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sat, 7 Mar 2020 19:42:21 +0000 Subject: [PATCH 114/239] Add ability to create Icons from embedded resources on Windows (#1410) * Add IconExtWindows trait * Move changelog entries to unreleased category Co-authored-by: Osspial --- CHANGELOG.md | 2 + src/icon.rs | 92 +++++++++++---- src/platform/windows.rs | 44 ++++++- src/platform_impl/android/mod.rs | 2 + src/platform_impl/ios/mod.rs | 2 + src/platform_impl/linux/mod.rs | 2 + src/platform_impl/linux/x11/util/icon.rs | 15 +-- src/platform_impl/macos/mod.rs | 2 + src/platform_impl/web/mod.rs | 2 + src/platform_impl/windows/icon.rs | 137 ++++++++++++++++------ src/platform_impl/windows/mod.rs | 6 +- src/platform_impl/windows/window.rs | 55 +++------ src/platform_impl/windows/window_state.rs | 13 +- src/window.rs | 2 +- 14 files changed, 258 insertions(+), 118 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e86e6d4433..0111b9433d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - On Wayland, Hide CSD for fullscreen windows. - On Windows, ignore spurious mouse move messages. - **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`. +- On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource +- Add `BadIcon::OsError` variant for when OS icon functionality fails - On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode # 0.21.0 (2020-02-04) diff --git a/src/icon.rs b/src/icon.rs index d512df1ada..418ea03afa 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -1,4 +1,5 @@ -use std::{error::Error, fmt, mem}; +use crate::platform_impl::PlatformIcon; +use std::{error::Error, fmt, io, mem}; #[repr(C)] #[derive(Debug)] @@ -11,7 +12,7 @@ pub(crate) struct Pixel { pub(crate) const PIXEL_SIZE: usize = mem::size_of::(); -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug)] /// An error produced when using `Icon::from_rgba` with invalid arguments. pub enum BadIcon { /// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be @@ -25,6 +26,8 @@ pub enum BadIcon { width_x_height: usize, pixel_count: usize, }, + /// Produced when underlying OS functionality failed to create the icon + OsError(io::Error), } impl fmt::Display for BadIcon { @@ -43,6 +46,7 @@ impl fmt::Display for BadIcon { "The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.", width, height, pixel_count, width_x_height, ), + BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e), } } } @@ -54,38 +58,78 @@ impl Error for BadIcon { } #[derive(Debug, Clone, PartialEq, Eq)] -/// An icon used for the window titlebar, taskbar, etc. -pub struct Icon { +pub(crate) struct RgbaIcon { pub(crate) rgba: Vec, pub(crate) width: u32, pub(crate) height: u32, } +/// For platforms which don't have window icons (e.g. web) +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct NoIcon; + +#[allow(dead_code)] // These are not used on every platform +mod constructors { + use super::*; + + impl RgbaIcon { + /// Creates an `Icon` from 32bpp RGBA data. + /// + /// The length of `rgba` must be divisible by 4, and `width * height` must equal + /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + if rgba.len() % PIXEL_SIZE != 0 { + return Err(BadIcon::ByteCountNotDivisibleBy4 { + byte_count: rgba.len(), + }); + } + let pixel_count = rgba.len() / PIXEL_SIZE; + if pixel_count != (width * height) as usize { + Err(BadIcon::DimensionsVsPixelCount { + width, + height, + width_x_height: (width * height) as usize, + pixel_count, + }) + } else { + Ok(RgbaIcon { + rgba, + width, + height, + }) + } + } + } + + impl NoIcon { + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + // Create the rgba icon anyway to validate the input + let _ = RgbaIcon::from_rgba(rgba, width, height)?; + Ok(NoIcon) + } + } +} + +/// An icon used for the window titlebar, taskbar, etc. +#[derive(Clone)] +pub struct Icon { + pub(crate) inner: PlatformIcon, +} + +impl fmt::Debug for Icon { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&self.inner, formatter) + } +} + impl Icon { /// Creates an `Icon` from 32bpp RGBA data. /// /// The length of `rgba` must be divisible by 4, and `width * height` must equal /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { - if rgba.len() % PIXEL_SIZE != 0 { - return Err(BadIcon::ByteCountNotDivisibleBy4 { - byte_count: rgba.len(), - }); - } - let pixel_count = rgba.len() / PIXEL_SIZE; - if pixel_count != (width * height) as usize { - Err(BadIcon::DimensionsVsPixelCount { - width, - height, - width_x_height: (width * height) as usize, - pixel_count, - }) - } else { - Ok(Icon { - rgba, - width, - height, - }) - } + Ok(Icon { + inner: PlatformIcon::from_rgba(rgba, width, height)?, + }) } } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 6e43734c36..a356f1e1c0 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1,16 +1,19 @@ #![cfg(target_os = "windows")] use std::os::raw::c_void; +use std::path::Path; use libc; +use winapi::shared::minwindef::WORD; use winapi::shared::windef::HWND; use crate::{ + dpi::PhysicalSize, event::DeviceId, event_loop::EventLoop, monitor::MonitorHandle, - platform_impl::EventLoop as WindowsEventLoop, - window::{Icon, Window, WindowBuilder}, + platform_impl::{EventLoop as WindowsEventLoop, WinIcon}, + window::{BadIcon, Icon, Window, WindowBuilder}, }; /// Additional methods on `EventLoop` that are specific to Windows. @@ -171,3 +174,40 @@ impl DeviceIdExtWindows for DeviceId { self.0.persistent_identifier() } } + +/// Additional methods on `Icon` that are specific to Windows. +pub trait IconExtWindows: Sized { + /// Create an icon from a file path. + /// + /// Specify `size` to load a specific icon size from the file, or `None` to load the default + /// icon size from the file. + /// + /// In cases where the specified size does not exist in the file, Windows may perform scaling + /// to get an icon of the desired size. + fn from_path>(path: P, size: Option>) + -> Result; + + /// Create an icon from a resource embedded in this executable or library. + /// + /// Specify `size` to load a specific icon size from the file, or `None` to load the default + /// icon size from the file. + /// + /// In cases where the specified size does not exist in the file, Windows may perform scaling + /// to get an icon of the desired size. + fn from_resource(ordinal: WORD, size: Option>) -> Result; +} + +impl IconExtWindows for Icon { + fn from_path>( + path: P, + size: Option>, + ) -> Result { + let win_icon = WinIcon::from_path(path, size)?; + Ok(Icon { inner: win_icon }) + } + + fn from_resource(ordinal: WORD, size: Option>) -> Result { + let win_icon = WinIcon::from_resource(ordinal, size)?; + Ok(Icon { inner: win_icon }) + } +} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index ef0639d1b7..cf1893749c 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -22,6 +22,8 @@ use crate::{ use raw_window_handle::{android::AndroidHandle, RawWindowHandle}; use CreationError::OsError; +pub(crate) use crate::icon::NoIcon as PlatformIcon; + pub type OsError = std::io::Error; pub struct EventLoop { diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index 3141dea4cc..da7b018572 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -83,6 +83,8 @@ pub use self::{ window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, }; +pub(crate) use crate::icon::NoIcon as PlatformIcon; + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId { uiscreen: ffi::id, diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 9411730324..8958ddc030 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -18,6 +18,8 @@ use crate::{ window::{CursorIcon, Fullscreen, WindowAttributes}, }; +pub(crate) use crate::icon::RgbaIcon as PlatformIcon; + pub mod wayland; pub mod x11; diff --git a/src/platform_impl/linux/x11/util/icon.rs b/src/platform_impl/linux/x11/util/icon.rs index 5334d4a9c9..eb13d48b5b 100644 --- a/src/platform_impl/linux/x11/util/icon.rs +++ b/src/platform_impl/linux/x11/util/icon.rs @@ -1,5 +1,5 @@ use super::*; -use crate::window::{Icon, Pixel, PIXEL_SIZE}; +use crate::icon::{Icon, Pixel, PIXEL_SIZE}; impl Pixel { pub fn to_packed_argb(&self) -> Cardinal { @@ -18,13 +18,14 @@ impl Pixel { impl Icon { pub(crate) fn to_cardinals(&self) -> Vec { - assert_eq!(self.rgba.len() % PIXEL_SIZE, 0); - let pixel_count = self.rgba.len() / PIXEL_SIZE; - assert_eq!(pixel_count, (self.width * self.height) as usize); + let rgba_icon = &self.inner; + assert_eq!(rgba_icon.rgba.len() % PIXEL_SIZE, 0); + let pixel_count = rgba_icon.rgba.len() / PIXEL_SIZE; + assert_eq!(pixel_count, (rgba_icon.width * rgba_icon.height) as usize); let mut data = Vec::with_capacity(pixel_count); - data.push(self.width as Cardinal); - data.push(self.height as Cardinal); - let pixels = self.rgba.as_ptr() as *const Pixel; + data.push(rgba_icon.width as Cardinal); + data.push(rgba_icon.height as Cardinal); + let pixels = rgba_icon.rgba.as_ptr() as *const Pixel; for pixel_index in 0..pixel_count { let pixel = unsafe { &*pixels.offset(pixel_index as isize) }; data.push(pixel.to_packed_argb()); diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 8b5ee6a59a..72b8e0a332 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -25,6 +25,8 @@ use crate::{ error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes, }; +pub(crate) use crate::icon::NoIcon as PlatformIcon; + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index ba022db6e7..43bbb2afff 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -44,3 +44,5 @@ pub use self::window::{ Id as WindowId, PlatformSpecificBuilderAttributes as PlatformSpecificWindowBuilderAttributes, Window, }; + +pub(crate) use crate::icon::NoIcon as PlatformIcon; diff --git a/src/platform_impl/windows/icon.rs b/src/platform_impl/windows/icon.rs index c865053dbc..1308c7467e 100644 --- a/src/platform_impl/windows/icon.rs +++ b/src/platform_impl/windows/icon.rs @@ -1,15 +1,17 @@ -use std::{io, mem, os::windows::ffi::OsStrExt, path::Path, ptr}; +use std::{fmt, io, iter::once, mem, os::windows::ffi::OsStrExt, path::Path, ptr, sync::Arc}; use winapi::{ ctypes::{c_int, wchar_t}, shared::{ - minwindef::{BYTE, LPARAM, WPARAM}, + minwindef::{BYTE, LPARAM, WORD, WPARAM}, windef::{HICON, HWND}, }, + um::libloaderapi, um::winuser, }; -use crate::icon::{Icon, Pixel, PIXEL_SIZE}; +use crate::dpi::PhysicalSize; +use crate::icon::*; impl Pixel { fn to_bgra(&mut self) { @@ -17,92 +19,149 @@ impl Pixel { } } +impl RgbaIcon { + fn into_windows_icon(self) -> Result { + let mut rgba = self.rgba; + let pixel_count = rgba.len() / PIXEL_SIZE; + let mut and_mask = Vec::with_capacity(pixel_count); + let pixels = + unsafe { std::slice::from_raw_parts_mut(rgba.as_mut_ptr() as *mut Pixel, pixel_count) }; + for pixel in pixels { + and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel + pixel.to_bgra(); + } + assert_eq!(and_mask.len(), pixel_count); + let handle = unsafe { + winuser::CreateIcon( + ptr::null_mut(), + self.width as c_int, + self.height as c_int, + 1, + (PIXEL_SIZE * 8) as BYTE, + and_mask.as_ptr() as *const BYTE, + rgba.as_ptr() as *const BYTE, + ) as HICON + }; + if !handle.is_null() { + Ok(WinIcon::from_handle(handle)) + } else { + Err(BadIcon::OsError(io::Error::last_os_error())) + } + } +} + #[derive(Debug)] pub enum IconType { Small = winuser::ICON_SMALL as isize, Big = winuser::ICON_BIG as isize, } -#[derive(Clone, Debug)] +#[derive(Debug)] +struct RaiiIcon { + handle: HICON, +} + +#[derive(Clone)] pub struct WinIcon { - pub handle: HICON, + inner: Arc, } unsafe impl Send for WinIcon {} impl WinIcon { - #[allow(dead_code)] - pub fn from_path>(path: P) -> Result { - let wide_path: Vec = path.as_ref().as_os_str().encode_wide().collect(); + pub fn as_raw_handle(&self) -> HICON { + self.inner.handle + } + + pub fn from_path>( + path: P, + size: Option>, + ) -> Result { + let wide_path: Vec = path + .as_ref() + .as_os_str() + .encode_wide() + .chain(once(0)) + .collect(); + + // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size + let (width, height) = size.map(Into::into).unwrap_or((0, 0)); + let handle = unsafe { winuser::LoadImageW( ptr::null_mut(), wide_path.as_ptr() as *const wchar_t, winuser::IMAGE_ICON, - 0, // 0 indicates that we want to use the actual width - 0, // and height - winuser::LR_LOADFROMFILE, + width as c_int, + height as c_int, + winuser::LR_DEFAULTSIZE | winuser::LR_LOADFROMFILE, ) as HICON }; if !handle.is_null() { - Ok(WinIcon { handle }) + Ok(WinIcon::from_handle(handle)) } else { - Err(io::Error::last_os_error()) + Err(BadIcon::OsError(io::Error::last_os_error())) } } - pub fn from_icon(icon: Icon) -> Result { - Self::from_rgba(icon.rgba, icon.width, icon.height) - } - - pub fn from_rgba(mut rgba: Vec, width: u32, height: u32) -> Result { - assert_eq!(rgba.len() % PIXEL_SIZE, 0); - let pixel_count = rgba.len() / PIXEL_SIZE; - assert_eq!(pixel_count, (width * height) as usize); - let mut and_mask = Vec::with_capacity(pixel_count); - let pixels = rgba.as_mut_ptr() as *mut Pixel; // how not to write idiomatic Rust - for pixel_index in 0..pixel_count { - let pixel = unsafe { &mut *pixels.offset(pixel_index as isize) }; - and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel - pixel.to_bgra(); - } - assert_eq!(and_mask.len(), pixel_count); + pub fn from_resource( + resource_id: WORD, + size: Option>, + ) -> Result { + // width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size + let (width, height) = size.map(Into::into).unwrap_or((0, 0)); let handle = unsafe { - winuser::CreateIcon( - ptr::null_mut(), + winuser::LoadImageW( + libloaderapi::GetModuleHandleW(ptr::null_mut()), + winuser::MAKEINTRESOURCEW(resource_id), + winuser::IMAGE_ICON, width as c_int, height as c_int, - 1, - (PIXEL_SIZE * 8) as BYTE, - and_mask.as_ptr() as *const BYTE, - rgba.as_ptr() as *const BYTE, + winuser::LR_DEFAULTSIZE, ) as HICON }; if !handle.is_null() { - Ok(WinIcon { handle }) + Ok(WinIcon::from_handle(handle)) } else { - Err(io::Error::last_os_error()) + Err(BadIcon::OsError(io::Error::last_os_error())) } } + pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result { + let rgba_icon = RgbaIcon::from_rgba(rgba, width, height)?; + rgba_icon.into_windows_icon() + } + pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) { unsafe { winuser::SendMessageW( hwnd, winuser::WM_SETICON, icon_type as WPARAM, - self.handle as LPARAM, + self.as_raw_handle() as LPARAM, ); } } + + fn from_handle(handle: HICON) -> Self { + Self { + inner: Arc::new(RaiiIcon { handle }), + } + } } -impl Drop for WinIcon { +impl Drop for RaiiIcon { fn drop(&mut self) { unsafe { winuser::DestroyIcon(self.handle) }; } } +impl fmt::Debug for WinIcon { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + (*self.inner).fmt(formatter) + } +} + pub fn unset_for_window(hwnd: HWND, icon_type: IconType) { unsafe { winuser::SendMessageW(hwnd, winuser::WM_SETICON, icon_type as WPARAM, 0 as LPARAM); diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 47bf7fb53f..021c903b8d 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -4,11 +4,15 @@ use winapi::{self, shared::windef::HWND}; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, + icon::WinIcon, monitor::{MonitorHandle, VideoMode}, window::Window, }; -use crate::{event::DeviceId as RootDeviceId, window::Icon}; +pub use self::icon::WinIcon as PlatformIcon; + +use crate::event::DeviceId as RootDeviceId; +use crate::icon::Icon; #[derive(Clone, Default)] pub struct PlatformSpecificWindowBuilderAttributes { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 0f9a09c7c8..3c58e1fd0b 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -32,18 +32,19 @@ use winapi::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, + icon::Icon, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ dark_mode::try_dark_mode, dpi::{dpi_to_scale_factor, hwnd_dpi}, drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, - icon::{self, IconType, WinIcon}, + icon::{self, IconType}, monitor, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, - window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, + window::{CursorIcon, Fullscreen, WindowAttributes}, }; /// The Win32 implementation of the main `Window` object. @@ -576,12 +577,11 @@ impl Window { } #[inline] - pub fn set_window_icon(&self, mut window_icon: Option) { - let window_icon = window_icon - .take() - .map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_SMALL`")); + pub fn set_window_icon(&self, window_icon: Option) { if let Some(ref window_icon) = window_icon { - window_icon.set_for_window(self.window.0, IconType::Small); + window_icon + .inner + .set_for_window(self.window.0, IconType::Small); } else { icon::unset_for_window(self.window.0, IconType::Small); } @@ -589,12 +589,11 @@ impl Window { } #[inline] - pub fn set_taskbar_icon(&self, mut taskbar_icon: Option) { - let taskbar_icon = taskbar_icon - .take() - .map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_BIG`")); + pub fn set_taskbar_icon(&self, taskbar_icon: Option) { if let Some(ref taskbar_icon) = taskbar_icon { - taskbar_icon.set_for_window(self.window.0, IconType::Big); + taskbar_icon + .inner + .set_for_window(self.window.0, IconType::Big); } else { icon::unset_for_window(self.window.0, IconType::Big); } @@ -636,7 +635,7 @@ unsafe impl Sync for WindowWrapper {} unsafe impl Send for WindowWrapper {} unsafe fn init( - mut attributes: WindowAttributes, + attributes: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, event_loop: &EventLoopWindowTarget, ) -> Result { @@ -645,25 +644,8 @@ unsafe fn init( .chain(Some(0).into_iter()) .collect::>(); - let window_icon = { - let icon = attributes.window_icon.take().map(WinIcon::from_icon); - if let Some(icon) = icon { - Some(icon.map_err(|e| os_error!(e))?) - } else { - None - } - }; - let taskbar_icon = { - let icon = attributes.window_icon.take().map(WinIcon::from_icon); - if let Some(icon) = icon { - Some(icon.map_err(|e| os_error!(e))?) - } else { - None - } - }; - // registering the window class - let class_name = register_window_class(&window_icon, &taskbar_icon); + let class_name = register_window_class(&attributes.window_icon, &pl_attribs.taskbar_icon); let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::DECORATIONS, attributes.decorations); @@ -756,8 +738,7 @@ unsafe fn init( let window_state = { let window_state = WindowState::new( &attributes, - window_icon, - taskbar_icon, + pl_attribs.taskbar_icon, scale_factor, dark_mode, ); @@ -787,8 +768,8 @@ unsafe fn init( } unsafe fn register_window_class( - window_icon: &Option, - taskbar_icon: &Option, + window_icon: &Option, + taskbar_icon: &Option, ) -> Vec { let class_name: Vec<_> = OsStr::new("Window Class") .encode_wide() @@ -797,11 +778,11 @@ unsafe fn register_window_class( let h_icon = taskbar_icon .as_ref() - .map(|icon| icon.handle) + .map(|icon| icon.inner.as_raw_handle()) .unwrap_or(ptr::null_mut()); let h_icon_small = window_icon .as_ref() - .map(|icon| icon.handle) + .map(|icon| icon.inner.as_raw_handle()) .unwrap_or(ptr::null_mut()); let class = winuser::WNDCLASSEXW { diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 9a5414a61b..11794821ad 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,7 +1,8 @@ use crate::{ dpi::{PhysicalPosition, Size}, event::ModifiersState, - platform_impl::platform::{event_loop, icon::WinIcon, util}, + icon::Icon, + platform_impl::platform::{event_loop, util}, window::{CursorIcon, Fullscreen, WindowAttributes}, }; use parking_lot::MutexGuard; @@ -15,7 +16,6 @@ use winapi::{ }; /// Contains information about states and the window that the callback is going to use. -#[derive(Clone)] pub struct WindowState { pub mouse: MouseProperties, @@ -23,8 +23,8 @@ pub struct WindowState { pub min_size: Option, pub max_size: Option, - pub window_icon: Option, - pub taskbar_icon: Option, + pub window_icon: Option, + pub taskbar_icon: Option, pub saved_window: Option, pub scale_factor: f64, @@ -96,8 +96,7 @@ bitflags! { impl WindowState { pub fn new( attributes: &WindowAttributes, - window_icon: Option, - taskbar_icon: Option, + taskbar_icon: Option, scale_factor: f64, is_dark_mode: bool, ) -> WindowState { @@ -112,7 +111,7 @@ impl WindowState { min_size: attributes.min_inner_size, max_size: attributes.max_inner_size, - window_icon, + window_icon: attributes.window_icon.clone(), taskbar_icon, saved_window: None, diff --git a/src/window.rs b/src/window.rs index 99644fee4b..113e7c042b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -9,7 +9,7 @@ use crate::{ platform_impl, }; -pub use crate::icon::*; +pub use crate::icon::{BadIcon, Icon}; /// Represents a window. /// From b1d8ce24e901ef813064defe3c62f7d23d1fe04c Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 8 Mar 2020 00:21:04 -0500 Subject: [PATCH 115/239] Use i32 instead of u32 for position type in WindowEvent::Moved (#1502) * Use i32 instead of u32 for position type in WindowEvent::Moved * Mark change as breaking --- CHANGELOG.md | 1 + src/event.rs | 2 +- src/platform_impl/windows/event_loop.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0111b9433d..9fd2b3b262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource - Add `BadIcon::OsError` variant for when OS icon functionality fails - On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode +- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. # 0.21.0 (2020-02-04) diff --git a/src/event.rs b/src/event.rs index e7e7be9e31..4756f3d895 100644 --- a/src/event.rs +++ b/src/event.rs @@ -185,7 +185,7 @@ pub enum WindowEvent<'a> { Resized(PhysicalSize), /// The position of the window has changed. Contains the window's new position. - Moved(PhysicalPosition), + Moved(PhysicalPosition), /// The window has been requested to close. CloseRequested, diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 26a2d321f2..5f632c3b4b 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -736,7 +736,7 @@ unsafe extern "system" fn public_window_callback( let windowpos = lparam as *const winuser::WINDOWPOS; if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE { let physical_position = - PhysicalPosition::new((*windowpos).x as u32, (*windowpos).y as u32); + PhysicalPosition::new((*windowpos).x as i32, (*windowpos).y as i32); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Moved(physical_position), From e85a80dd65945bebc22c19736b92713ffc20c06d Mon Sep 17 00:00:00 2001 From: Imberflur <2002109+Imberflur@users.noreply.github.com> Date: Sun, 8 Mar 2020 01:22:53 -0500 Subject: [PATCH 116/239] Fix freeze when pressing modifier keys on Windows (#1503) --- src/platform_impl/windows/event_loop.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 5f632c3b4b..e5a4d4fce8 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -610,6 +610,9 @@ fn update_modifiers(window: HWND, subclass_input: &SubclassInput) { if window_state.modifiers_state != modifiers { window_state.modifiers_state = modifiers; + // Drop lock + drop(window_state); + unsafe { subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), From b208daa271c8840a8c767601656944df57cfb520 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 9 Mar 2020 23:57:04 +0300 Subject: [PATCH 117/239] =?UTF-8?q?Revert=20"on=20MacOS,=20Fix=20not=20sen?= =?UTF-8?q?ding=20ReceivedCharacter=20event=20for=20s=E2=80=A6=20(#1501)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 9daa0738a9aeb60768d9b7d19f4e3deddd55958b. This commit introduced other bug #1453 with likely much more common bindings, so reverting it for now. Fixes #1453. Co-authored-by: Osspial --- CHANGELOG.md | 3 ++- src/platform_impl/macos/view.rs | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fd2b3b262..412c11c494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource - Add `BadIcon::OsError` variant for when OS icon functionality fails - On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode +- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations. +- on macOS, fix incorrect ReceivedCharacter events for some key combinations. - **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. # 0.21.0 (2020-02-04) @@ -37,7 +39,6 @@ # 0.20.0 (2020-01-05) - On X11, fix `ModifiersChanged` emitting incorrect modifier change events - - **Breaking**: Overhaul how Winit handles DPI: + Window functions and events now return `PhysicalSize` instead of `LogicalSize`. + Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types. diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 2307d3e849..839a2bc023 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -383,8 +383,13 @@ extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) { } } -extern "C" fn has_marked_text(_this: &Object, _sel: Sel) -> BOOL { - YES +extern "C" fn has_marked_text(this: &Object, _sel: Sel) -> BOOL { + unsafe { + trace!("Triggered `hasMarkedText`"); + let marked_text: id = *this.get_ivar("markedText"); + trace!("Completed `hasMarkedText`"); + (marked_text.length() > 0) as i8 + } } extern "C" fn marked_range(this: &Object, _sel: Sel) -> NSRange { From 7a9c17a520669177a6124cd833cd63e8a0f8717d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Mon, 9 Mar 2020 20:58:54 +0000 Subject: [PATCH 118/239] Bump version to 0.22.0 (#1500) There are two PRs I'm aware of that should be relatively trivial to get merged, which would fix some issues. Other than those, I don't think it makes sense to wait on anything. - Fix Windows crash: https://github.com/rust-windowing/winit/pull/1459 - Fix macOS mouse reports: https://github.com/rust-windowing/winit/pull/1490 While #1459 seems pretty essential to actually make winit run, #1490 is much less important and can probably be ignored if there aren't any resources to merge it. --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 412c11c494..8ec38c7a26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 0.22.0 (2020-03-07) - On Windows, fix minor timing issue in wait_until_time_or_msg - On Windows, rework handling of request_redraw() to address panics. diff --git a/Cargo.toml b/Cargo.toml index 3ea410c913..71fb66f792 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.21.0" +version = "0.22.0" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index 1a3bdecfb2..cd8e837131 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ```toml [dependencies] -winit = "0.21.0" +winit = "0.22.0" ``` ## [Documentation](https://docs.rs/winit) From 29ab0bb62953e629070236e521c4ab33d9366185 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 9 Mar 2020 16:59:39 -0400 Subject: [PATCH 119/239] Correct 0.22.0 date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ec38c7a26..eafc867e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.22.0 (2020-03-07) +# 0.22.0 (2020-03-09) - On Windows, fix minor timing issue in wait_until_time_or_msg - On Windows, rework handling of request_redraw() to address panics. From 0683bdcd42b39f7bb4b0fbff2f5fd4923da8f53b Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 9 Mar 2020 17:01:34 -0400 Subject: [PATCH 120/239] Add Unreleased category back to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eafc867e32..832aaccba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +# Unreleased + # 0.22.0 (2020-03-09) - On Windows, fix minor timing issue in wait_until_time_or_msg From 7e0427371957a4434ac78843fa33ec75e6b8cf0e Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 9 Mar 2020 18:23:03 -0400 Subject: [PATCH 121/239] Replace Travis and Appveyor CI badges with GitHub Actions CI badge --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index cd8e837131..f6e69a7539 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ [![Crates.io](https://img.shields.io/crates/v/winit.svg)](https://crates.io/crates/winit) [![Docs.rs](https://docs.rs/winit/badge.svg)](https://docs.rs/winit) -[![Build Status](https://travis-ci.org/rust-windowing/winit.svg?branch=master)](https://travis-ci.org/rust-windowing/winit) -[![Build status](https://ci.appveyor.com/api/projects/status/hr89but4x1n3dphq/branch/master?svg=true)](https://ci.appveyor.com/project/Osspial/winit/branch/master) +[![CI Status](https://github.com/rust-windowing/winit/workflows/CI/badge.svg)](https://github.com/rust-windowing/winit/actions) ```toml [dependencies] From c2aed1979d147a0fe64e2054ad416339a2bbffe1 Mon Sep 17 00:00:00 2001 From: Murarth Date: Wed, 11 Mar 2020 21:54:23 -0700 Subject: [PATCH 122/239] X11: Fix `ResumeTimeReached` being fired early (#1505) * X11: Fix `ResumeTimeReached` being fired early * Update CHANGELOG.md Co-authored-by: Osspial --- CHANGELOG.md | 2 ++ src/platform_impl/linux/x11/mod.rs | 54 +++++++++++------------------- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 832aaccba8..67a233ac42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- On X11, fix `ResumeTimeReached` being fired too early. + # 0.22.0 (2020-03-09) - On Windows, fix minor timing issue in wait_until_time_or_msg diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index f9e0ea569b..93a386f222 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -268,14 +268,16 @@ impl EventLoop { { let mut control_flow = ControlFlow::default(); let mut events = Events::with_capacity(8); - - callback( - crate::event::Event::NewEvents(crate::event::StartCause::Init), - &self.target, - &mut control_flow, - ); + let mut cause = StartCause::Init; loop { + sticky_exit_callback( + crate::event::Event::NewEvents(cause), + &self.target, + &mut control_flow, + &mut callback, + ); + // Process all pending events self.drain_events(&mut callback, &mut control_flow); @@ -326,7 +328,7 @@ impl EventLoop { } let start = Instant::now(); - let (mut cause, deadline, timeout); + let (deadline, timeout); match control_flow { ControlFlow::Exit => break, @@ -357,38 +359,20 @@ impl EventLoop { } } - if self.event_processor.poll() { - // If the XConnection already contains buffered events, we don't - // need to wait for data on the socket. - // However, we still need to check for user events. - self.poll - .poll(&mut events, Some(Duration::from_millis(0))) - .unwrap(); - events.clear(); - - callback( - crate::event::Event::NewEvents(cause), - &self.target, - &mut control_flow, - ); - } else { + // If the XConnection already contains buffered events, we don't + // need to wait for data on the socket. + if !self.event_processor.poll() { self.poll.poll(&mut events, timeout).unwrap(); events.clear(); + } - let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline); + let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline); - if wait_cancelled { - cause = StartCause::WaitCancelled { - start, - requested_resume: deadline, - }; - } - - callback( - crate::event::Event::NewEvents(cause), - &self.target, - &mut control_flow, - ); + if wait_cancelled { + cause = StartCause::WaitCancelled { + start, + requested_resume: deadline, + }; } } From 28023d9f5bb74b66a9b294b4c8fef6c46a9d9b92 Mon Sep 17 00:00:00 2001 From: Matthew Russo Date: Wed, 25 Mar 2020 22:38:25 -0700 Subject: [PATCH 123/239] upgrades x11-dl to 2.18.5 to fix #376 (#1517) x11-dl was using std::mem::uninitialized incorrectly and when rustlang added MaybeUninit and intrinsic panics on UB caused by improper use of uninitialized (see rust-lang/rust/pull/69922) it caused issues with X11 initialization. x11-dl pr erlepereira/x11-rs/pull/101 updated x11-dl to use MaybeUninit correctly --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 71fb66f792..4cc88d5f1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", " mio = "0.6" mio-extras = "2.0" smithay-client-toolkit = "^0.6.6" -x11-dl = "2.18.3" +x11-dl = "2.18.5" percent-encoding = "2.0" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot] From 0bc58f695b01977f81d1259745c89093d18bb24a Mon Sep 17 00:00:00 2001 From: Murarth Date: Fri, 10 Apr 2020 11:29:33 -0700 Subject: [PATCH 124/239] Fix warnings (#1530) --- src/platform_impl/linux/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 8958ddc030..4713038f38 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -63,7 +63,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { lazy_static! { pub static ref X11_BACKEND: Mutex, XNotSupported>> = - { Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) }; + Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)); } #[derive(Debug, Clone)] From a8e777a5dffac9d8528166d44a4cafddffe21e37 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Sat, 11 Apr 2020 15:20:38 -0400 Subject: [PATCH 125/239] Fix a possible double-borrow during event handling (#1512) --- CHANGELOG.md | 1 + src/platform_impl/web/event_loop/runner.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67a233ac42..0d198d7de1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On X11, fix `ResumeTimeReached` being fired too early. +- On Web, fix a possible panic during event handling # 0.22.0 (2020-03-09) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 398fcaf6cb..7c67eac049 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -202,7 +202,6 @@ impl Shared { // It should only ever be called from send_event fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) { let is_closed = self.is_closed(); - match *self.0.runner.borrow_mut() { Some(ref mut runner) => { // An event is being processed, so the runner should be marked busy @@ -227,7 +226,9 @@ impl Shared { // If the runner doesn't exist and this method recurses, it will recurse infinitely if !is_closed && self.0.runner.borrow().is_some() { // Take an event out of the queue and handle it - if let Some(event) = self.0.events.borrow_mut().pop_front() { + // Make sure not to let the borrow_mut live during the next handle_event + let event = { self.0.events.borrow_mut().pop_front() }; + if let Some(event) = event { self.handle_event(event, control); } } From 1f24a09570d3bf0a39af836c3a7c212a0b65ff70 Mon Sep 17 00:00:00 2001 From: Jurgis Date: Sat, 11 Apr 2020 22:49:07 +0300 Subject: [PATCH 126/239] Implement `requestAnimationFrame` for web (#1519) * Use requestAnimationFrame for polling wasm * Implement `requestAnimationFrame` for stdweb Co-authored-by: Ryan G --- CHANGELOG.md | 1 + src/event_loop.rs | 3 +- src/platform_impl/web/event_loop/runner.rs | 2 +- src/platform_impl/web/event_loop/state.rs | 2 +- src/platform_impl/web/stdweb/mod.rs | 2 +- src/platform_impl/web/stdweb/timeout.rs | 40 +++++++++++++++++- src/platform_impl/web/web_sys/mod.rs | 2 +- src/platform_impl/web/web_sys/timeout.rs | 47 ++++++++++++++++++++++ 8 files changed, 93 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d198d7de1..8af1139dbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On X11, fix `ResumeTimeReached` being fired too early. +- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling # 0.22.0 (2020-03-09) diff --git a/src/event_loop.rs b/src/event_loop.rs index 8a05e31359..224292a564 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -72,7 +72,8 @@ impl fmt::Debug for EventLoopWindowTarget { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of - /// whether or not new events are available to process. + /// whether or not new events are available to process. For web, events are sent when + /// `requestAnimationFrame` fires. Poll, /// When the current loop iteration finishes, suspend the thread until another event arrives. Wait, diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 7c67eac049..d381b942b2 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -241,7 +241,7 @@ impl Shared { root::ControlFlow::Poll => { let cloned = self.clone(); State::Poll { - timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)), + request: backend::AnimationFrameRequest::new(move || cloned.poll()), } } root::ControlFlow::Wait => State::Wait { diff --git a/src/platform_impl/web/event_loop/state.rs b/src/platform_impl/web/event_loop/state.rs index 23e8045fc3..16b6e6232c 100644 --- a/src/platform_impl/web/event_loop/state.rs +++ b/src/platform_impl/web/event_loop/state.rs @@ -15,7 +15,7 @@ pub enum State { start: Instant, }, Poll { - timeout: backend::Timeout, + request: backend::AnimationFrameRequest, }, Exit, } diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index a95dfc7d36..3632307ca7 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -3,7 +3,7 @@ mod event; mod timeout; pub use self::canvas::Canvas; -pub use self::timeout::Timeout; +pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; use crate::platform::web::WindowExtStdweb; diff --git a/src/platform_impl/web/stdweb/timeout.rs b/src/platform_impl/web/stdweb/timeout.rs index 00ac2ab02d..72bfb72bb1 100644 --- a/src/platform_impl/web/stdweb/timeout.rs +++ b/src/platform_impl/web/stdweb/timeout.rs @@ -1,5 +1,7 @@ +use std::cell::Cell; +use std::rc::Rc; use std::time::Duration; -use stdweb::web::{window, IWindowOrWorker, TimeoutHandle}; +use stdweb::web::{window, IWindowOrWorker, RequestAnimationFrameHandle, TimeoutHandle}; #[derive(Debug)] pub struct Timeout { @@ -23,3 +25,39 @@ impl Drop for Timeout { handle.clear(); } } + +#[derive(Debug)] +pub struct AnimationFrameRequest { + handle: Option, + // track callback state, because `cancelAnimationFrame` is slow + fired: Rc>, +} + +impl AnimationFrameRequest { + pub fn new(mut f: F) -> AnimationFrameRequest + where + F: 'static + FnMut(), + { + let fired = Rc::new(Cell::new(false)); + let c_fired = fired.clone(); + let handle = window().request_animation_frame(move |_| { + (*c_fired).set(true); + f(); + }); + + AnimationFrameRequest { + handle: Some(handle), + fired, + } + } +} + +impl Drop for AnimationFrameRequest { + fn drop(&mut self) { + if !(*self.fired).get() { + if let Some(handle) = self.handle.take() { + handle.cancel(); + } + } + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index c41cd069f2..94efe1ec27 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -3,7 +3,7 @@ mod event; mod timeout; pub use self::canvas::Canvas; -pub use self::timeout::Timeout; +pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; use crate::platform::web::WindowExtWebSys; diff --git a/src/platform_impl/web/web_sys/timeout.rs b/src/platform_impl/web/web_sys/timeout.rs index e7ce69a083..e95c54ed51 100644 --- a/src/platform_impl/web/web_sys/timeout.rs +++ b/src/platform_impl/web/web_sys/timeout.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; +use std::rc::Rc; use std::time::Duration; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; @@ -38,3 +40,48 @@ impl Drop for Timeout { window.clear_timeout_with_handle(self.handle); } } + +#[derive(Debug)] +pub struct AnimationFrameRequest { + handle: i32, + // track callback state, because `cancelAnimationFrame` is slow + fired: Rc>, + _closure: Closure, +} + +impl AnimationFrameRequest { + pub fn new(mut f: F) -> AnimationFrameRequest + where + F: 'static + FnMut(), + { + let window = web_sys::window().expect("Failed to obtain window"); + + let fired = Rc::new(Cell::new(false)); + let c_fired = fired.clone(); + let closure = Closure::wrap(Box::new(move || { + (*c_fired).set(true); + f(); + }) as Box); + + let handle = window + .request_animation_frame(&closure.as_ref().unchecked_ref()) + .expect("Failed to request animation frame"); + + AnimationFrameRequest { + handle, + fired, + _closure: closure, + } + } +} + +impl Drop for AnimationFrameRequest { + fn drop(&mut self) { + if !(*self.fired).get() { + let window = web_sys::window().expect("Failed to obtain window"); + window + .cancel_animation_frame(self.handle) + .expect("Failed to cancel animation frame"); + } + } +} From d5609729cc5c23a1d74c8c25d3af9c3bc7512a6f Mon Sep 17 00:00:00 2001 From: Ryan G Date: Fri, 17 Apr 2020 13:36:42 -0400 Subject: [PATCH 127/239] Bump version to 0.22.1 (#1537) There are a few relatively important bugfixes with no API impact in the master branch. We might as well release this as a non-breaking change. --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8af1139dbc..ecb4aa4f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 0.22.1 (2020-04-16) + - On X11, fix `ResumeTimeReached` being fired too early. - On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling diff --git a/Cargo.toml b/Cargo.toml index 4cc88d5f1b..62d46bf0ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.22.0" +version = "0.22.1" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index f6e69a7539..3937622557 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```toml [dependencies] -winit = "0.22.0" +winit = "0.22.1" ``` ## [Documentation](https://docs.rs/winit) From 4c4d0916fd736fcf28d5ee0f80a0fb367de4a0d8 Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Sun, 19 Apr 2020 19:55:10 +0200 Subject: [PATCH 128/239] control_flow example: fix wait_cancelled logic again (#1511) --- examples/control_flow.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/examples/control_flow.rs b/examples/control_flow.rs index 13b5bcf832..6d71541216 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -41,15 +41,9 @@ fn main() { println!("{:?}", event); match event { Event::NewEvents(start_cause) => { - wait_cancelled = mode == Mode::WaitUntil; - match start_cause { - StartCause::ResumeTimeReached { - start: _, - requested_resume: _, - } => { - wait_cancelled = false; - } - _ => (), + wait_cancelled = match start_cause { + StartCause::WaitCancelled { .. } => mode == Mode::WaitUntil, + _ => false, } } Event::WindowEvent { event, .. } => match event { From 6dae994bb4c2d73d5a9f31718feb41e1fa2b98ff Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 19 Apr 2020 11:58:58 -0700 Subject: [PATCH 129/239] Mention raw-window-handle in library docs (#1528) --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6011203e0b..cf43f3964b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,8 +94,9 @@ //! # Drawing on the window //! //! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to -//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you -//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. +//! retrieve the raw handle of the window (see the [`platform`] module and/or the +//! [`raw_window_handle`] method), which in turn allows you to create an +//! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. //! //! [`EventLoop`]: event_loop::EventLoop //! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return @@ -116,6 +117,7 @@ //! [`UserEvent`]: event::Event::UserEvent //! [`LoopDestroyed`]: event::Event::LoopDestroyed //! [`platform`]: platform +//! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle #![deny(rust_2018_idioms)] #![deny(intra_doc_link_resolution_failure)] From 78a62ec5473e9cd5472c18f859103b0b0cb88362 Mon Sep 17 00:00:00 2001 From: simlay Date: Sun, 19 Apr 2020 12:37:13 -0700 Subject: [PATCH 130/239] Added more docs.rs targets (#1521) --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 62d46bf0ed..0b6f833f4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,9 @@ documentation = "https://docs.rs/winit" categories = ["gui"] [package.metadata.docs.rs] -features = ["serde"] +features = ["serde", "web-sys"] +default-target = "x86_64-unknown-linux-gnu" +targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"] [features] web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"] From aabe42d252f66c6410b2f805a9ea80f12517e16d Mon Sep 17 00:00:00 2001 From: Yanchi Toth Date: Sun, 19 Apr 2020 21:52:48 +0200 Subject: [PATCH 131/239] Preserve with_maximized on windows (#1515) --- CHANGELOG.md | 2 ++ src/platform_impl/windows/window.rs | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb4aa4f7c..e428fbbee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- On Windows, fix `WindowBuilder::with_maximized` being ignored. + # 0.22.1 (2020-04-16) - On X11, fix `ResumeTimeReached` being fired too early. diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 3c58e1fd0b..45a3b5debd 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -728,8 +728,6 @@ unsafe fn init( } } - window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized); - // If the system theme is dark, we need to set the window theme now // before we update the window flags (and possibly show the // window for the first time). @@ -757,6 +755,11 @@ unsafe fn init( .inner_size .unwrap_or_else(|| PhysicalSize::new(1024, 768).into()); win.set_inner_size(dimensions); + if attributes.maximized { + // Need to set MAXIMIZED after setting `inner_size` as + // `Window::set_inner_size` changes MAXIMIZED to false. + win.set_maximized(true); + } win.set_visible(attributes.visible); if let Some(_) = attributes.fullscreen { From 849b8f5dce4d3f93b251259f5323ea9fd43e0c05 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 19 Apr 2020 14:09:08 -0700 Subject: [PATCH 132/239] Clarify when RedrawRequested is useful (#1529) Co-Authored-By: Osspial --- src/event.rs | 10 +++++++--- src/lib.rs | 10 +++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/event.rs b/src/event.rs index 4756f3d895..6b9fe1a630 100644 --- a/src/event.rs +++ b/src/event.rs @@ -81,10 +81,11 @@ pub enum Event<'a, T: 'static> { /// /// This event is useful as a place to put your code that should be run after all /// state-changing events have been handled and you want to do stuff (updating state, performing - /// calculations, etc) that happens as the "main body" of your event loop. If your program draws - /// graphics, it's usually better to do it in response to + /// calculations, etc) that happens as the "main body" of your event loop. If your program only draws + /// graphics when something changes, it's usually better to do it in response to /// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted - /// immediately after this event. + /// immediately after this event. Programs that draw graphics continuously, like most games, + /// can render here unconditionally for simplicity. MainEventsCleared, /// Emitted after `MainEventsCleared` when a window should be redrawn. @@ -97,6 +98,9 @@ pub enum Event<'a, T: 'static> { /// /// During each iteration of the event loop, Winit will aggregate duplicate redraw requests /// into a single event, to help avoid duplicating rendering work. + /// + /// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless + /// something changes, like most non-game GUIs. RedrawRequested(WindowId), /// Emitted after all `RedrawRequested` events have been processed and control flow is about to diff --git a/src/lib.rs b/src/lib.rs index cf43f3964b..e3b0be75d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,14 +73,18 @@ //! // Application update code. //! //! // Queue a RedrawRequested event. +//! // +//! // You only need to call this if you've determined that you need to redraw, in +//! // applications which do not always need to. Applications that redraw continuously +//! // can just render here instead. //! window.request_redraw(); //! }, //! Event::RedrawRequested(_) => { //! // Redraw the application. //! // -//! // It's preferrable to render in this event rather than in MainEventsCleared, since -//! // rendering in here allows the program to gracefully handle redraws requested -//! // by the OS. +//! // It's preferable for applications that do not render continuously to render in +//! // this event rather than in MainEventsCleared, since rendering in here allows +//! // the program to gracefully handle redraws requested by the OS. //! }, //! _ => () //! } From 47ff8d61d1330372bd04cc398827e2063c395dd3 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 20 Apr 2020 00:04:30 -0400 Subject: [PATCH 133/239] Document that platforms will display garbage data in the window by default (#1541) --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e3b0be75d3..ce66665616 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,6 +102,12 @@ //! [`raw_window_handle`] method), which in turn allows you to create an //! OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics. //! +//! Note that many platforms will display garbage data in the window's client area if the +//! application doesn't render anything to the window by the time the desktop compositor is ready to +//! display the window to the user. If you notice this happening, you should create the window with +//! [`visible` set to `false`](crate::window::WindowBuilder::with_visible) and explicitly make the +//! window visible only once you're ready to render into it. +//! //! [`EventLoop`]: event_loop::EventLoop //! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return //! [`EventLoop::new()`]: event_loop::EventLoop::new From 54bc41f68bc852fd220840dd4aa3c5f4369b70ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n?= Date: Mon, 20 Apr 2020 23:48:42 +0200 Subject: [PATCH 134/239] Implement `Drop` for `Proxy` on macOS platform (#1526) --- CHANGELOG.md | 1 + src/platform_impl/macos/event_loop.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e428fbbee1..ef5e9fd955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - On X11, fix `ResumeTimeReached` being fired too early. - On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling +- On macOS, fix `EventLoopProxy` leaking memory for every instance. # 0.22.0 (2020-03-09) diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index cc3d392840..01437b845e 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -117,6 +117,14 @@ pub struct Proxy { unsafe impl Send for Proxy {} +impl Drop for Proxy { + fn drop(&mut self) { + unsafe { + CFRelease(self.source as _); + } + } +} + impl Clone for Proxy { fn clone(&self) -> Self { Proxy::new(self.sender.clone()) From 114fe9d502cd4b37ee332f4165fb92c1056e25d9 Mon Sep 17 00:00:00 2001 From: Matthias Fauconneau Date: Wed, 22 Apr 2020 18:00:41 +0200 Subject: [PATCH 135/239] wayland: rework scale factor handling (#1538) - Always send Resized events in case of scale factor change - Properly take into account the resize the user can do using the resize event. --- src/platform_impl/linux/wayland/event_loop.rs | 85 ++++++++++--------- src/platform_impl/linux/wayland/window.rs | 83 +++++++++--------- 2 files changed, 90 insertions(+), 78 deletions(-) diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 47c5aea634..5a3614c8b5 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -714,8 +714,43 @@ impl EventLoop { window_target.store.lock().unwrap().for_each(|window| { let window_id = crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid)); - if let Some(frame) = window.frame { - if let Some((w, h)) = window.newsize { + + // Update window logical .size field (for callbacks using .inner_size) + let (old_logical_size, mut logical_size) = { + let mut window_size = window.size.lock().unwrap(); + let old_logical_size = *window_size; + *window_size = window.new_size.unwrap_or(old_logical_size); + (old_logical_size, *window_size) + }; + + if let Some(scale_factor) = window.new_scale_factor { + // Update cursor scale factor + self.cursor_manager + .lock() + .unwrap() + .update_scale_factor(scale_factor as u32); + let new_logical_size = { + let scale_factor = scale_factor as f64; + let mut physical_size = + LogicalSize::::from(logical_size).to_physical(scale_factor); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut physical_size, + }, + }); + physical_size.to_logical::(scale_factor).into() + }; + // Update size if changed by callback + if new_logical_size != logical_size { + logical_size = new_logical_size; + *window.size.lock().unwrap() = logical_size.into(); + } + } + + if window.new_size.is_some() || window.new_scale_factor.is_some() { + if let Some(frame) = window.frame { // Update decorations state match window.decorations_action { Some(DecorationsAction::Hide) => frame.set_decorate(false), @@ -726,51 +761,23 @@ impl EventLoop { // mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case // it overlaps mutter's `bounding box`, so we can't avoid this resize call, // which calls `set_geometry` under the hood, for now. + let (w, h) = logical_size; frame.resize(w, h); frame.refresh(); - - // Don't send resize event downstream if the new size is identical to the - // current one. - if (w, h) != *window.size { - let logical_size = crate::dpi::LogicalSize::new(w as f64, h as f64); - let physical_size = logical_size - .to_physical(window.new_dpi.unwrap_or(window.prev_dpi) as f64); - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Resized(physical_size), - }); - *window.size = (w, h); - } } - - if let Some(dpi) = window.new_dpi { - // Update cursor scale factor - { - self.cursor_manager - .lock() - .unwrap() - .update_scale_factor(dpi as u32); - }; - let dpi = dpi as f64; - let logical_size = LogicalSize::::from(*window.size); - let mut new_inner_size = logical_size.to_physical(dpi); - + // Don't send resize event downstream if the new logical size and scale is identical to the + // current one + if logical_size != old_logical_size || window.new_scale_factor.is_some() { + let physical_size = LogicalSize::::from(logical_size).to_physical( + window.new_scale_factor.unwrap_or(window.prev_scale_factor) as f64, + ); callback(Event::WindowEvent { window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: dpi, - new_inner_size: &mut new_inner_size, - }, + event: WindowEvent::Resized(physical_size), }); - - let (w, h) = new_inner_size.to_logical::(dpi).into(); - frame.resize(w, h); - // Refresh frame to rescale decorations - frame.refresh(); - *window.size = (w, h); } } + if window.closed { callback(Event::WindowEvent { window_id, diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 7c30a79bf5..12ae9ba1d5 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -59,15 +59,20 @@ impl Window { // Create the surface first to get initial DPI let window_store = evlp.store.clone(); let cursor_manager = evlp.cursor_manager.clone(); - let surface = evlp.env.create_surface(move |dpi, surface| { - window_store.lock().unwrap().dpi_change(&surface, dpi); - surface.set_buffer_scale(dpi); + let surface = evlp.env.create_surface(move |scale_factor, surface| { + window_store + .lock() + .unwrap() + .scale_factor_change(&surface, scale_factor); + surface.set_buffer_scale(scale_factor); }); - let dpi = get_dpi_factor(&surface) as f64; + // Always 1. + let scale_factor = get_dpi_factor(&surface); + let (width, height) = attributes .inner_size - .map(|size| size.to_logical::(dpi).into()) + .map(|size| size.to_logical::(scale_factor as f64).into()) .unwrap_or((800, 600)); // Create the window @@ -91,7 +96,7 @@ impl Window { for window in &mut store.windows { if window.surface.as_ref().equals(&my_surface.as_ref()) { - window.newsize = new_size; + window.new_size = new_size; *(window.need_refresh.lock().unwrap()) = true; { // Get whether we're in fullscreen @@ -173,12 +178,12 @@ impl Window { frame.set_min_size( attributes .min_inner_size - .map(|size| size.to_logical::(dpi).into()), + .map(|size| size.to_logical::(scale_factor as f64).into()), ); frame.set_max_size( attributes .max_inner_size - .map(|size| size.to_logical::(dpi).into()), + .map(|size| size.to_logical::(scale_factor as f64).into()), ); let kill_switch = Arc::new(Mutex::new(false)); @@ -189,7 +194,7 @@ impl Window { evlp.store.lock().unwrap().windows.push(InternalWindow { closed: false, - newsize: None, + new_size: None, size: size.clone(), need_refresh: need_refresh.clone(), fullscreen: fullscreen.clone(), @@ -198,8 +203,8 @@ impl Window { surface: surface.clone(), kill_switch: kill_switch.clone(), frame: Arc::downgrade(&frame), - current_dpi: 1, - new_dpi: None, + current_scale_factor: scale_factor, + new_scale_factor: None, decorated: decorated.clone(), pending_decorations_action: pending_decorations_action.clone(), }); @@ -250,9 +255,9 @@ impl Window { } pub fn inner_size(&self) -> PhysicalSize { - let dpi = self.scale_factor() as f64; + let scale_factor = self.scale_factor() as f64; let size = LogicalSize::::from(*self.size.lock().unwrap()); - size.to_physical(dpi) + size.to_physical(scale_factor) } pub fn request_redraw(&self) { @@ -261,38 +266,38 @@ impl Window { #[inline] pub fn outer_size(&self) -> PhysicalSize { - let dpi = self.scale_factor() as f64; + let scale_factor = self.scale_factor() as f64; let (w, h) = self.size.lock().unwrap().clone(); // let (w, h) = super::wayland_window::add_borders(w as i32, h as i32); let size = LogicalSize::::from((w, h)); - size.to_physical(dpi) + size.to_physical(scale_factor) } #[inline] // NOTE: This will only resize the borders, the contents must be updated by the user pub fn set_inner_size(&self, size: Size) { - let dpi = self.scale_factor() as f64; - let (w, h) = size.to_logical::(dpi).into(); + let scale_factor = self.scale_factor() as f64; + let (w, h) = size.to_logical::(scale_factor).into(); self.frame.lock().unwrap().resize(w, h); *(self.size.lock().unwrap()) = (w, h); } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { - let dpi = self.scale_factor() as f64; + let scale_factor = self.scale_factor() as f64; self.frame .lock() .unwrap() - .set_min_size(dimensions.map(|dim| dim.to_logical::(dpi).into())); + .set_min_size(dimensions.map(|dim| dim.to_logical::(scale_factor).into())); } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { - let dpi = self.scale_factor() as f64; + let scale_factor = self.scale_factor() as f64; self.frame .lock() .unwrap() - .set_max_size(dimensions.map(|dim| dim.to_logical::(dpi).into())); + .set_max_size(dimensions.map(|dim| dim.to_logical::(scale_factor).into())); } #[inline] @@ -428,7 +433,7 @@ impl Drop for Window { struct InternalWindow { surface: wl_surface::WlSurface, // TODO: CONVERT TO LogicalSizes - newsize: Option<(u32, u32)>, + new_size: Option<(u32, u32)>, size: Arc>, need_refresh: Arc>, fullscreen: Arc>, @@ -437,8 +442,8 @@ struct InternalWindow { closed: bool, kill_switch: Arc>, frame: Weak>>, - current_dpi: i32, - new_dpi: Option, + current_scale_factor: i32, + new_scale_factor: Option, decorated: Arc>, pending_decorations_action: Arc>>, } @@ -448,10 +453,10 @@ pub struct WindowStore { } pub struct WindowStoreForEach<'a> { - pub newsize: Option<(u32, u32)>, - pub size: &'a mut (u32, u32), - pub prev_dpi: i32, - pub new_dpi: Option, + pub new_size: Option<(u32, u32)>, + pub size: &'a Mutex<(u32, u32)>, + pub prev_scale_factor: i32, + pub new_scale_factor: Option, pub closed: bool, pub grab_cursor: Option, pub surface: &'a wl_surface::WlSurface, @@ -499,10 +504,11 @@ impl WindowStore { } } - fn dpi_change(&mut self, surface: &wl_surface::WlSurface, new: i32) { + fn scale_factor_change(&mut self, surface: &wl_surface::WlSurface, new: i32) { for window in &mut self.windows { if surface.as_ref().equals(&window.surface.as_ref()) { - window.new_dpi = Some(new); + window.new_scale_factor = Some(new); + *(window.need_refresh.lock().unwrap()) = true; } } } @@ -512,15 +518,18 @@ impl WindowStore { F: FnMut(WindowStoreForEach<'_>), { for window in &mut self.windows { + let prev_scale_factor = window.current_scale_factor; + if let Some(scale_factor) = window.new_scale_factor { + window.current_scale_factor = scale_factor; + } let opt_arc = window.frame.upgrade(); let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); - let mut size = { *window.size.lock().unwrap() }; let decorations_action = { window.pending_decorations_action.lock().unwrap().take() }; f(WindowStoreForEach { - newsize: window.newsize.take(), - size: &mut size, - prev_dpi: window.current_dpi, - new_dpi: window.new_dpi, + new_size: window.new_size.take(), + size: &window.size, + prev_scale_factor, + new_scale_factor: window.new_scale_factor.take(), closed: window.closed, grab_cursor: window.cursor_grab_changed.lock().unwrap().take(), surface: &window.surface, @@ -528,10 +537,6 @@ impl WindowStore { frame: opt_mutex_lock.as_mut().map(|m| &mut **m), decorations_action, }); - *window.size.lock().unwrap() = size; - if let Some(dpi) = window.new_dpi.take() { - window.current_dpi = dpi; - } // avoid re-spamming the event window.closed = false; } From 26775fa0b621c225753cefb2a3babead3be33f46 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 26 Apr 2020 20:42:45 +0000 Subject: [PATCH 136/239] Report mouse motion before click (#1490) * Report mouse motion before click This fixes an issue on macOS where a mouse click would be generated, without ever getting a mouse motion to the position before the click. This leads to the application thinking the mouse click occurred at a position other than the actual mouse location. This happens due to mouse motion above the window not automatically giving focus to the window, unless it is actually clicked, making it possible to move the window without motion events. Fixes #942. * Add additional mouse motion events Co-authored-by: Ryan Goldstein --- CHANGELOG.md | 1 + src/platform_impl/macos/view.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef5e9fd955..dc79991608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations. - on macOS, fix incorrect ReceivedCharacter events for some key combinations. - **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. +- On macOS, a mouse motion event is now generated before every mouse click. # 0.21.0 (2020-02-04) diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 839a2bc023..82ee50767e 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -869,26 +869,32 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem } extern "C" fn mouse_down(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Left, ElementState::Pressed); } extern "C" fn mouse_up(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Left, ElementState::Released); } extern "C" fn right_mouse_down(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Right, ElementState::Pressed); } extern "C" fn right_mouse_up(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Right, ElementState::Released); } extern "C" fn other_mouse_down(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Middle, ElementState::Pressed); } extern "C" fn other_mouse_up(this: &Object, _sel: Sel, event: id) { + mouse_motion(this, event); mouse_click(this, event, MouseButton::Middle, ElementState::Released); } @@ -986,6 +992,9 @@ extern "C" fn mouse_exited(this: &Object, _sel: Sel, _event: id) { extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { trace!("Triggered `scrollWheel`"); + + mouse_motion(this, event); + unsafe { let delta = { let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); @@ -1031,6 +1040,9 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { extern "C" fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { trace!("Triggered `pressureChangeWithEvent`"); + + mouse_motion(this, event); + unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); From b4c6cdf9a33530a2116f37a21547341c34537be1 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 4 May 2020 15:14:13 -0400 Subject: [PATCH 137/239] Fix several crashes on Windows by heavily simplifying the event loop code (#1496) --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 492 ++++++++---- .../windows/event_loop/runner.rs | 705 ++++++++---------- src/platform_impl/windows/window_state.rs | 4 +- 4 files changed, 658 insertions(+), 544 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc79991608..f9259fd55b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. # 0.22.1 (2020-04-16) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index e5a4d4fce8..a7deac50c5 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1,17 +1,4 @@ #![allow(non_snake_case)] -//! An events loop on Win32 is a background thread. -//! -//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop. -//! Destroying the events loop stops the thread. -//! -//! You can use the `execute_in_thread` method to execute some code in the background thread. -//! Since Win32 requires you to create a window in the right thread, you must use this method -//! to create a window. -//! -//! If you create a window whose class is set to `callback`, the window's events will be -//! propagated with `run_forever` and `poll_events`. -//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to -//! add a `WindowState` entry to a list of window to be used by the callback. mod runner; @@ -24,6 +11,7 @@ use std::{ mpsc::{self, Receiver, Sender}, Arc, }, + thread, time::{Duration, Instant}, }; use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR}; @@ -41,7 +29,6 @@ use winapi::{ }, }; -use self::runner::{ELRShared, EventLoopRunnerShared}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, @@ -57,6 +44,7 @@ use crate::{ }, window::{Fullscreen, WindowId as RootWindowId}, }; +use runner::{EventLoopRunner, EventLoopRunnerShared}; type GetPointerFrameInfoHistory = unsafe extern "system" fn( pointerId: UINT, @@ -160,9 +148,17 @@ impl EventLoop { pub fn new_dpi_unaware_any_thread() -> EventLoop { let thread_id = unsafe { processthreadsapi::GetCurrentThreadId() }; - let runner_shared = Rc::new(ELRShared::new()); - let (thread_msg_target, thread_msg_sender) = - thread_event_target_window(runner_shared.clone()); + + let thread_msg_target = create_event_target_window(); + + let send_thread_msg_target = thread_msg_target as usize; + thread::spawn(move || wait_thread(thread_id, send_thread_msg_target as HWND)); + let wait_thread_id = get_wait_thread_id(); + + let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); + + let thread_msg_sender = + subclass_event_target_window(thread_msg_target, runner_shared.clone()); raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target); EventLoop { @@ -200,87 +196,39 @@ impl EventLoop { self.window_target .p .runner_shared - .set_runner(self, move |event, control_flow| { + .set_event_handler(move |event, control_flow| { event_handler(event, event_loop_windows_ref, control_flow) - }) + }); } let runner = &self.window_target.p.runner_shared; unsafe { let mut msg = mem::zeroed(); - let mut unread_message_exists = false; + runner.poll(); 'main: loop { - if let Err(payload) = runner.take_panic_error() { - runner.destroy_runner(); - panic::resume_unwind(payload); + if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { + break 'main; } + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); - runner.new_events(); - loop { - if !unread_message_exists { - if 0 == winuser::PeekMessageW( - &mut msg, - ptr::null_mut(), - 0, - 0, - winuser::PM_REMOVE, - ) { - break; - } - } - winuser::TranslateMessage(&mut msg); - winuser::DispatchMessageW(&mut msg); - - unread_message_exists = false; - - if msg.message == winuser::WM_PAINT { - // An "external" redraw was requested. - // Note that the WM_PAINT has been dispatched and - // has caused the event loop to emit the MainEventsCleared event. - // See EventLoopRunner::process_event(). - // The call to main_events_cleared() below will do nothing. - break; - } + if let Err(payload) = runner.take_panic_error() { + runner.reset_runner(); + panic::resume_unwind(payload); } - // Make sure we emit the MainEventsCleared event if no WM_PAINT message was received. - runner.main_events_cleared(); - // Drain eventual WM_PAINT messages sent if user called request_redraw() - // during handling of MainEventsCleared. - loop { - if 0 == winuser::PeekMessageW( - &mut msg, - ptr::null_mut(), - winuser::WM_PAINT, - winuser::WM_PAINT, - winuser::PM_QS_PAINT | winuser::PM_REMOVE, - ) { - break; - } - winuser::TranslateMessage(&mut msg); - winuser::DispatchMessageW(&mut msg); - } - runner.redraw_events_cleared(); - match runner.control_flow() { - ControlFlow::Exit => break 'main, - ControlFlow::Wait => { - if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { - break 'main; - } - unread_message_exists = true; - } - ControlFlow::WaitUntil(resume_time) => { - wait_until_time_or_msg(resume_time); - } - ControlFlow::Poll => (), + if runner.control_flow() == ControlFlow::Exit && !runner.handling_events() { + break 'main; } } } - runner.destroy_loop(); - runner.destroy_runner(); + unsafe { + runner.call_event_handler(Event::LoopDestroyed); + } + runner.reset_runner(); } pub fn create_proxy(&self) -> EventLoopProxy { @@ -316,24 +264,83 @@ fn main_thread_id() -> DWORD { unsafe { MAIN_THREAD_ID } } -unsafe fn wait_until_time_or_msg(wait_until: Instant) { - let now = Instant::now(); - if now < wait_until { - // MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract 1 millisecond - // from the requested time and spinlock for the remainder to compensate for that. - let resume_reason = winuser::MsgWaitForMultipleObjectsEx( +fn get_wait_thread_id() -> DWORD { + unsafe { + let mut msg = mem::zeroed(); + let result = winuser::GetMessageW( + &mut msg, + -1 as _, + *SEND_WAIT_THREAD_ID_MSG_ID, + *SEND_WAIT_THREAD_ID_MSG_ID, + ); + assert_eq!( + msg.message, *SEND_WAIT_THREAD_ID_MSG_ID, + "this shouldn't be possible. please open an issue with Winit. error code: {}", + result + ); + msg.lParam as DWORD + } +} + +fn wait_thread(parent_thread_id: DWORD, msg_window_id: HWND) { + unsafe { + let mut msg: winuser::MSG; + + let cur_thread_id = processthreadsapi::GetCurrentThreadId(); + winuser::PostThreadMessageW( + parent_thread_id, + *SEND_WAIT_THREAD_ID_MSG_ID, 0, - ptr::null(), - dur2timeout(wait_until - now).saturating_sub(1), - winuser::QS_ALLEVENTS, - winuser::MWMO_INPUTAVAILABLE, + cur_thread_id as LPARAM, ); - if resume_reason == winerror::WAIT_TIMEOUT { - let mut msg = mem::zeroed(); - while Instant::now() < wait_until { - if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { - break; + let mut wait_until_opt = None; + 'main: loop { + // Zeroing out the message ensures that the `WaitUntilInstantBox` doesn't get + // double-freed if `MsgWaitForMultipleObjectsEx` returns early and there aren't + // additional messages to process. + msg = mem::zeroed(); + + if wait_until_opt.is_some() { + if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, winuser::PM_REMOVE) { + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + } + } else { + if 0 == winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) { + break 'main; + } else { + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + } + } + + if msg.message == *WAIT_UNTIL_MSG_ID { + wait_until_opt = Some(*WaitUntilInstantBox::from_raw(msg.lParam as *mut _)); + } else if msg.message == *CANCEL_WAIT_UNTIL_MSG_ID { + wait_until_opt = None; + } + + if let Some(wait_until) = wait_until_opt { + let now = Instant::now(); + if now < wait_until { + // MsgWaitForMultipleObjects tends to overshoot just a little bit. We subtract + // 1 millisecond from the requested time and spinlock for the remainder to + // compensate for that. + let resume_reason = winuser::MsgWaitForMultipleObjectsEx( + 0, + ptr::null(), + dur2timeout(wait_until - now).saturating_sub(1), + winuser::QS_ALLEVENTS, + winuser::MWMO_INPUTAVAILABLE, + ); + if resume_reason == winerror::WAIT_TIMEOUT { + winuser::PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0); + wait_until_opt = None; + } + } else { + winuser::PostMessageW(msg_window_id, *PROCESS_NEW_EVENTS_MSG_ID, 0, 0); + wait_until_opt = None; } } } @@ -461,6 +468,8 @@ impl EventLoopProxy { } } +type WaitUntilInstantBox = Box; + lazy_static! { // Message sent by the `EventLoopProxy` when we want to wake up the thread. // WPARAM and LPARAM are unused. @@ -477,6 +486,29 @@ lazy_static! { winuser::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as *const i8) } }; + static ref PROCESS_NEW_EVENTS_MSG_ID: u32 = { + unsafe { + winuser::RegisterWindowMessageA("Winit::ProcessNewEvents\0".as_ptr() as *const i8) + } + }; + /// lparam is the wait thread's message id. + static ref SEND_WAIT_THREAD_ID_MSG_ID: u32 = { + unsafe { + winuser::RegisterWindowMessageA("Winit::SendWaitThreadId\0".as_ptr() as *const i8) + } + }; + /// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should + /// be sent. + static ref WAIT_UNTIL_MSG_ID: u32 = { + unsafe { + winuser::RegisterWindowMessageA("Winit::WaitUntil\0".as_ptr() as *const i8) + } + }; + static ref CANCEL_WAIT_UNTIL_MSG_ID: u32 = { + unsafe { + winuser::RegisterWindowMessageA("Winit::CancelWaitUntil\0".as_ptr() as *const i8) + } + }; // Message sent by a `Window` when it wants to be destroyed by the main thread. // WPARAM and LPARAM are unused. pub static ref DESTROY_MSG_ID: u32 = { @@ -519,7 +551,7 @@ lazy_static! { }; } -fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> (HWND, Sender) { +fn create_event_target_window() -> HWND { unsafe { let window = winuser::CreateWindowExW( winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED, @@ -543,7 +575,15 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> // the LAYERED style. (winuser::WS_VISIBLE | winuser::WS_POPUP) as _, ); + window + } +} +fn subclass_event_target_window( + window: HWND, + event_loop_runner: EventLoopRunnerShared, +) -> Sender { + unsafe { let (tx, rx) = mpsc::channel(); let subclass_input = ThreadMsgTargetSubclassInput { @@ -559,7 +599,7 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> ); assert_eq!(subclass_result, 1); - (window, tx) + tx } } @@ -582,6 +622,7 @@ unsafe fn release_mouse(window_state: &mut WindowState) { const WINDOW_SUBCLASS_ID: UINT_PTR = 0; const THREAD_EVENT_TARGET_SUBCLASS_ID: UINT_PTR = 1; pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) { + subclass_input.event_loop_runner.register_window(window); let input_ptr = Box::into_raw(Box::new(subclass_input)); let subclass_result = unsafe { commctrl::SetWindowSubclass( @@ -601,6 +642,68 @@ fn normalize_pointer_pressure(pressure: u32) -> Option { } } +/// Flush redraw events for Winit's windows. +/// +/// Winit's API guarantees that all redraw events will be clustered together and dispatched all at +/// once, but the standard Windows message loop doesn't always exhibit that behavior. If multiple +/// windows have had redraws scheduled, but an input event is pushed to the message queue between +/// the `WM_PAINT` call for the first window and the `WM_PAINT` call for the second window, Windows +/// will dispatch the input event immediately instead of flushing all the redraw events. This +/// function explicitly pulls all of Winit's redraw events out of the event queue so that they +/// always all get processed in one fell swoop. +/// +/// Returns `true` if this invocation flushed all the redraw events. If this function is re-entrant, +/// it won't flush the redraw events and will return `false`. +#[must_use] +unsafe fn flush_paint_messages( + except: Option, + runner: &EventLoopRunner, +) -> bool { + if !runner.redrawing() { + runner.main_events_cleared(); + let mut msg = mem::zeroed(); + runner.owned_windows(|redraw_window| { + if Some(redraw_window) == except { + return; + } + + if 0 == winuser::PeekMessageW( + &mut msg, + redraw_window, + winuser::WM_PAINT, + winuser::WM_PAINT, + winuser::PM_REMOVE | winuser::PM_QS_PAINT, + ) { + return; + } + + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + }); + true + } else { + false + } +} + +unsafe fn process_control_flow(runner: &EventLoopRunner) { + match runner.control_flow() { + ControlFlow::Poll => { + winuser::PostMessageW(runner.thread_msg_target(), *PROCESS_NEW_EVENTS_MSG_ID, 0, 0); + } + ControlFlow::Wait => (), + ControlFlow::WaitUntil(until) => { + winuser::PostThreadMessageW( + runner.wait_thread_id(), + *WAIT_UNTIL_MSG_ID, + 0, + Box::into_raw(WaitUntilInstantBox::new(until)) as LPARAM, + ); + } + ControlFlow::Exit => (), + } +} + /// Emit a `ModifiersChanged` event whenever modifiers have changed. fn update_modifiers(window: HWND, subclass_input: &SubclassInput) { use crate::event::WindowEvent::ModifiersChanged; @@ -639,20 +742,37 @@ unsafe extern "system" fn public_window_callback( ) -> LRESULT { let subclass_input = &*(subclass_input_ptr as *const SubclassInput); - match msg { + winuser::RedrawWindow( + subclass_input.event_loop_runner.thread_msg_target(), + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + + // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing + // the closure to catch_unwind directly so that the match body indendation wouldn't change and + // the git blame and history would be preserved. + let callback = || match msg { winuser::WM_ENTERSIZEMOVE => { - subclass_input.event_loop_runner.set_modal_loop(true); + subclass_input + .window_state + .lock() + .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); 0 } + winuser::WM_EXITSIZEMOVE => { - subclass_input.event_loop_runner.set_modal_loop(false); + subclass_input + .window_state + .lock() + .set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE)); 0 } + winuser::WM_NCCREATE => { enable_non_client_dpi_scaling(window); commctrl::DefSubclassProc(window, msg, wparam, lparam) } - winuser::WM_NCLBUTTONDOWN => { if wparam == winuser::HTCAPTION as _ { winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0); @@ -676,6 +796,7 @@ unsafe extern "system" fn public_window_callback( window_id: RootWindowId(WindowId(window)), event: Destroyed, }); + subclass_input.event_loop_runner.remove_window(window); drop(subclass_input); Box::from_raw(subclass_input_ptr as *mut SubclassInput); @@ -683,7 +804,25 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_PAINT => { - subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); + if subclass_input.event_loop_runner.should_buffer() { + // this branch can happen in response to `UpdateWindow`, if win32 decides to + // redraw the window outside the normal flow of the event loop. + winuser::RedrawWindow( + window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } else { + let managing_redraw = + flush_paint_messages(Some(window), &subclass_input.event_loop_runner); + subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); + if managing_redraw { + subclass_input.event_loop_runner.redraw_events_cleared(); + process_control_flow(&subclass_input.event_loop_runner); + } + } + commctrl::DefSubclassProc(window, msg, wparam, lparam) } @@ -1583,11 +1722,19 @@ unsafe extern "system" fn public_window_callback( }, }); - // Unset maximized if we're changing the window's size. - if new_physical_inner_size != old_physical_inner_size { - WindowState::set_window_flags(subclass_input.window_state.lock(), window, |f| { - f.set(WindowFlags::MAXIMIZED, false) - }); + let dragging_window: bool; + + { + let window_state = subclass_input.window_state.lock(); + dragging_window = window_state + .window_flags() + .contains(WindowFlags::MARKER_IN_SIZE_MOVE); + // Unset maximized if we're changing the window's size. + if new_physical_inner_size != old_physical_inner_size { + WindowState::set_window_flags(window_state, window, |f| { + f.set(WindowFlags::MAXIMIZED, false) + }); + } } let new_outer_rect: RECT; @@ -1612,9 +1759,8 @@ unsafe extern "system" fn public_window_callback( ) .unwrap_or(conservative_rect); - // If we're not dragging the window, offset the window so that the cursor's + // If we're dragging the window, offset the window so that the cursor's // relative horizontal position in the title bar is preserved. - let dragging_window = subclass_input.event_loop_runner.in_modal_loop(); if dragging_window { let bias = { let cursor_pos = { @@ -1742,7 +1888,12 @@ unsafe extern "system" fn public_window_callback( commctrl::DefSubclassProc(window, msg, wparam, lparam) } } - } + }; + + subclass_input + .event_loop_runner + .catch_unwind(callback) + .unwrap_or(-1) } unsafe extern "system" fn thread_event_target_callback( @@ -1754,7 +1905,21 @@ unsafe extern "system" fn thread_event_target_callback( subclass_input_ptr: DWORD_PTR, ) -> LRESULT { let subclass_input = &mut *(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput); - match msg { + let runner = subclass_input.event_loop_runner.clone(); + + if msg != winuser::WM_PAINT { + winuser::RedrawWindow( + window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } + + // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing + // the closure to catch_unwind directly so that the match body indendation wouldn't change and + // the git blame and history would be preserved. + let callback = || match msg { winuser::WM_DESTROY => { Box::from_raw(subclass_input); drop(subclass_input); @@ -1764,52 +1929,20 @@ unsafe extern "system" fn thread_event_target_callback( // when the event queue has been emptied. See `process_event` for more details. winuser::WM_PAINT => { winuser::ValidateRect(window, ptr::null()); - let queue_call_again = || { - winuser::RedrawWindow( - window, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); - }; - let in_modal_loop = subclass_input.event_loop_runner.in_modal_loop(); - if in_modal_loop { - let runner = &subclass_input.event_loop_runner; - runner.main_events_cleared(); - // Drain eventual WM_PAINT messages sent if user called request_redraw() - // during handling of MainEventsCleared. - let mut msg = mem::zeroed(); - loop { - if 0 == winuser::PeekMessageW( - &mut msg, - ptr::null_mut(), - winuser::WM_PAINT, - winuser::WM_PAINT, - winuser::PM_QS_PAINT | winuser::PM_REMOVE, - ) { - break; - } - - if msg.hwnd != window { - winuser::TranslateMessage(&mut msg); - winuser::DispatchMessageW(&mut msg); - } - } - runner.redraw_events_cleared(); - match runner.control_flow() { - // Waiting is handled by the modal loop. - ControlFlow::Exit | ControlFlow::Wait => runner.new_events(), - ControlFlow::WaitUntil(resume_time) => { - wait_until_time_or_msg(resume_time); - runner.new_events(); - queue_call_again(); - } - ControlFlow::Poll => { - runner.new_events(); - queue_call_again(); - } - } + // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw + // events, `handling_events` will return false and we won't emit a second + // `RedrawEventsCleared` event. + if subclass_input.event_loop_runner.handling_events() { + // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` + // doesn't call WM_PAINT for the thread event target (i.e. this window). + assert!(flush_paint_messages( + None, + &subclass_input.event_loop_runner + )); + subclass_input.event_loop_runner.redraw_events_cleared(); + process_control_flow(&subclass_input.event_loop_runner); } + 0 } @@ -1940,6 +2073,49 @@ unsafe extern "system" fn thread_event_target_callback( function(); 0 } + _ if msg == *PROCESS_NEW_EVENTS_MSG_ID => { + winuser::PostThreadMessageW( + subclass_input.event_loop_runner.wait_thread_id(), + *CANCEL_WAIT_UNTIL_MSG_ID, + 0, + 0, + ); + + // if the control_flow is WaitUntil, make sure the given moment has actually passed + // before emitting NewEvents + if let ControlFlow::WaitUntil(wait_until) = + subclass_input.event_loop_runner.control_flow() + { + let mut msg = mem::zeroed(); + while Instant::now() < wait_until { + if 0 != winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0) { + // This works around a "feature" in PeekMessageW. If the message PeekMessageW + // gets is a WM_PAINT message that had RDW_INTERNALPAINT set (i.e. doesn't + // have an update region), PeekMessageW will remove that window from the + // redraw queue even though we told it not to remove messages from the + // queue. We fix it by re-dispatching an internal paint message to that + // window. + if msg.message == winuser::WM_PAINT { + let mut rect = mem::zeroed(); + if 0 == winuser::GetUpdateRect(msg.hwnd, &mut rect, 0) { + winuser::RedrawWindow( + msg.hwnd, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } + } + + break; + } + } + } + subclass_input.event_loop_runner.poll(); + 0 + } _ => commctrl::DefSubclassProc(window, msg, wparam, lparam), - } + }; + + runner.catch_unwind(callback).unwrap_or(-1) } diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index e5c062b035..258a40c08c 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -1,475 +1,410 @@ -use std::{any::Any, cell::RefCell, collections::VecDeque, mem, panic, ptr, rc::Rc, time::Instant}; +use std::{ + any::Any, + cell::{Cell, RefCell}, + collections::{HashSet, VecDeque}, + mem, panic, ptr, + rc::Rc, + time::Instant, +}; -use winapi::{shared::windef::HWND, um::winuser}; +use winapi::{ + shared::{minwindef::DWORD, windef::HWND}, + um::winuser, +}; use crate::{ dpi::PhysicalSize, event::{Event, StartCause, WindowEvent}, event_loop::ControlFlow, - platform_impl::platform::event_loop::{util, EventLoop}, + platform_impl::platform::util, window::WindowId, }; -pub(crate) type EventLoopRunnerShared = Rc>; -pub(crate) struct ELRShared { - runner: RefCell>>, - buffer: RefCell>>, -} +pub(crate) type EventLoopRunnerShared = Rc>; +pub(crate) struct EventLoopRunner { + // The event loop's win32 handles + thread_msg_target: HWND, + wait_thread_id: DWORD, + + control_flow: Cell, + runner_state: Cell, + last_events_cleared: Cell, + + event_handler: Cell, &mut ControlFlow)>>>, + event_buffer: RefCell>>, + + owned_windows: Cell>, -struct EventLoopRunner { - control_flow: ControlFlow, - runner_state: RunnerState, - modal_redraw_window: HWND, - in_modal_loop: bool, - event_handler: Box, &mut ControlFlow)>, - panic_error: Option, + panic_error: Cell>, } pub type PanicError = Box; -pub enum BufferedEvent { +/// See `move_state_to` function for details on how the state loop works. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum RunnerState { + /// The event loop has just been created, and an `Init` event must be sent. + Uninitialized, + /// The event loop is idling. + Idle, + /// The event loop is handling the OS's events and sending them to the user's callback. + /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. + HandlingMainEvents, + /// The event loop is handling the redraw events and sending them to the user's callback. + /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. + HandlingRedrawEvents, +} + +enum BufferedEvent { Event(Event<'static, T>), ScaleFactorChanged(WindowId, f64, PhysicalSize), } -impl BufferedEvent { - pub fn from_event(event: Event<'_, T>) -> BufferedEvent { - match event { - Event::WindowEvent { - event: - WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size, - }, - window_id, - } => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size), - event => BufferedEvent::Event(event.to_static().unwrap()), +impl EventLoopRunner { + pub(crate) fn new(thread_msg_target: HWND, wait_thread_id: DWORD) -> EventLoopRunner { + EventLoopRunner { + thread_msg_target, + wait_thread_id, + runner_state: Cell::new(RunnerState::Uninitialized), + control_flow: Cell::new(ControlFlow::Poll), + panic_error: Cell::new(None), + last_events_cleared: Cell::new(Instant::now()), + event_handler: Cell::new(None), + event_buffer: RefCell::new(VecDeque::new()), + owned_windows: Cell::new(HashSet::new()), } } - pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) { - match self { - Self::Event(event) => dispatch(event), - Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => { - dispatch(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size: &mut new_inner_size, - }, - }); - util::set_inner_size_physical( - (window_id.0).0, - new_inner_size.width as _, - new_inner_size.height as _, - ); - } - } + pub(crate) unsafe fn set_event_handler(&self, f: F) + where + F: FnMut(Event<'_, T>, &mut ControlFlow), + { + let old_event_handler = self.event_handler.replace(mem::transmute::< + Option, &mut ControlFlow)>>, + Option, &mut ControlFlow)>>, + >(Some(Box::new(f)))); + assert!(old_event_handler.is_none()); + } + + pub(crate) fn reset_runner(&self) { + let EventLoopRunner { + thread_msg_target: _, + wait_thread_id: _, + runner_state, + panic_error, + control_flow, + last_events_cleared: _, + event_handler, + event_buffer: _, + owned_windows: _, + } = self; + runner_state.set(RunnerState::Uninitialized); + panic_error.set(None); + control_flow.set(ControlFlow::Poll); + event_handler.set(None); } } -impl ELRShared { - pub(crate) fn new() -> ELRShared { - ELRShared { - runner: RefCell::new(None), - buffer: RefCell::new(VecDeque::new()), - } +/// State retrieval functions. +impl EventLoopRunner { + pub fn thread_msg_target(&self) -> HWND { + self.thread_msg_target } - pub(crate) unsafe fn set_runner(&self, event_loop: &EventLoop, f: F) - where - F: FnMut(Event<'_, T>, &mut ControlFlow), - { - let mut runner = EventLoopRunner::new(event_loop, f); - { - let mut runner_ref = self.runner.borrow_mut(); - // Dispatch any events that were buffered during the creation of the window - self.dispatch_buffered_events(&mut runner); - *runner_ref = Some(runner); - } + pub fn wait_thread_id(&self) -> DWORD { + self.wait_thread_id } - pub(crate) fn destroy_runner(&self) { - *self.runner.borrow_mut() = None; + pub fn redrawing(&self) -> bool { + self.runner_state.get() == RunnerState::HandlingRedrawEvents } - pub(crate) fn new_events(&self) { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.new_events(); - // Dispatch any events that were buffered during the call `new_events` - self.dispatch_buffered_events(runner); + pub fn take_panic_error(&self) -> Result<(), PanicError> { + match self.panic_error.take() { + Some(err) => Err(err), + None => Ok(()), } } - pub(crate) fn send_event(&self, event: Event<'_, T>) { - if let Err(event) = self.send_event_unbuffered(event) { - // If the runner is already borrowed, we're in the middle of an event loop invocation. - // Add the event to a buffer to be processed later. - if let Event::RedrawRequested(_) = event { - panic!("buffering RedrawRequested event"); - } - self.buffer - .borrow_mut() - .push_back(BufferedEvent::from_event(event)); - } + pub fn control_flow(&self) -> ControlFlow { + self.control_flow.get() } - fn send_event_unbuffered<'e>(&self, event: Event<'e, T>) -> Result<(), Event<'e, T>> { - if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { - if let Some(ref mut runner) = *runner_ref { - runner.process_event(event); - // Dispatch any events that were buffered during the call to `process_event`. - self.dispatch_buffered_events(runner); - return Ok(()); - } - } - Err(event) + pub fn handling_events(&self) -> bool { + self.runner_state.get() != RunnerState::Idle } - fn dispatch_buffered_events(&self, runner: &mut EventLoopRunner) { - // We do this instead of using a `while let` loop because if we use a `while let` - // loop the reference returned `borrow_mut()` doesn't get dropped until the end - // of the loop's body and attempts to add events to the event buffer while in - // `process_event` will fail. - loop { - let buffered_event_opt = self.buffer.borrow_mut().pop_front(); - match buffered_event_opt { - Some(e) => e.dispatch_event(|e| runner.process_event(e)), - None => break, - } - } + pub fn should_buffer(&self) -> bool { + let handler = self.event_handler.take(); + let should_buffer = handler.is_none(); + self.event_handler.set(handler); + should_buffer } +} - pub(crate) fn main_events_cleared(&self) { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.main_events_cleared(); - if !self.buffer.borrow().is_empty() { - warn!("Buffered events while dispatching MainEventsCleared"); +/// Misc. functions +impl EventLoopRunner { + pub fn catch_unwind(&self, f: impl FnOnce() -> R) -> Option { + let panic_error = self.panic_error.take(); + if panic_error.is_none() { + let result = panic::catch_unwind(panic::AssertUnwindSafe(f)); + + // Check to see if the panic error was set in a re-entrant call to catch_unwind inside + // of `f`. If it was, that error takes priority. If it wasn't, check if our call to + // catch_unwind caught any panics and set panic_error appropriately. + match self.panic_error.take() { + None => match result { + Ok(r) => Some(r), + Err(e) => { + self.panic_error.set(Some(e)); + None + } + }, + Some(e) => { + self.panic_error.set(Some(e)); + None + } } + } else { + self.panic_error.set(panic_error); + None } } + pub fn register_window(&self, window: HWND) { + let mut owned_windows = self.owned_windows.take(); + owned_windows.insert(window); + self.owned_windows.set(owned_windows); + } - pub(crate) fn redraw_events_cleared(&self) { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.redraw_events_cleared(); - if !self.buffer.borrow().is_empty() { - warn!("Buffered events while dispatching RedrawEventsCleared"); - } - } + pub fn remove_window(&self, window: HWND) { + let mut owned_windows = self.owned_windows.take(); + owned_windows.remove(&window); + self.owned_windows.set(owned_windows); } - pub(crate) fn destroy_loop(&self) { - if let Ok(mut runner_ref) = self.runner.try_borrow_mut() { - if let Some(ref mut runner) = *runner_ref { - runner.call_event_handler(Event::LoopDestroyed); - } + pub fn owned_windows(&self, mut f: impl FnMut(HWND)) { + let mut owned_windows = self.owned_windows.take(); + for hwnd in &owned_windows { + f(*hwnd); } + let new_owned_windows = self.owned_windows.take(); + owned_windows.extend(&new_owned_windows); + self.owned_windows.set(owned_windows); } +} - pub(crate) fn take_panic_error(&self) -> Result<(), PanicError> { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.take_panic_error() - } else { - Ok(()) - } +/// Event dispatch functions. +impl EventLoopRunner { + pub(crate) unsafe fn poll(&self) { + self.move_state_to(RunnerState::HandlingMainEvents); } - pub(crate) fn set_modal_loop(&self, in_modal_loop: bool) { - let mut runner_ref = self.runner.borrow_mut(); - if let Some(ref mut runner) = *runner_ref { - runner.in_modal_loop = in_modal_loop; - if in_modal_loop { - // jumpstart the modal loop - unsafe { - winuser::RedrawWindow( - runner.modal_redraw_window, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); - } + pub(crate) unsafe fn send_event(&self, event: Event<'_, T>) { + if let Event::RedrawRequested(_) = event { + if self.runner_state.get() != RunnerState::HandlingRedrawEvents { + warn!("RedrawRequested dispatched without explicit MainEventsCleared"); + self.move_state_to(RunnerState::HandlingRedrawEvents); + } + self.call_event_handler(event); + } else { + if self.should_buffer() { + // If the runner is already borrowed, we're in the middle of an event loop invocation. Add + // the event to a buffer to be processed later. + self.event_buffer + .borrow_mut() + .push_back(BufferedEvent::from_event(event)) + } else { + self.move_state_to(RunnerState::HandlingMainEvents); + self.call_event_handler(event); + self.dispatch_buffered_events(); } } } - pub(crate) fn in_modal_loop(&self) -> bool { - let runner = self.runner.borrow(); - if let Some(ref runner) = *runner { - runner.in_modal_loop - } else { - false - } + pub(crate) unsafe fn main_events_cleared(&self) { + self.move_state_to(RunnerState::HandlingRedrawEvents); } - pub fn control_flow(&self) -> ControlFlow { - let runner_ref = self.runner.borrow(); - if let Some(ref runner) = *runner_ref { - runner.control_flow - } else { - ControlFlow::Exit - } + pub(crate) unsafe fn redraw_events_cleared(&self) { + self.move_state_to(RunnerState::Idle); } -} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum RunnerState { - /// The event loop has just been created, and an `Init` event must be sent. - New, - /// The event loop is idling, and began idling at the given instant. - Idle(Instant), - /// The event loop has received a signal from the OS that the loop may resume, but no winit - /// events have been generated yet. We're waiting for an event to be processed or the events - /// to be marked as cleared to send `NewEvents`, depending on the current `ControlFlow`. - DeferredNewEvents(Instant), - /// The event loop is handling the OS's events and sending them to the user's callback. - /// `NewEvents` has been sent, and `MainEventsCleared` hasn't. - HandlingEvents, - /// The event loop is handling the redraw events and sending them to the user's callback. - /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. - HandlingRedraw, -} + pub(crate) unsafe fn call_event_handler(&self, event: Event<'_, T>) { + self.catch_unwind(|| { + let mut control_flow = self.control_flow.take(); + let mut event_handler = self.event_handler.take() + .expect("either event handler is re-entrant (likely), or no event handler is registered (very unlikely)"); -impl EventLoopRunner { - unsafe fn new(event_loop: &EventLoop, f: F) -> EventLoopRunner - where - F: FnMut(Event<'_, T>, &mut ControlFlow), - { - EventLoopRunner { - control_flow: ControlFlow::default(), - runner_state: RunnerState::New, - in_modal_loop: false, - modal_redraw_window: event_loop.window_target.p.thread_msg_target, - event_handler: mem::transmute::< - Box, &mut ControlFlow)>, - Box, &mut ControlFlow)>, - >(Box::new(f)), - panic_error: None, - } + if control_flow != ControlFlow::Exit { + event_handler(event, &mut control_flow); + } else { + event_handler(event, &mut ControlFlow::Exit); + } + + assert!(self.event_handler.replace(Some(event_handler)).is_none()); + self.control_flow.set(control_flow); + }); } - fn take_panic_error(&mut self) -> Result<(), PanicError> { - match self.panic_error.take() { - Some(err) => Err(err), - None => Ok(()), + unsafe fn dispatch_buffered_events(&self) { + loop { + // We do this instead of using a `while let` loop because if we use a `while let` + // loop the reference returned `borrow_mut()` doesn't get dropped until the end + // of the loop's body and attempts to add events to the event buffer while in + // `process_event` will fail. + let buffered_event_opt = self.event_buffer.borrow_mut().pop_front(); + match buffered_event_opt { + Some(e) => e.dispatch_event(|e| self.call_event_handler(e)), + None => break, + } } } - fn new_events(&mut self) { - self.runner_state = match self.runner_state { - // If we're already handling events or have deferred `NewEvents`, we don't need to do - // do any processing. - RunnerState::HandlingEvents - | RunnerState::HandlingRedraw - | RunnerState::DeferredNewEvents(..) => self.runner_state, - - // Send the `Init` `NewEvents` and immediately move into event processing. - RunnerState::New => { - self.call_event_handler(Event::NewEvents(StartCause::Init)); - RunnerState::HandlingEvents + /// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared`) as + /// necessary to bring the internal `RunnerState` to the new runner state. + /// + /// The state transitions are defined as follows: + /// + /// ```text + /// Uninitialized + /// | + /// V + /// HandlingMainEvents + /// ^ | + /// | V + /// Idle <--- HandlingRedrawEvents + /// ``` + /// + /// Attempting to transition back to `Uninitialized` will result in a panic. Transitioning to + /// the current state is a no-op. Even if the `new_runner_state` isn't the immediate next state + /// in the runner state machine (e.g. `self.runner_state == HandlingMainEvents` and + /// `new_runner_state == Idle`), the intermediate state transitions will still be executed. + unsafe fn move_state_to(&self, new_runner_state: RunnerState) { + use RunnerState::{HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized}; + + match ( + self.runner_state.replace(new_runner_state), + new_runner_state, + ) { + (Uninitialized, Uninitialized) + | (Idle, Idle) + | (HandlingMainEvents, HandlingMainEvents) + | (HandlingRedrawEvents, HandlingRedrawEvents) => (), + + // State transitions that initialize the event loop. + (Uninitialized, HandlingMainEvents) => { + self.call_new_events(true); } - - // When `NewEvents` gets sent after an idle depends on the control flow... - // Some `NewEvents` are deferred because not all Windows messages trigger an event_loop event. - // So we defer the `NewEvents` to when we actually process an event. - RunnerState::Idle(wait_start) => { - match self.control_flow { - // If we're polling, send `NewEvents` and immediately move into event processing. - ControlFlow::Poll => { - self.call_event_handler(Event::NewEvents(StartCause::Poll)); - RunnerState::HandlingEvents - }, - // If the user was waiting until a specific time, the `NewEvents` call gets sent - // at varying times depending on the current time. - ControlFlow::WaitUntil(resume_time) => { - match Instant::now() >= resume_time { - // If the current time is later than the requested resume time, we can tell the - // user that the resume time has been reached with `NewEvents` and immdiately move - // into event processing. - true => { - self.call_event_handler(Event::NewEvents(StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - })); - RunnerState::HandlingEvents - }, - // However, if the current time is EARLIER than the requested resume time, we - // don't want to send the `WaitCancelled` event until we know an event is being - // sent. Defer. - false => RunnerState::DeferredNewEvents(wait_start) - } - }, - // If we're waiting, `NewEvents` doesn't get sent until winit gets an event, so - // we defer. - ControlFlow::Wait | - // `Exit` shouldn't really ever get sent here, but if it does do something somewhat sane. - ControlFlow::Exit => RunnerState::DeferredNewEvents(wait_start), - } + (Uninitialized, HandlingRedrawEvents) => { + self.call_new_events(true); + self.call_event_handler(Event::MainEventsCleared); } - }; - } - - fn process_event(&mut self, event: Event<'_, T>) { - // If we're in the modal loop, we need to have some mechanism for finding when the event - // queue has been cleared so we can call `events_cleared`. Windows doesn't give any utilities - // for doing this, but it DOES guarantee that WM_PAINT will only occur after input events have - // been processed. So, we send WM_PAINT to a dummy window which calls `events_cleared` when - // the events queue has been emptied. - if self.in_modal_loop { - unsafe { - winuser::RedrawWindow( - self.modal_redraw_window, - ptr::null(), - ptr::null_mut(), - winuser::RDW_INTERNALPAINT, - ); + (Uninitialized, Idle) => { + self.call_new_events(true); + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); } - } + (_, Uninitialized) => panic!("cannot move state to Uninitialized"), - // If new event processing has to be done (i.e. call NewEvents or defer), do it. If we're - // already in processing nothing happens with this call. - self.new_events(); - - // Now that an event has been received, we have to send any `NewEvents` calls that were - // deferred. - if let RunnerState::DeferredNewEvents(wait_start) = self.runner_state { - match self.control_flow { - ControlFlow::Exit | ControlFlow::Wait => { - self.call_event_handler(Event::NewEvents(StartCause::WaitCancelled { - start: wait_start, - requested_resume: None, - })) - } - ControlFlow::WaitUntil(resume_time) => { - let start_cause = match Instant::now() >= resume_time { - // If the current time is later than the requested resume time, the resume time - // has been reached. - true => StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - }, - // Otherwise, the requested resume time HASN'T been reached and we send a WaitCancelled. - false => StartCause::WaitCancelled { - start: wait_start, - requested_resume: Some(resume_time), - }, - }; - self.call_event_handler(Event::NewEvents(start_cause)); - } - // This can be reached if the control flow is changed to poll during a `RedrawRequested` - // that was sent after `MainEventsCleared`. - ControlFlow::Poll => self.call_event_handler(Event::NewEvents(StartCause::Poll)), + // State transitions that start the event handling process. + (Idle, HandlingMainEvents) => { + self.call_new_events(false); + } + (Idle, HandlingRedrawEvents) => { + self.call_new_events(false); + self.call_event_handler(Event::MainEventsCleared); } - self.runner_state = RunnerState::HandlingEvents; - } - match (self.runner_state, &event) { - (RunnerState::HandlingEvents, Event::RedrawRequested(window_id)) => { + (HandlingMainEvents, HandlingRedrawEvents) => { self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; - self.call_event_handler(Event::RedrawRequested(*window_id)); } - (RunnerState::HandlingRedraw, Event::RedrawRequested(window_id)) => { - self.call_event_handler(Event::RedrawRequested(*window_id)); + (HandlingMainEvents, Idle) => { + warn!("RedrawEventsCleared emitted without explicit MainEventsCleared"); + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); } - (RunnerState::HandlingRedraw, _) => { - warn!( - "non-redraw event in redraw phase: {:?}", - event.map_nonuser_event::<()>().ok() - ); + + (HandlingRedrawEvents, Idle) => { + self.call_redraw_events_cleared(); } - (_, _) => { - self.runner_state = RunnerState::HandlingEvents; - self.call_event_handler(event); + (HandlingRedrawEvents, HandlingMainEvents) => { + warn!("NewEvents emitted without explicit RedrawEventsCleared"); + self.call_redraw_events_cleared(); + self.call_new_events(false); } } } - fn main_events_cleared(&mut self) { - match self.runner_state { - // If we were handling events, send the MainEventsCleared message. - RunnerState::HandlingEvents => { - self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; - } - - // We already cleared the main events, we don't have to do anything. - // This happens when process_events() processed a RedrawRequested event. - RunnerState::HandlingRedraw => {} - - // If we *weren't* handling events, we don't have to do anything. - RunnerState::New | RunnerState::Idle(..) => (), - - // Some control flows require a NewEvents call even if no events were received. This - // branch handles those. - RunnerState::DeferredNewEvents(wait_start) => { - match self.control_flow { - // If we had deferred a Poll, send the Poll NewEvents and MainEventsCleared. - ControlFlow::Poll => { - self.call_event_handler(Event::NewEvents(StartCause::Poll)); - self.runner_state = RunnerState::HandlingEvents; - self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; + unsafe fn call_new_events(&self, init: bool) { + let start_cause = match (init, self.control_flow()) { + (true, _) => StartCause::Init, + (false, ControlFlow::Poll) => StartCause::Poll, + (false, ControlFlow::Exit) | (false, ControlFlow::Wait) => StartCause::WaitCancelled { + requested_resume: None, + start: self.last_events_cleared.get(), + }, + (false, ControlFlow::WaitUntil(requested_resume)) => { + if Instant::now() < requested_resume { + StartCause::WaitCancelled { + requested_resume: Some(requested_resume), + start: self.last_events_cleared.get(), } - // If we had deferred a WaitUntil and the resume time has since been reached, - // send the resume notification and MainEventsCleared event. - ControlFlow::WaitUntil(resume_time) => { - if Instant::now() >= resume_time { - self.call_event_handler(Event::NewEvents( - StartCause::ResumeTimeReached { - start: wait_start, - requested_resume: resume_time, - }, - )); - self.runner_state = RunnerState::HandlingEvents; - self.call_event_handler(Event::MainEventsCleared); - self.runner_state = RunnerState::HandlingRedraw; - } + } else { + StartCause::ResumeTimeReached { + requested_resume, + start: self.last_events_cleared.get(), } - // If we deferred a wait and no events were received, the user doesn't have to - // get an event. - ControlFlow::Wait | ControlFlow::Exit => (), } } - } + }; + self.call_event_handler(Event::NewEvents(start_cause)); + self.dispatch_buffered_events(); + winuser::RedrawWindow( + self.thread_msg_target, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); } - fn redraw_events_cleared(&mut self) { - match self.runner_state { - // If we were handling redraws, send the RedrawEventsCleared message. - RunnerState::HandlingRedraw => { - self.call_event_handler(Event::RedrawEventsCleared); - self.runner_state = RunnerState::Idle(Instant::now()); - } - // No event was processed, we don't have to do anything. - RunnerState::DeferredNewEvents(_) => (), - // Should not happen. - _ => warn!( - "unexpected state in redraw_events_cleared: {:?}", - self.runner_state - ), + unsafe fn call_redraw_events_cleared(&self) { + self.call_event_handler(Event::RedrawEventsCleared); + self.last_events_cleared.set(Instant::now()); + } +} + +impl BufferedEvent { + pub fn from_event(event: Event<'_, T>) -> BufferedEvent { + match event { + Event::WindowEvent { + event: + WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + }, + window_id, + } => BufferedEvent::ScaleFactorChanged(window_id, scale_factor, *new_inner_size), + event => BufferedEvent::Event(event.to_static().unwrap()), } } - fn call_event_handler(&mut self, event: Event<'_, T>) { - if self.panic_error.is_none() { - let EventLoopRunner { - ref mut panic_error, - ref mut event_handler, - ref mut control_flow, - .. - } = self; - *panic_error = panic::catch_unwind(panic::AssertUnwindSafe(|| { - if *control_flow != ControlFlow::Exit { - (*event_handler)(event, control_flow); - } else { - (*event_handler)(event, &mut ControlFlow::Exit); - } - })) - .err(); + pub fn dispatch_event(self, dispatch: impl FnOnce(Event<'_, T>)) { + match self { + Self::Event(event) => dispatch(event), + Self::ScaleFactorChanged(window_id, scale_factor, mut new_inner_size) => { + dispatch(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); + util::set_inner_size_physical( + (window_id.0).0, + new_inner_size.width as _, + new_inner_size.height as _, + ); + } } } } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 11794821ad..124090813c 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -80,7 +80,9 @@ bitflags! { /// window's state to match our stored state. This controls whether to accept those changes. const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10; - const MINIMIZED = 1 << 11; + const MARKER_IN_SIZE_MOVE = 1 << 11; + + const MINIMIZED = 1 << 12; const FULLSCREEN_AND_MASK = !( WindowFlags::DECORATIONS.bits | From 007b195a5ec4228daf4512f723c050eac7314fa6 Mon Sep 17 00:00:00 2001 From: Francesca Lovebloom Date: Mon, 4 May 2020 15:55:58 -0700 Subject: [PATCH 138/239] iOS: convert touch positions to physical (#1551) --- CHANGELOG.md | 1 + src/platform_impl/ios/view.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9259fd55b..d9c79649b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. +- On iOS, touch positions are now properly converted to physical pixels. # 0.22.1 (2020-04-16) diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 77f3fc5995..5481b8eff5 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -6,6 +6,7 @@ use objc::{ }; use crate::{ + dpi::PhysicalPosition, event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, platform::ios::MonitorHandleExtIOS, platform_impl::platform::{ @@ -209,7 +210,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { if touch == nil { break; } - let location: CGPoint = msg_send![touch, locationInView: nil]; + let logical_location: CGPoint = msg_send![touch, locationInView: nil]; let touch_type: UITouchType = msg_send![touch, type]; let force = if os_supports_force { let trait_collection: id = msg_send![object, traitCollection]; @@ -248,12 +249,19 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { _ => panic!("unexpected touch phase: {:?}", phase as i32), }; + let physical_location = { + let scale_factor: CGFloat = msg_send![object, contentScaleFactor]; + PhysicalPosition::from_logical::<(f64, f64), f64>( + (logical_location.x as _, logical_location.y as _), + scale_factor, + ) + }; touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::Touch(Touch { device_id: RootDeviceId(DeviceId { uiscreen }), id: touch_id, - location: (location.x as f64, location.y as f64).into(), + location: physical_location, force, phase, }), From b8828105cf6231e249da279851285aecd2970b4b Mon Sep 17 00:00:00 2001 From: Jasper De Sutter Date: Wed, 6 May 2020 15:27:49 +0200 Subject: [PATCH 139/239] add android NDK event loop (#1556) * add android NDK event loop * add Android build documentation & cargo-apk to CI Co-authored-by: David Craven --- .github/workflows/ci.yml | 34 +- CHANGELOG.md | 1 + Cargo.toml | 6 +- README.md | 19 + src/platform/android.rs | 37 +- src/platform_impl/android/ffi.rs | 122 ------ src/platform_impl/android/mod.rs | 725 +++++++++++++++++-------------- 7 files changed, 465 insertions(+), 479 deletions(-) delete mode 100644 src/platform_impl/android/ffi.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 459844569c..b98dbc56b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,20 +37,21 @@ jobs: - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } - { target: i686-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } + - { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: x86_64-apple-ios, os: macos-latest, } - { target: aarch64-apple-ios, os: macos-latest, } # We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web # doesn't currently work on Linux. - - { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web } - - { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web } + - { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, cmd: web } + - { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, cmd: web } env: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0" FEATURES: ${{ format(',{0}', matrix.platform.features ) }} - WEB: ${{ matrix.platform.web }} + CMD: ${{ matrix.platform.cmd }} runs-on: ${{ matrix.platform.os }} steps: @@ -70,6 +71,9 @@ jobs: - name: Install GCC Multilib if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') run: sudo apt-get update && sudo apt-get install gcc-multilib + - name: Install cargo-apk + if: contains(matrix.platform.target, 'android') + run: cargo install cargo-apk - name: Install cargo-web continue-on-error: true if: contains(matrix.platform.target, 'wasm32') @@ -78,29 +82,35 @@ jobs: - name: Check documentation shell: bash if: matrix.platform.target != 'wasm32-unknown-unknown' - run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES - name: Build shell: bash - run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Build tests shell: bash - run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Run tests shell: bash - if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES + if: ( + !contains(matrix.platform.target, 'android') && + !contains(matrix.platform.target, 'ios') && + !contains(matrix.platform.target, 'wasm32')) + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features $FEATURES - name: Build with serde enabled shell: bash - run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES - name: Build tests with serde enabled shell: bash - run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES - name: Run tests with serde enabled shell: bash - if: (!contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + if: ( + !contains(matrix.platform.target, 'android') && + !contains(matrix.platform.target, 'ios') && + !contains(matrix.platform.target, 'wasm32')) + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES diff --git a/CHANGELOG.md b/CHANGELOG.md index d9c79649b1..4ef3d12c79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. +- On Android, minimal platform support. - On iOS, touch positions are now properly converted to physical pixels. # 0.22.1 (2020-04-16) diff --git a/Cargo.toml b/Cargo.toml index 0b6f833f4e..cb466f9f46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,10 @@ bitflags = "1" image = "0.23" simple_logger = "1" -[target.'cfg(target_os = "android")'.dependencies.android_glue] -version = "0.2" +[target.'cfg(target_os = "android")'.dependencies] +ndk = "0.1.0" +ndk-sys = "0.1.0" +ndk-glue = "0.1.0" [target.'cfg(target_os = "ios")'.dependencies] objc = "0.2.3" diff --git a/README.md b/README.md index 3937622557..95aad64afd 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,22 @@ Building a binary will yield a `.js` file. In order to use it in an HTML file, y the element of the `` element (in the example you would retrieve it via `document.getElementById("my_id")`). More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html). - Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created. + +#### Android + +This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation. + +Running on an Android device needs a dynamic system library, add this to Cargo.toml: +```toml +[[example]] +name = "request_redraw_threaded" +crate-type = ["cdylib"] +``` + +And add this to the example file to add the native activity glue: +```rust +#[cfg(target_os = "android")] +ndk_glue::ndk_glue!(main); +``` + +And run the application with `cargo apk run --example request_redraw_threaded` diff --git a/src/platform/android.rs b/src/platform/android.rs index dafb7a391a..b4e9917642 100644 --- a/src/platform/android.rs +++ b/src/platform/android.rs @@ -1,32 +1,39 @@ #![cfg(any(target_os = "android"))] -use crate::{EventLoop, Window, WindowBuilder}; -use std::os::raw::c_void; +use crate::{ + event_loop::{EventLoop, EventLoopWindowTarget}, + window::{Window, WindowBuilder}, +}; +use ndk::configuration::Configuration; +use ndk_glue::Rect; /// Additional methods on `EventLoop` that are specific to Android. -pub trait EventLoopExtAndroid { - /// Makes it possible for glutin to register a callback when a suspend event happens on Android - fn set_suspend_callback(&self, cb: Option ()>>); -} +pub trait EventLoopExtAndroid {} -impl EventLoopExtAndroid for EventLoop { - fn set_suspend_callback(&self, cb: Option ()>>) { - self.event_loop.set_suspend_callback(cb); - } -} +impl EventLoopExtAndroid for EventLoop {} + +/// Additional methods on `EventLoopWindowTarget` that are specific to Android. +pub trait EventLoopWindowTargetExtAndroid {} /// Additional methods on `Window` that are specific to Android. pub trait WindowExtAndroid { - fn native_window(&self) -> *const c_void; + fn content_rect(&self) -> Rect; + + fn config(&self) -> Configuration; } impl WindowExtAndroid for Window { - #[inline] - fn native_window(&self) -> *const c_void { - self.window.native_window() + fn content_rect(&self) -> Rect { + self.window.content_rect() + } + + fn config(&self) -> Configuration { + self.window.config() } } +impl EventLoopWindowTargetExtAndroid for EventLoopWindowTarget {} + /// Additional methods on `WindowBuilder` that are specific to Android. pub trait WindowBuilderExtAndroid {} diff --git a/src/platform_impl/android/ffi.rs b/src/platform_impl/android/ffi.rs deleted file mode 100644 index 93a59b8247..0000000000 --- a/src/platform_impl/android/ffi.rs +++ /dev/null @@ -1,122 +0,0 @@ -#![allow(dead_code)] -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(non_upper_case_globals)] - -use libc; -use std::os::raw; - -#[link(name = "android")] -#[link(name = "EGL")] -#[link(name = "GLESv2")] -extern "C" {} - -/** - ** asset_manager.h - **/ -pub type AAssetManager = raw::c_void; - -/** - ** native_window.h - **/ -pub type ANativeWindow = raw::c_void; - -extern "C" { - pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t; - pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t; -} - -/** - ** native_activity.h - **/ -pub type JavaVM = (); -pub type JNIEnv = (); -pub type jobject = *const libc::c_void; - -pub type AInputQueue = (); // FIXME: wrong -pub type ARect = (); // FIXME: wrong - -#[repr(C)] -pub struct ANativeActivity { - pub callbacks: *mut ANativeActivityCallbacks, - pub vm: *mut JavaVM, - pub env: *mut JNIEnv, - pub clazz: jobject, - pub internalDataPath: *const libc::c_char, - pub externalDataPath: *const libc::c_char, - pub sdkVersion: libc::int32_t, - pub instance: *mut libc::c_void, - pub assetManager: *mut AAssetManager, - pub obbPath: *const libc::c_char, -} - -#[repr(C)] -pub struct ANativeActivityCallbacks { - pub onStart: extern "C" fn(*mut ANativeActivity), - pub onResume: extern "C" fn(*mut ANativeActivity), - pub onSaveInstanceState: extern "C" fn(*mut ANativeActivity, *mut libc::size_t), - pub onPause: extern "C" fn(*mut ANativeActivity), - pub onStop: extern "C" fn(*mut ANativeActivity), - pub onDestroy: extern "C" fn(*mut ANativeActivity), - pub onWindowFocusChanged: extern "C" fn(*mut ANativeActivity, libc::c_int), - pub onNativeWindowCreated: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onNativeWindowResized: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onNativeWindowRedrawNeeded: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onNativeWindowDestroyed: extern "C" fn(*mut ANativeActivity, *const ANativeWindow), - pub onInputQueueCreated: extern "C" fn(*mut ANativeActivity, *mut AInputQueue), - pub onInputQueueDestroyed: extern "C" fn(*mut ANativeActivity, *mut AInputQueue), - pub onContentRectChanged: extern "C" fn(*mut ANativeActivity, *const ARect), - pub onConfigurationChanged: extern "C" fn(*mut ANativeActivity), - pub onLowMemory: extern "C" fn(*mut ANativeActivity), -} - -/** - ** looper.h - **/ -pub type ALooper = (); - -#[link(name = "android")] -extern "C" { - pub fn ALooper_forThread() -> *const ALooper; - pub fn ALooper_acquire(looper: *const ALooper); - pub fn ALooper_release(looper: *const ALooper); - pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper; - pub fn ALooper_pollOnce( - timeoutMillis: libc::c_int, - outFd: *mut libc::c_int, - outEvents: *mut libc::c_int, - outData: *mut *mut libc::c_void, - ) -> libc::c_int; - pub fn ALooper_pollAll( - timeoutMillis: libc::c_int, - outFd: *mut libc::c_int, - outEvents: *mut libc::c_int, - outData: *mut *mut libc::c_void, - ) -> libc::c_int; - pub fn ALooper_wake(looper: *const ALooper); - pub fn ALooper_addFd( - looper: *const ALooper, - fd: libc::c_int, - ident: libc::c_int, - events: libc::c_int, - callback: ALooper_callbackFunc, - data: *mut libc::c_void, - ) -> libc::c_int; - pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int; -} - -pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0; - -pub const ALOOPER_POLL_WAKE: libc::c_int = -1; -pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2; -pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3; -pub const ALOOPER_POLL_ERROR: libc::c_int = -4; - -pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0; -pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1; -pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2; -pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3; -pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4; - -pub type ALooper_callbackFunc = - extern "C" fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int; diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index cf1893749c..ce26994b5d 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -1,450 +1,519 @@ #![cfg(target_os = "android")] -extern crate android_glue; - -mod ffi; - +use crate::{ + dpi::{PhysicalPosition, PhysicalSize, Position, Size}, + error, event, + event_loop::{self, ControlFlow}, + monitor, window, +}; +use ndk::{ + configuration::Configuration, + event::{InputEvent, MotionAction}, + looper::{ForeignLooper, Poll, ThreadLooper}, +}; +use ndk_glue::{Event, Rect}; use std::{ - cell::RefCell, collections::VecDeque, - fmt, - os::raw::c_void, - sync::mpsc::{channel, Receiver}, + sync::{Arc, Mutex, RwLock}, + time::{Duration, Instant}, }; -use crate::{ - error::{ExternalError, NotSupportedError}, - events::{Touch, TouchPhase}, - window::MonitorHandle as RootMonitorHandle, - CreationError, CursorIcon, Event, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, - WindowAttributes, WindowEvent, WindowId as RootWindowId, -}; -use raw_window_handle::{android::AndroidHandle, RawWindowHandle}; -use CreationError::OsError; - -pub(crate) use crate::icon::NoIcon as PlatformIcon; - -pub type OsError = std::io::Error; - -pub struct EventLoop { - event_rx: Receiver, - suspend_callback: RefCell ()>>>, +lazy_static! { + static ref CONFIG: RwLock = RwLock::new(Configuration::new()); } -#[derive(Clone)] -pub struct EventLoopProxy; +enum EventSource { + Callback, + InputQueue, + User, +} -impl EventLoop { - pub fn new() -> EventLoop { - let (tx, rx) = channel(); - android_glue::add_sender(tx); - EventLoop { - event_rx: rx, - suspend_callback: Default::default(), - } +fn poll(poll: Poll) -> Option { + match poll { + Poll::Event { data, .. } => match data as usize { + 0 => Some(EventSource::Callback), + 1 => Some(EventSource::InputQueue), + _ => unreachable!(), + }, + Poll::Timeout => None, + Poll::Wake => Some(EventSource::User), + Poll::Callback => unreachable!(), } +} - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::with_capacity(1); - rb.push_back(MonitorHandle); - rb - } +pub struct EventLoop { + window_target: event_loop::EventLoopWindowTarget, + user_queue: Arc>>, +} - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle +impl EventLoop { + pub fn new() -> Self { + Self { + window_target: event_loop::EventLoopWindowTarget { + p: EventLoopWindowTarget { + _marker: std::marker::PhantomData, + }, + _marker: std::marker::PhantomData, + }, + user_queue: Default::default(), + } } - pub fn poll_events(&mut self, mut callback: F) + pub fn run(self, mut event_handler: F) -> ! where - F: FnMut(::Event), + F: 'static + + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - while let Ok(event) = self.event_rx.try_recv() { - let e = match event { - android_glue::Event::EventMotion(motion) => { - let scale_factor = MonitorHandle.scale_factor(); - let location = LogicalPosition::from_physical( - (motion.x as f64, motion.y as f64), - scale_factor, - ); - Some(Event::WindowEvent { - window_id: RootWindowId(WindowId), - event: WindowEvent::Touch(Touch { - phase: match motion.action { - android_glue::MotionAction::Down => TouchPhase::Started, - android_glue::MotionAction::Move => TouchPhase::Moved, - android_glue::MotionAction::Up => TouchPhase::Ended, - android_glue::MotionAction::Cancel => TouchPhase::Cancelled, - }, - location, - force: None, // TODO - id: motion.pointer_id as u64, - device_id: DEVICE_ID, - }), - }) + let mut cf = ControlFlow::default(); + let mut first_event = None; + let mut start_cause = event::StartCause::Init; + let looper = ThreadLooper::for_thread().unwrap(); + let mut running = false; + + loop { + event_handler( + event::Event::NewEvents(start_cause), + self.window_target(), + &mut cf, + ); + + let mut redraw = false; + let mut resized = false; + + match first_event.take() { + Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { + Event::WindowCreated => { + event_handler(event::Event::Resumed, self.window_target(), &mut cf); + } + Event::WindowResized => resized = true, + Event::WindowRedrawNeeded => redraw = true, + Event::WindowDestroyed => { + event_handler(event::Event::Suspended, self.window_target(), &mut cf); + } + Event::Pause => running = false, + Event::Resume => running = true, + Event::ConfigChanged => { + let am = ndk_glue::native_activity().asset_manager(); + let config = Configuration::from_asset_manager(&am); + let old_scale_factor = MonitorHandle.scale_factor(); + *CONFIG.write().unwrap() = config; + let scale_factor = MonitorHandle.scale_factor(); + if (scale_factor - old_scale_factor).abs() < f64::EPSILON { + let mut size = MonitorHandle.size(); + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::ScaleFactorChanged { + new_inner_size: &mut size, + scale_factor, + }, + }; + event_handler(event, self.window_target(), &mut cf); + } + } + _ => {} + }, + Some(EventSource::InputQueue) => { + if let Some(input_queue) = ndk_glue::input_queue().as_ref() { + while let Some(event) = input_queue.get_event() { + println!("event {:?}", event); + if let Some(event) = input_queue.pre_dispatch(event) { + let window_id = window::WindowId(WindowId); + let device_id = event::DeviceId(DeviceId); + match &event { + InputEvent::MotionEvent(motion_event) => { + let phase = match motion_event.action() { + MotionAction::Down => Some(event::TouchPhase::Started), + MotionAction::Up => Some(event::TouchPhase::Ended), + MotionAction::Move => Some(event::TouchPhase::Moved), + MotionAction::Cancel => { + Some(event::TouchPhase::Cancelled) + } + _ => None, // TODO mouse events + }; + let pointer = motion_event.pointer_at_index(0); + let location = PhysicalPosition { + x: pointer.x() as _, + y: pointer.y() as _, + }; + + if let Some(phase) = phase { + let event = event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Touch(event::Touch { + device_id, + phase, + location, + id: 0, + force: None, + }), + }; + event_handler(event, self.window_target(), &mut cf); + } + } + InputEvent::KeyEvent(_) => {} // TODO + }; + input_queue.finish_event(event, true); + } + } + } } - android_glue::Event::InitWindow => { - // The activity went to foreground. - if let Some(cb) = self.suspend_callback.borrow().as_ref() { - (*cb)(false); + Some(EventSource::User) => { + let mut user_queue = self.user_queue.lock().unwrap(); + while let Some(event) = user_queue.pop_front() { + event_handler( + event::Event::UserEvent(event), + self.window_target(), + &mut cf, + ); } - Some(Event::Resumed) } - android_glue::Event::TermWindow => { - // The activity went to background. - if let Some(cb) = self.suspend_callback.borrow().as_ref() { - (*cb)(true); + None => {} + } + + event_handler( + event::Event::MainEventsCleared, + self.window_target(), + &mut cf, + ); + + if resized && running { + let size = MonitorHandle.size(); + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::Resized(size), + }; + event_handler(event, self.window_target(), &mut cf); + } + + if redraw && running { + let event = event::Event::RedrawRequested(window::WindowId(WindowId)); + event_handler(event, self.window_target(), &mut cf); + } + + event_handler( + event::Event::RedrawEventsCleared, + self.window_target(), + &mut cf, + ); + + match cf { + ControlFlow::Exit => panic!(), + ControlFlow::Poll => { + start_cause = event::StartCause::Poll; + } + ControlFlow::Wait => { + first_event = poll(looper.poll_all().unwrap()); + start_cause = event::StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, } - Some(Event::Suspended) } - android_glue::Event::WindowResized | android_glue::Event::ConfigChanged => { - // Activity Orientation changed or resized. - let native_window = unsafe { android_glue::native_window() }; - if native_window.is_null() { - None + ControlFlow::WaitUntil(instant) => { + let start = Instant::now(); + let duration = if instant <= start { + Duration::default() } else { - let scale_factor = MonitorHandle.scale_factor(); - let physical_size = MonitorHandle.size(); - let size = LogicalSize::from_physical(physical_size, scale_factor); - Some(Event::WindowEvent { - window_id: RootWindowId(WindowId), - event: WindowEvent::Resized(size), - }) + instant - start + }; + first_event = poll(looper.poll_all_timeout(duration).unwrap()); + start_cause = if first_event.is_some() { + event::StartCause::WaitCancelled { + start, + requested_resume: Some(instant), + } + } else { + event::StartCause::ResumeTimeReached { + start, + requested_resume: instant, + } } } - android_glue::Event::WindowRedrawNeeded => { - // The activity needs to be redrawn. - Some(Event::WindowEvent { - window_id: RootWindowId(WindowId), - event: WindowEvent::Redraw, - }) - } - android_glue::Event::Wake => Some(Event::Awakened), - _ => None, - }; - - if let Some(event) = e { - callback(event); } } } - pub fn set_suspend_callback(&self, cb: Option ()>>) { - *self.suspend_callback.borrow_mut() = cb; + pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { + &self.window_target } - pub fn run_forever(&mut self, mut callback: F) - where - F: FnMut(::Event) -> ::ControlFlow, - { - // Yeah that's a very bad implementation. - loop { - let mut control_flow = ::ControlFlow::Continue; - self.poll_events(|e| { - if let ::ControlFlow::Break = callback(e) { - control_flow = ::ControlFlow::Break; - } - }); - if let ::ControlFlow::Break = control_flow { - break; - } - ::std::thread::sleep(::std::time::Duration::from_millis(5)); - } + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle } - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(self.primary_monitor()); + v } + + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy { + queue: self.user_queue.clone(), + looper: ForeignLooper::for_thread().expect("called from event loop thread"), + } + } +} + +pub struct EventLoopProxy { + queue: Arc>>, + looper: ForeignLooper, } -impl EventLoopProxy { - pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> { - android_glue::wake_event_loop(); +impl EventLoopProxy { + pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { + self.queue.lock().unwrap().push_back(event); + self.looper.wake(); Ok(()) } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + EventLoopProxy { + queue: self.queue.clone(), + looper: self.looper.clone(), + } + } +} + +pub struct EventLoopWindowTarget { + _marker: std::marker::PhantomData, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WindowId; impl WindowId { - pub unsafe fn dummy() -> Self { + pub fn dummy() -> Self { WindowId } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct DeviceId; impl DeviceId { - pub unsafe fn dummy() -> Self { + pub fn dummy() -> Self { DeviceId } } -pub struct Window { - native_window: *const c_void, -} - -#[derive(Clone)] -pub struct MonitorHandle; +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PlatformSpecificWindowBuilderAttributes; -impl fmt::Debug for MonitorHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[derive(Debug)] - struct MonitorHandle { - name: Option, - dimensions: PhysicalSize, - position: PhysicalPosition, - scale_factor: f64, - } +pub struct Window; - let monitor_id_proxy = MonitorHandle { - name: self.name(), - dimensions: self.size(), - position: self.outer_position(), - scale_factor: self.scale_factor(), - }; +impl Window { + pub fn new( + _el: &EventLoopWindowTarget, + _window_attrs: window::WindowAttributes, + _: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + // FIXME this ignores requested window attributes + Ok(Self) + } - monitor_id_proxy.fmt(f) + pub fn id(&self) -> WindowId { + WindowId } -} -impl MonitorHandle { - #[inline] - pub fn name(&self) -> Option { - Some("Primary".to_string()) + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle } - #[inline] - pub fn size(&self) -> PhysicalSize { - unsafe { - let window = android_glue::native_window(); - ( - ffi::ANativeWindow_getWidth(window) as f64, - ffi::ANativeWindow_getHeight(window) as f64, - ) - .into() - } + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(MonitorHandle); + v } - #[inline] - pub fn outer_position(&self) -> PhysicalPosition { - // Android assumes single screen - (0, 0).into() + pub fn current_monitor(&self) -> monitor::MonitorHandle { + monitor::MonitorHandle { + inner: MonitorHandle, + } } - #[inline] pub fn scale_factor(&self) -> f64 { - 1.0 + MonitorHandle.scale_factor() } -} - -#[derive(Clone, Default)] -pub struct PlatformSpecificWindowBuilderAttributes; -#[derive(Clone, Default)] -pub struct PlatformSpecificHeadlessBuilderAttributes; - -impl Window { - pub fn new( - _: &EventLoop, - win_attribs: WindowAttributes, - _: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - let native_window = unsafe { android_glue::native_window() }; - if native_window.is_null() { - return Err(OsError(format!("Android's native window is null"))); - } - android_glue::set_multitouch(true); - - Ok(Window { - native_window: native_window as *const _, - }) + pub fn request_redraw(&self) { + // TODO } - #[inline] - pub fn native_window(&self) -> *const c_void { - self.native_window + pub fn inner_position(&self) -> Result, error::NotSupportedError> { + Err(error::NotSupportedError::new()) } - #[inline] - pub fn set_title(&self, _: &str) { - // N/A + pub fn outer_position(&self) -> Result, error::NotSupportedError> { + Err(error::NotSupportedError::new()) } - #[inline] - pub fn show(&self) { - // N/A + pub fn set_outer_position(&self, _position: Position) { + // no effect } - #[inline] - pub fn hide(&self) { - // N/A + pub fn inner_size(&self) -> PhysicalSize { + self.outer_size() } - #[inline] - pub fn outer_position(&self) -> Option> { - // N/A - None + pub fn set_inner_size(&self, _size: Size) { + panic!("Cannot set window size on Android"); } - #[inline] - pub fn inner_position(&self) -> Option> { - // N/A - None + pub fn outer_size(&self) -> PhysicalSize { + MonitorHandle.size() } - #[inline] - pub fn set_outer_position(&self, _position: LogicalPosition) { - // N/A - } + pub fn set_min_inner_size(&self, _: Option) {} - #[inline] - pub fn set_min_inner_size(&self, _dimensions: Option>) { - // N/A - } + pub fn set_max_inner_size(&self, _: Option) {} - #[inline] - pub fn set_max_inner_size(&self, _dimensions: Option>) { - // N/A - } + pub fn set_title(&self, _title: &str) {} - #[inline] - pub fn set_resizable(&self, _resizable: bool) { - // N/A - } + pub fn set_visible(&self, _visibility: bool) {} - #[inline] - pub fn inner_size(&self) -> Option> { - if self.native_window.is_null() { - None - } else { - let scale_factor = self.scale_factor(); - let physical_size = self.current_monitor().size(); - Some(LogicalSize::from_physical(physical_size, scale_factor)) - } - } + pub fn set_resizable(&self, _resizeable: bool) {} - #[inline] - pub fn outer_size(&self) -> Option> { - self.inner_size() - } + pub fn set_minimized(&self, _minimized: bool) {} - #[inline] - pub fn set_inner_size(&self, _size: LogicalSize) { - // N/A - } + pub fn set_maximized(&self, _maximized: bool) {} - #[inline] - pub fn scale_factor(&self) -> f64 { - self.current_monitor().scale_factor() + pub fn set_fullscreen(&self, _monitor: Option) { + panic!("Cannot set fullscreen on Android"); } - #[inline] - pub fn set_cursor_icon(&self, _: CursorIcon) { - // N/A + pub fn fullscreen(&self) -> Option { + None } - #[inline] - pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } + pub fn set_decorations(&self, _decorations: bool) {} - #[inline] - pub fn hide_cursor(&self, _hide: bool) { - // N/A - } + pub fn set_always_on_top(&self, _always_on_top: bool) {} - #[inline] - pub fn set_cursor_position( - &self, - _position: LogicalPosition, - ) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } + pub fn set_window_icon(&self, _window_icon: Option) {} - #[inline] - pub fn set_minimized(&self, _minimized: bool) { - unimplemented!() - } + pub fn set_ime_position(&self, _position: Position) {} + + pub fn set_cursor_icon(&self, _: window::CursorIcon) {} - #[inline] - pub fn set_maximized(&self, _maximized: bool) { - // N/A - // Android has single screen maximized apps so nothing to do + pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) } - #[inline] - pub fn fullscreen(&self) -> Option { - // N/A - // Android has single screen maximized apps so nothing to do - None + pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> { + Err(error::ExternalError::NotSupported( + error::NotSupportedError::new(), + )) } - #[inline] - pub fn set_fullscreen(&self, _monitor: Option) { - // N/A - // Android has single screen maximized apps so nothing to do + pub fn set_cursor_visible(&self, _: bool) {} + + pub fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { + let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() { + unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ } + } else { + panic!("native window null"); + }; + let mut handle = raw_window_handle::android::AndroidHandle::empty(); + handle.a_native_window = a_native_window; + raw_window_handle::RawWindowHandle::Android(handle) } - #[inline] - pub fn set_decorations(&self, _decorations: bool) { - // N/A + pub fn config(&self) -> Configuration { + CONFIG.read().unwrap().clone() } - #[inline] - pub fn set_always_on_top(&self, _always_on_top: bool) { - // N/A + pub fn content_rect(&self) -> Rect { + ndk_glue::content_rect() } +} - #[inline] - pub fn set_window_icon(&self, _icon: Option<::Icon>) { - // N/A +#[derive(Default, Clone, Debug)] +pub struct OsError; + +use std::fmt::{self, Display, Formatter}; +impl Display for OsError { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { + write!(fmt, "Android OS Error") } +} - #[inline] - pub fn set_ime_position(&self, _spot: LogicalPosition) { - // N/A +pub(crate) use crate::icon::NoIcon as PlatformIcon; + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct MonitorHandle; + +impl MonitorHandle { + pub fn name(&self) -> Option { + Some("Android Device".to_owned()) } - #[inline] - pub fn current_monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: MonitorHandle, + pub fn size(&self) -> PhysicalSize { + if let Some(native_window) = ndk_glue::native_window().as_ref() { + let width = native_window.width() as _; + let height = native_window.height() as _; + PhysicalSize::new(width, height) + } else { + PhysicalSize::new(0, 0) } } - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut rb = VecDeque::with_capacity(1); - rb.push_back(MonitorHandle); - rb + pub fn position(&self) -> PhysicalPosition { + (0, 0).into() } - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle + pub fn scale_factor(&self) -> f64 { + let config = CONFIG.read().unwrap(); + config + .density() + .map(|dpi| dpi as f64 / 160.0) + .unwrap_or(1.0) + } + + pub fn video_modes(&self) -> impl Iterator { + let size = self.size().into(); + let mut v = Vec::new(); + // FIXME this is not the real refresh rate + // (it is guarunteed to support 32 bit color though) + v.push(monitor::VideoMode { + video_mode: VideoMode { + size, + bit_depth: 32, + refresh_rate: 60, + monitor: self.clone(), + }, + }); + v.into_iter() } +} - #[inline] - pub fn id(&self) -> WindowId { - WindowId +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct VideoMode { + size: (u32, u32), + bit_depth: u16, + refresh_rate: u16, + monitor: MonitorHandle, +} + +impl VideoMode { + pub fn size(&self) -> PhysicalSize { + self.size.into() } - #[inline] - pub fn raw_window_handle(&self) -> RawWindowHandle { - let handle = AndroidHandle { - a_native_window: self.native_window, - ..WindowsHandle::empty() - }; - RawWindowHandle::Android(handle) + pub fn bit_depth(&self) -> u16 { + self.bit_depth } -} -unsafe impl Send for Window {} -unsafe impl Sync for Window {} + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } -// Constant device ID, to be removed when this backend is updated to report real device IDs. -const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); + pub fn monitor(&self) -> monitor::MonitorHandle { + monitor::MonitorHandle { + inner: self.monitor.clone(), + } + } +} From 3c38afdb4733572270805896160e8217a04051e9 Mon Sep 17 00:00:00 2001 From: j4qfrost Date: Thu, 7 May 2020 19:32:09 -0700 Subject: [PATCH 140/239] Update macOS dependencies (#1554) * update macos libs * modify dependency * changelog * update core-video-sys version --- CHANGELOG.md | 1 + Cargo.toml | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef3d12c79..32f66702cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. +- On macOS, updated core-* dependencies and cocoa - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. diff --git a/Cargo.toml b/Cargo.toml index cb466f9f46..218fb2965a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,14 +42,14 @@ ndk-glue = "0.1.0" objc = "0.2.3" [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.19.1" -core-foundation = "0.6" -core-graphics = "0.17.3" +cocoa = "0.20" +core-foundation = "0.7" +core-graphics = "0.19" dispatch = "0.2.0" objc = "0.2.6" [target.'cfg(target_os = "macos")'.dependencies.core-video-sys] -version = "0.1.3" +version = "0.1.4" default_features = false features = ["display_link"] From c7a33f926b2177a214a3d7515720a0111cf1c52c Mon Sep 17 00:00:00 2001 From: curldivergence Date: Fri, 15 May 2020 21:31:32 +0300 Subject: [PATCH 141/239] Fixed a couple of typos in repo description (#1568) --- FEATURES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 0918d358f5..cdd8cc9f13 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -109,8 +109,8 @@ If your PR makes notable changes to Winit's features, please update this section translating keypresses into UTF-8 characters, handling dead keys and IMEs. - **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled. - **Raw Device Events**: Capturing input from input devices without any OS filtering. -- **Gamepad/Joystick events**: Capturing input from gampads and joysticks. -- **Device movement events:**: Capturing input from the device gyroscope and accelerometer. +- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks. +- **Device movement events**: Capturing input from the device gyroscope and accelerometer. ## Platform ### Windows From bc19c04339e5b3b28cf7441c9802d52703cae6b5 Mon Sep 17 00:00:00 2001 From: j4qfrost Date: Fri, 15 May 2020 11:32:04 -0700 Subject: [PATCH 142/239] Fixed changelog line for core-* dependencies (#1561) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f66702cf..284b9961be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - On Windows, fix `WindowBuilder::with_maximized` being ignored. - On Android, minimal platform support. - On iOS, touch positions are now properly converted to physical pixels. +- On macOS, updated core-* dependencies and cocoa # 0.22.1 (2020-04-16) @@ -43,7 +44,6 @@ - On macOS, fix `CursorMoved` event reporting the cursor position using logical coordinates. - On macOS, fix issue where unbundled applications would sometimes open without being focused. - On macOS, fix `run_return` does not return unless it receives a message. -- On macOS, updated core-* dependencies and cocoa - On Windows, fix bug where `RedrawRequested` would only get emitted every other iteration of the event loop. - On X11, fix deadlock on window state when handling certain window events. - `WindowBuilder` now implements `Default`. From 878c179761cbc536ad24906486430ed2bb1d6092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Hornick=C3=BD?= Date: Fri, 15 May 2020 20:58:12 +0200 Subject: [PATCH 143/239] Implement Clone for 'static events (#1478) --- CHANGELOG.md | 1 + src/event.rs | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 284b9961be..4086eefbcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- Added Clone implementation for 'static events. - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. - On Android, minimal platform support. diff --git a/src/event.rs b/src/event.rs index 6b9fe1a630..47119fa807 100644 --- a/src/event.rs +++ b/src/event.rs @@ -118,6 +118,30 @@ pub enum Event<'a, T: 'static> { LoopDestroyed, } +impl Clone for Event<'static, T> { + fn clone(&self) -> Self { + use self::Event::*; + match self { + WindowEvent { window_id, event } => WindowEvent { + window_id: *window_id, + event: event.clone(), + }, + UserEvent(event) => UserEvent(event.clone()), + DeviceEvent { device_id, event } => DeviceEvent { + device_id: *device_id, + event: event.clone(), + }, + NewEvents(cause) => NewEvents(cause.clone()), + MainEventsCleared => MainEventsCleared, + RedrawRequested(wid) => RedrawRequested(*wid), + RedrawEventsCleared => RedrawEventsCleared, + LoopDestroyed => LoopDestroyed, + Suspended => Suspended, + Resumed => Resumed, + } + } +} + impl<'a, T> Event<'a, T> { pub fn map_nonuser_event(self) -> Result, Event<'a, T>> { use self::Event::*; @@ -330,6 +354,97 @@ pub enum WindowEvent<'a> { ThemeChanged(Theme), } +impl Clone for WindowEvent<'static> { + fn clone(&self) -> Self { + use self::WindowEvent::*; + return match self { + Resized(size) => Resized(size.clone()), + Moved(pos) => Moved(pos.clone()), + CloseRequested => CloseRequested, + Destroyed => Destroyed, + DroppedFile(file) => DroppedFile(file.clone()), + HoveredFile(file) => HoveredFile(file.clone()), + HoveredFileCancelled => HoveredFileCancelled, + ReceivedCharacter(c) => ReceivedCharacter(*c), + Focused(f) => Focused(*f), + KeyboardInput { + device_id, + input, + is_synthetic, + } => KeyboardInput { + device_id: *device_id, + input: *input, + is_synthetic: *is_synthetic, + }, + + ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()), + #[allow(deprecated)] + CursorMoved { + device_id, + position, + modifiers, + } => CursorMoved { + device_id: *device_id, + position: *position, + modifiers: *modifiers, + }, + CursorEntered { device_id } => CursorEntered { + device_id: *device_id, + }, + CursorLeft { device_id } => CursorLeft { + device_id: *device_id, + }, + #[allow(deprecated)] + MouseWheel { + device_id, + delta, + phase, + modifiers, + } => MouseWheel { + device_id: *device_id, + delta: *delta, + phase: *phase, + modifiers: *modifiers, + }, + #[allow(deprecated)] + MouseInput { + device_id, + state, + button, + modifiers, + } => MouseInput { + device_id: *device_id, + state: *state, + button: *button, + modifiers: *modifiers, + }, + TouchpadPressure { + device_id, + pressure, + stage, + } => TouchpadPressure { + device_id: *device_id, + pressure: *pressure, + stage: *stage, + }, + AxisMotion { + device_id, + axis, + value, + } => AxisMotion { + device_id: *device_id, + axis: *axis, + value: *value, + }, + Touch(touch) => Touch(*touch), + ThemeChanged(theme) => ThemeChanged(theme.clone()), + ScaleFactorChanged { .. } => { + unreachable!("Static event can't be about scale factor changing") + } + }; + } +} + impl<'a> WindowEvent<'a> { pub fn to_static(self) -> Option> { use self::WindowEvent::*; @@ -809,8 +924,10 @@ pub enum VirtualKeyCode { Multiply, Mute, MyComputer, - NavigateForward, // also called "Prior" - NavigateBackward, // also called "Next" + // also called "Next" + NavigateForward, + // also called "Prior" + NavigateBackward, NextTrack, NoConvert, NumpadComma, From 49bcec1d274aaedc453a64bf6f53ea1ec59aa3a2 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sat, 16 May 2020 12:27:16 -0400 Subject: [PATCH 144/239] Release 0.22.2 (#1570) --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4086eefbcb..3a0dbabf5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 0.22.2 (2020-05-16) + - Added Clone implementation for 'static events. - On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`. - On Windows, fix `WindowBuilder::with_maximized` being ignored. diff --git a/Cargo.toml b/Cargo.toml index 218fb2965a..2f4903d9f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.22.1" +version = "0.22.2" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index 95aad64afd..5e489631c6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```toml [dependencies] -winit = "0.22.1" +winit = "0.22.2" ``` ## [Documentation](https://docs.rs/winit) From 6cfddfea21319ef7e28f4a6dc16e119ab033f550 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Thu, 21 May 2020 13:13:33 -0400 Subject: [PATCH 145/239] Prevent the default browser behavior of events (#1576) This stops things like page scrolling on spacebar / arrow keys --- CHANGELOG.md | 1 + src/platform_impl/web/stdweb/canvas.rs | 9 ++++++--- src/platform_impl/web/web_sys/canvas.rs | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0dbabf5e..250ef38a02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index f6ed4866b9..64f6a18f10 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -10,9 +10,9 @@ use stdweb::js; use stdweb::traits::IPointerEvent; use stdweb::unstable::TryInto; use stdweb::web::event::{ - BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, KeyDownEvent, KeyPressEvent, - KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent, PointerOutEvent, - PointerOverEvent, PointerUpEvent, + BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, IEvent, KeyDownEvent, + KeyPressEvent, KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent, + PointerOutEvent, PointerOverEvent, PointerUpEvent, }; use stdweb::web::html_element::CanvasElement; use stdweb::web::{ @@ -130,6 +130,7 @@ impl Canvas { F: 'static + FnMut(ScanCode, Option, ModifiersState), { self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| { + event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -143,6 +144,7 @@ impl Canvas { F: 'static + FnMut(ScanCode, Option, ModifiersState), { self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| { + event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -228,6 +230,7 @@ impl Canvas { F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| { + event.prevent_default(); if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index a2755b95d2..e8fd3a2bba 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -135,6 +135,7 @@ impl Canvas { { self.on_keyboard_release = Some(self.add_user_event("keyup", move |event: KeyboardEvent| { + event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -149,6 +150,7 @@ impl Canvas { { self.on_keyboard_press = Some(self.add_user_event("keydown", move |event: KeyboardEvent| { + event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -242,6 +244,7 @@ impl Canvas { F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| { + event.prevent_default(); if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); } From ff66bdda7ca5386cd159b4f44711fd97d7c9aef4 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 22 May 2020 13:33:04 +0300 Subject: [PATCH 146/239] On Wayland, fix deadlock when calling set_inner_size from event loop Fixes #1571. --- CHANGELOG.md | 2 ++ src/platform_impl/linux/wayland/event_loop.rs | 3 +++ src/platform_impl/linux/wayland/window.rs | 14 ++++++-------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 250ef38a02..d15294b985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Unreleased - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas +- On Wayland, fix deadlock when calling to `set_inner_size` from a callback. + # 0.22.2 (2020-05-16) - Added Clone implementation for 'static events. diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 5a3614c8b5..3262fe1e59 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -667,6 +667,8 @@ impl EventLoop { window_target.store.lock().unwrap().for_each_redraw_trigger( |refresh, frame_refresh, wid, frame| { if let Some(frame) = frame { + let mut frame = frame.lock().unwrap(); + if frame_refresh { frame.refresh(); if !refresh { @@ -751,6 +753,7 @@ impl EventLoop { if window.new_size.is_some() || window.new_scale_factor.is_some() { if let Some(frame) = window.frame { + let mut frame = frame.lock().unwrap(); // Update decorations state match window.decorations_action { Some(DecorationsAction::Hide) => frame.set_decorate(false), diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 12ae9ba1d5..5b680999a6 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -461,7 +461,7 @@ pub struct WindowStoreForEach<'a> { pub grab_cursor: Option, pub surface: &'a wl_surface::WlSurface, pub wid: WindowId, - pub frame: Option<&'a mut SWindow>, + pub frame: Option>>>, pub decorations_action: Option, } @@ -522,8 +522,7 @@ impl WindowStore { if let Some(scale_factor) = window.new_scale_factor { window.current_scale_factor = scale_factor; } - let opt_arc = window.frame.upgrade(); - let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); + let frame = window.frame.upgrade(); let decorations_action = { window.pending_decorations_action.lock().unwrap().take() }; f(WindowStoreForEach { new_size: window.new_size.take(), @@ -534,7 +533,7 @@ impl WindowStore { grab_cursor: window.cursor_grab_changed.lock().unwrap().take(), surface: &window.surface, wid: make_wid(&window.surface), - frame: opt_mutex_lock.as_mut().map(|m| &mut **m), + frame, decorations_action, }); // avoid re-spamming the event @@ -544,16 +543,15 @@ impl WindowStore { pub fn for_each_redraw_trigger(&mut self, mut f: F) where - F: FnMut(bool, bool, WindowId, Option<&mut SWindow>), + F: FnMut(bool, bool, WindowId, Option>>>), { for window in &mut self.windows { - let opt_arc = window.frame.upgrade(); - let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap()); + let frame = window.frame.upgrade(); f( replace(&mut *window.need_refresh.lock().unwrap(), false), replace(&mut *window.need_frame_refresh.lock().unwrap(), false), make_wid(&window.surface), - opt_mutex_lock.as_mut().map(|m| &mut **m), + frame, ); } } From 03335cef851495e0a2843db7019516988740688b Mon Sep 17 00:00:00 2001 From: Andrew Slater Date: Sun, 24 May 2020 17:26:29 +0100 Subject: [PATCH 147/239] macOS: add function to hide other applications --- CHANGELOG.md | 1 + src/platform/macos.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d15294b985..279905d623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. +- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. # 0.22.2 (2020-05-16) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index b275c7538d..f07e2f6e77 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -215,6 +215,8 @@ impl MonitorHandleExtMacOS for MonitorHandle { pub trait EventLoopWindowTargetExtMacOS { /// Hide the entire application. In most applications this is typically triggered with Command-H. fn hide_application(&self); + /// Hide the other applications. In most applications this is typically triggered with Command+Option-H. + fn hide_other_applications(&self); } impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { @@ -223,4 +225,10 @@ impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] }; unsafe { msg_send![app, hide: 0] } } + + fn hide_other_applications(&self) { + let cls = objc::runtime::Class::get("NSApplication").unwrap(); + let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] }; + unsafe { msg_send![app, hideOtherApplications: 0] } + } } From a4121a2c2e13c274e27fc653df907ceaeca8132f Mon Sep 17 00:00:00 2001 From: Boqin Qin Date: Thu, 28 May 2020 00:24:08 +0800 Subject: [PATCH 148/239] platform_impl/linux/x11: fix deadlock in fn set_fullscreen_inner (#1579) Co-authored-by: Kirill Chibisov --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/window.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 279905d623..cc920994ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index f178ba3aa5..f4cb3d4536 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -640,6 +640,7 @@ impl UnownedWindow { let flusher = self.set_fullscreen_hint(false); let mut shared_state_lock = self.shared_state.lock(); if let Some(position) = shared_state_lock.restore_position.take() { + drop(shared_state_lock); self.set_position_inner(position.0, position.1).queue(); } Some(flusher) From 5a6cfc314ea4890d79eefb01ef94bd46ca7858cf Mon Sep 17 00:00:00 2001 From: Viktor Zoutman Date: Tue, 9 Jun 2020 23:46:33 +0200 Subject: [PATCH 149/239] Macos fullscreen & dialog support with `run_return` (#1581) * Fix for fullscreen with run_return on mac * Cleanup * Removed a comment * fmt * This doesn't break exiting run_return anymore * Now you can also transition from code * Fmt & cleanup * Now using a atomic instead of a static bool * reinserted a line * Fmt * Added support for dialogs and child windows * Cargo fmt * Dialogs are now being shutdown properly * Cargo fmt * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/platform_impl/macos/app_state.rs | 61 +++++++++++++++------- src/platform_impl/macos/window.rs | 5 +- src/platform_impl/macos/window_delegate.rs | 13 ++++- 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc920994ce..808a3c377d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. +- On MacOS, Fixed fullscreen and dialog support for `run_return`. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 135a3d2180..29fafbe5fa 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -92,6 +92,7 @@ impl EventHandler for EventLoopHandler { struct Handler { ready: AtomicBool, in_callback: AtomicBool, + dialog_is_closing: AtomicBool, control_flow: Mutex, control_flow_prev: Mutex, start_time: Mutex>, @@ -223,6 +224,8 @@ impl Handler { } } +pub static INTERRUPT_EVENT_LOOP_EXIT: AtomicBool = AtomicBool::new(false); + pub enum AppState {} impl AppState { @@ -336,29 +339,51 @@ impl AppState { } if HANDLER.should_exit() { unsafe { - let _: () = msg_send![NSApp(), stop: nil]; - - let pool = NSAutoreleasePool::new(nil); - - let windows: id = msg_send![NSApp(), windows]; + let app: id = NSApp(); + let windows: id = msg_send![app, windows]; let window: id = msg_send![windows, objectAtIndex:0]; + let window_count: usize = msg_send![windows, count]; assert_ne!(window, nil); - let dummy_event: id = msg_send![class!(NSEvent), - otherEventWithType: NSApplicationDefined - location: NSPoint::new(0.0, 0.0) - modifierFlags: 0 - timestamp: 0 - windowNumber: 0 - context: nil - subtype: 0 - data1: 0 - data2: 0 - ]; - // To stop event loop immediately, we need to post some event here. - let _: () = msg_send![window, postEvent: dummy_event atStart: YES]; + let dialog_open = if window_count > 1 { + let dialog: id = msg_send![windows, lastObject]; + let is_main_window: bool = msg_send![dialog, isMainWindow]; + msg_send![dialog, isVisible] && !is_main_window + } else { + false + }; + let dialog_is_closing = HANDLER.dialog_is_closing.load(Ordering::SeqCst); + let pool = NSAutoreleasePool::new(nil); + if !INTERRUPT_EVENT_LOOP_EXIT.load(Ordering::SeqCst) + && !dialog_open + && !dialog_is_closing + { + let _: () = msg_send![app, stop: nil]; + + let dummy_event: id = msg_send![class!(NSEvent), + otherEventWithType: NSApplicationDefined + location: NSPoint::new(0.0, 0.0) + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: nil + subtype: 0 + data1: 0 + data2: 0 + ]; + // To stop event loop immediately, we need to post some event here. + let _: () = msg_send![window, postEvent: dummy_event atStart: YES]; + } pool.drain(); + + let window_has_focus = msg_send![window, isKeyWindow]; + if !dialog_open && window_has_focus && dialog_is_closing { + HANDLER.dialog_is_closing.store(false, Ordering::SeqCst); + } + if dialog_open { + HANDLER.dialog_is_closing.store(true, Ordering::SeqCst); + } }; } HANDLER.update_start_time(); diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 2d692949ed..5505b661af 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -19,6 +19,7 @@ use crate::{ platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, platform_impl::platform::{ app_state::AppState, + app_state::INTERRUPT_EVENT_LOOP_EXIT, ffi, monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, @@ -820,6 +821,8 @@ impl UnownedWindow { shared_state_lock.fullscreen = fullscreen.clone(); trace!("Unlocked shared state in `set_fullscreen`"); + INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); + match (&old_fullscreen, &fullscreen) { (&None, &Some(_)) => unsafe { util::toggle_full_screen_async( @@ -865,7 +868,7 @@ impl UnownedWindow { ) => unsafe { util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); }, - _ => (), + _ => INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst), } } diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index a89e99fcaf..8c49e773be 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1,7 +1,7 @@ use std::{ f64, os::raw::c_void, - sync::{Arc, Weak}, + sync::{atomic::Ordering, Arc, Weak}, }; use cocoa::{ @@ -19,6 +19,7 @@ use crate::{ event::{Event, ModifiersState, WindowEvent}, platform_impl::platform::{ app_state::AppState, + app_state::INTERRUPT_EVENT_LOOP_EXIT, event::{EventProxy, EventWrapper}, util::{self, IdRef}, view::ViewState, @@ -429,6 +430,9 @@ extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { /// Invoked when before enter fullscreen extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { trace!("Triggered `windowWillEnterFullscreen:`"); + + INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); + with_state(this, |state| { state.with_window(|window| { trace!("Locked shared state in `window_will_enter_fullscreen`"); @@ -459,6 +463,9 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { /// Invoked when before exit fullscreen extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) { trace!("Triggered `windowWillExitFullScreen:`"); + + INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst); + with_state(this, |state| { state.with_window(|window| { trace!("Locked shared state in `window_will_exit_fullscreen`"); @@ -492,6 +499,8 @@ extern "C" fn window_will_use_fullscreen_presentation_options( /// Invoked when entered fullscreen extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { + INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst); + trace!("Triggered `windowDidEnterFullscreen:`"); with_state(this, |state| { state.initial_fullscreen = false; @@ -512,6 +521,8 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { /// Invoked when exited fullscreen extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { + INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst); + trace!("Triggered `windowDidExitFullscreen:`"); with_state(this, |state| { state.with_window(|window| { From c1ea0dde92e1de9e029e7fbb2485a454aa81b918 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Mon, 15 Jun 2020 09:15:27 +0200 Subject: [PATCH 150/239] On Unix, add option to pick backends Add features 'x11' and 'wayland' to pick backends on Linux/BSD, with both enabled by default. Fixes #774. --- CHANGELOG.md | 1 + Cargo.toml | 9 +- README.md | 2 + build.rs | 21 + src/platform/unix.rs | 93 ++++- src/platform_impl/linux/mod.rs | 375 +++++++++--------- src/platform_impl/linux/wayland/event_loop.rs | 4 + src/platform_impl/linux/wayland/window.rs | 2 + src/platform_impl/linux/x11/mod.rs | 13 + src/platform_impl/linux/x11/window.rs | 1 + 10 files changed, 322 insertions(+), 199 deletions(-) create mode 100644 build.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 808a3c377d..c586be8d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas diff --git a/Cargo.toml b/Cargo.toml index 2f4903d9f2..017e5927ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,11 @@ default-target = "x86_64-unknown-linux-gnu" targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "wasm32-unknown-unknown"] [features] +default = ["x11", "wayland"] web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"] stdweb = ["std_web", "instant/stdweb"] +x11 = ["x11-dl"] +wayland = ["wayland-client", "smithay-client-toolkit"] [dependencies] instant = "0.1" @@ -78,11 +81,11 @@ features = [ ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] -wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] } +wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] , optional = true } mio = "0.6" mio-extras = "2.0" -smithay-client-toolkit = "^0.6.6" -x11-dl = "2.18.5" +smithay-client-toolkit = { version = "^0.6.6", optional = true } +x11-dl = { version = "2.18.5", optional = true } percent-encoding = "2.0" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot] diff --git a/README.md b/README.md index 5e489631c6..c7b312f6b8 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ Winit is only officially supported on the latest stable version of the Rust comp Winit provides the following features, which can be enabled in your `Cargo.toml` file: * `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde). +* `x11` (enabled by default): On Unix platform, compiles with the X11 backend +* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend ### Platform-specific usage diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..f1c4e9bf89 --- /dev/null +++ b/build.rs @@ -0,0 +1,21 @@ +#[cfg(all( + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + not(feature = "x11"), + not(feature = "wayland") +))] +compile_error!("at least one of the \"x11\"/\"wayland\" features must be enabled"); + +#[cfg(all( + target_arch = "wasm32", + not(feature = "web-sys"), + not(feature = "stdweb") +))] +compile_error!("at least one of the \"web-sys\"/\"stdweb\" features must be enabled"); + +fn main() {} diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 6ab2d7dff4..13346a86cd 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -1,37 +1,46 @@ #![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] -use std::{os::raw, ptr, sync::Arc}; +use std::os::raw; +#[cfg(feature = "x11")] +use std::{ptr, sync::Arc}; +#[cfg(feature = "wayland")] use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme}; use crate::{ - dpi::Size, event_loop::{EventLoop, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; +#[cfg(feature = "x11")] +use crate::dpi::Size; +#[cfg(feature = "x11")] +use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection}; use crate::platform_impl::{ - x11::{ffi::XVisualInfo, XConnection}, EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget, Window as LinuxWindow, }; // TODO: stupid hack so that glutin can do its work #[doc(hidden)] +#[cfg(feature = "x11")] pub use crate::platform_impl::x11; - +#[cfg(feature = "x11")] pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported}; /// Additional methods on `EventLoopWindowTarget` that are specific to Unix. pub trait EventLoopWindowTargetExtUnix { /// True if the `EventLoopWindowTarget` uses Wayland. + #[cfg(feature = "wayland")] fn is_wayland(&self) -> bool; - /// + /// True if the `EventLoopWindowTarget` uses X11. + #[cfg(feature = "x11")] fn is_x11(&self) -> bool; #[doc(hidden)] + #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option>; /// Returns a pointer to the `wl_display` object of wayland that is used by this @@ -40,35 +49,42 @@ pub trait EventLoopWindowTargetExtUnix { /// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example). /// /// The pointer will become invalid when the winit `EventLoop` is destroyed. + #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void>; } impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { #[inline] + #[cfg(feature = "wayland")] fn is_wayland(&self) -> bool { self.p.is_wayland() } #[inline] + #[cfg(feature = "x11")] fn is_x11(&self) -> bool { !self.p.is_wayland() } #[inline] #[doc(hidden)] + #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option> { match self.p { LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.p { LinuxEventLoopWindowTarget::Wayland(ref p) => { Some(p.display().get_display_ptr() as *mut _) } + #[cfg(feature = "x11")] _ => None, } } @@ -82,6 +98,7 @@ pub trait EventLoopExtUnix { /// /// If called outside the main thread. To initialize an X11 event loop outside /// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread). + #[cfg(feature = "x11")] fn new_x11() -> Result where Self: Sized; @@ -92,6 +109,7 @@ pub trait EventLoopExtUnix { /// /// If called outside the main thread. To initialize a Wayland event loop outside /// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread). + #[cfg(feature = "wayland")] fn new_wayland() -> Self where Self: Sized; @@ -108,6 +126,7 @@ pub trait EventLoopExtUnix { /// /// This method bypasses the cross-platform compatibility requirement /// that `EventLoop` be created on the main thread. + #[cfg(feature = "x11")] fn new_x11_any_thread() -> Result where Self: Sized; @@ -116,6 +135,7 @@ pub trait EventLoopExtUnix { /// /// This method bypasses the cross-platform compatibility requirement /// that `EventLoop` be created on the main thread. + #[cfg(feature = "wayland")] fn new_wayland_any_thread() -> Self where Self: Sized; @@ -135,11 +155,13 @@ impl EventLoopExtUnix for EventLoop { } #[inline] + #[cfg(feature = "x11")] fn new_x11_any_thread() -> Result { LinuxEventLoop::new_x11_any_thread().map(wrap_ev) } #[inline] + #[cfg(feature = "wayland")] fn new_wayland_any_thread() -> Self { wrap_ev( LinuxEventLoop::new_wayland_any_thread() @@ -149,11 +171,13 @@ impl EventLoopExtUnix for EventLoop { } #[inline] + #[cfg(feature = "x11")] fn new_x11() -> Result { LinuxEventLoop::new_x11().map(wrap_ev) } #[inline] + #[cfg(feature = "wayland")] fn new_wayland() -> Self { wrap_ev( LinuxEventLoop::new_wayland() @@ -168,6 +192,7 @@ pub trait WindowExtUnix { /// Returns the ID of the `Window` xlib object that is used by this window. /// /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). + #[cfg(feature = "x11")] fn xlib_window(&self) -> Option; /// Returns a pointer to the `Display` object of xlib that is used by this window. @@ -175,14 +200,18 @@ pub trait WindowExtUnix { /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. + #[cfg(feature = "x11")] fn xlib_display(&self) -> Option<*mut raw::c_void>; + #[cfg(feature = "x11")] fn xlib_screen_id(&self) -> Option; #[doc(hidden)] + #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option>; /// Set window urgency hint (`XUrgencyHint`). Only relevant on X. + #[cfg(feature = "x11")] fn set_urgent(&self, is_urgent: bool); /// This function returns the underlying `xcb_connection_t` of an xlib `Display`. @@ -190,6 +219,7 @@ pub trait WindowExtUnix { /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. + #[cfg(feature = "x11")] fn xcb_connection(&self) -> Option<*mut raw::c_void>; /// Returns a pointer to the `wl_surface` object of wayland that is used by this window. @@ -197,6 +227,7 @@ pub trait WindowExtUnix { /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. + #[cfg(feature = "wayland")] fn wayland_surface(&self) -> Option<*mut raw::c_void>; /// Returns a pointer to the `wl_display` object of wayland that is used by this window. @@ -204,9 +235,11 @@ pub trait WindowExtUnix { /// Returns `None` if the window doesn't use wayland (if it uses xlib for example). /// /// The pointer will become invalid when the glutin `Window` is destroyed. + #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void>; /// Sets the color theme of the client side window decorations on wayland + #[cfg(feature = "wayland")] fn set_wayland_theme(&self, theme: T); /// Check if the window is ready for drawing @@ -221,73 +254,92 @@ pub trait WindowExtUnix { impl WindowExtUnix for Window { #[inline] + #[cfg(feature = "x11")] fn xlib_window(&self) -> Option { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_window()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "x11")] fn xlib_display(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_display()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "x11")] fn xlib_screen_id(&self) -> Option { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_screen_id()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] #[doc(hidden)] + #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option> { match self.window { LinuxWindow::X(ref w) => Some(w.xlib_xconnection()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "x11")] fn set_urgent(&self, is_urgent: bool) { - if let LinuxWindow::X(ref w) = self.window { - w.set_urgent(is_urgent); + match self.window { + LinuxWindow::X(ref w) => w.set_urgent(is_urgent), + #[cfg(feature = "wayland")] + _ => (), } } #[inline] + #[cfg(feature = "x11")] fn xcb_connection(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::X(ref w) => Some(w.xcb_connection()), + #[cfg(feature = "wayland")] _ => None, } } #[inline] + #[cfg(feature = "wayland")] fn wayland_surface(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _), + #[cfg(feature = "x11")] _ => None, } } #[inline] + #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.window { LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _), + #[cfg(feature = "x11")] _ => None, } } #[inline] + #[cfg(feature = "wayland")] fn set_wayland_theme(&self, theme: T) { match self.window { LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)), + #[cfg(feature = "x11")] _ => {} } } @@ -300,20 +352,28 @@ impl WindowExtUnix for Window { /// Additional methods on `WindowBuilder` that are specific to Unix. pub trait WindowBuilderExtUnix { + #[cfg(feature = "x11")] fn with_x11_visual(self, visual_infos: *const T) -> Self; + #[cfg(feature = "x11")] fn with_x11_screen(self, screen_id: i32) -> Self; /// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11. + #[cfg(feature = "x11")] fn with_class(self, class: String, instance: String) -> Self; /// Build window with override-redirect flag; defaults to false. Only relevant on X11. + #[cfg(feature = "x11")] fn with_override_redirect(self, override_redirect: bool) -> Self; /// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11. + #[cfg(feature = "x11")] fn with_x11_window_type(self, x11_window_type: Vec) -> Self; /// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11. + #[cfg(feature = "x11")] fn with_gtk_theme_variant(self, variant: String) -> Self; /// Build window with resize increment hint. Only implemented on X11. + #[cfg(feature = "x11")] fn with_resize_increments>(self, increments: S) -> Self; /// Build window with base size hint. Only implemented on X11. + #[cfg(feature = "x11")] fn with_base_size>(self, base_size: S) -> Self; /// Build window with a given application ID. It should match the `.desktop` file distributed with @@ -321,60 +381,72 @@ pub trait WindowBuilderExtUnix { /// /// For details about application ID conventions, see the /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) + #[cfg(feature = "wayland")] fn with_app_id(self, app_id: String) -> Self; } impl WindowBuilderExtUnix for WindowBuilder { #[inline] + #[cfg(feature = "x11")] fn with_x11_visual(mut self, visual_infos: *const T) -> Self { - self.platform_specific.visual_infos = - Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) }); + { + self.platform_specific.visual_infos = + Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) }); + } self } #[inline] + #[cfg(feature = "x11")] fn with_x11_screen(mut self, screen_id: i32) -> Self { self.platform_specific.screen_id = Some(screen_id); self } #[inline] + #[cfg(feature = "x11")] fn with_class(mut self, instance: String, class: String) -> Self { self.platform_specific.class = Some((instance, class)); self } #[inline] + #[cfg(feature = "x11")] fn with_override_redirect(mut self, override_redirect: bool) -> Self { self.platform_specific.override_redirect = override_redirect; self } #[inline] + #[cfg(feature = "x11")] fn with_x11_window_type(mut self, x11_window_types: Vec) -> Self { self.platform_specific.x11_window_types = x11_window_types; self } #[inline] + #[cfg(feature = "x11")] fn with_gtk_theme_variant(mut self, variant: String) -> Self { self.platform_specific.gtk_theme_variant = Some(variant); self } #[inline] + #[cfg(feature = "x11")] fn with_resize_increments>(mut self, increments: S) -> Self { self.platform_specific.resize_increments = Some(increments.into()); self } #[inline] + #[cfg(feature = "x11")] fn with_base_size>(mut self, base_size: S) -> Self { self.platform_specific.base_size = Some(base_size.into()); self } #[inline] + #[cfg(feature = "wayland")] fn with_app_id(mut self, app_id: String) -> Self { self.platform_specific.app_id = Some(app_id); self @@ -395,6 +467,7 @@ impl MonitorHandleExtUnix for MonitorHandle { } /// Wrapper for implementing SCTK's theme trait. +#[cfg(feature = "wayland")] struct WaylandTheme(T); pub trait Theme: Send + 'static { @@ -432,6 +505,7 @@ pub trait Theme: Send + 'static { } } +#[cfg(feature = "wayland")] impl SCTKTheme for WaylandTheme { fn get_primary_color(&self, active: bool) -> [u8; 4] { self.0.primary_color(active) @@ -478,6 +552,7 @@ pub enum ButtonState { Disabled, } +#[cfg(feature = "wayland")] impl ButtonState { fn from_sctk(button_state: SCTKButtonState) -> Self { match button_state { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 4713038f38..613d0e231b 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -1,12 +1,27 @@ -#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] - -use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc}; - +#![cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] + +#[cfg(all(not(feature = "x11"), not(feature = "wayland")))] +compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); + +use std::{collections::VecDeque, env, fmt}; +#[cfg(feature = "x11")] +use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; + +#[cfg(feature = "x11")] use parking_lot::Mutex; use raw_window_handle::RawWindowHandle; +#[cfg(feature = "wayland")] use smithay_client_toolkit::reexports::client::ConnectError; +#[cfg(feature = "x11")] pub use self::x11::XNotSupported; +#[cfg(feature = "x11")] use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError}; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, @@ -20,7 +35,9 @@ use crate::{ pub(crate) use crate::icon::RgbaIcon as PlatformIcon; +#[cfg(feature = "wayland")] pub mod wayland; +#[cfg(feature = "x11")] pub mod x11; /// Environment variable specifying which backend should be used on unix platform. @@ -34,33 +51,52 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { + #[cfg(feature = "x11")] pub visual_infos: Option, + #[cfg(feature = "x11")] pub screen_id: Option, + #[cfg(feature = "x11")] pub resize_increments: Option, + #[cfg(feature = "x11")] pub base_size: Option, + #[cfg(feature = "x11")] pub class: Option<(String, String)>, + #[cfg(feature = "x11")] pub override_redirect: bool, + #[cfg(feature = "x11")] pub x11_window_types: Vec, + #[cfg(feature = "x11")] pub gtk_theme_variant: Option, + #[cfg(feature = "wayland")] pub app_id: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { fn default() -> Self { Self { + #[cfg(feature = "x11")] visual_infos: None, + #[cfg(feature = "x11")] screen_id: None, + #[cfg(feature = "x11")] resize_increments: None, + #[cfg(feature = "x11")] base_size: None, + #[cfg(feature = "x11")] class: None, + #[cfg(feature = "x11")] override_redirect: false, + #[cfg(feature = "x11")] x11_window_types: vec![XWindowType::Normal], + #[cfg(feature = "x11")] gtk_theme_variant: None, + #[cfg(feature = "wayland")] app_id: None, } } } +#[cfg(feature = "x11")] lazy_static! { pub static ref X11_BACKEND: Mutex, XNotSupported>> = Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)); @@ -68,141 +104,159 @@ lazy_static! { #[derive(Debug, Clone)] pub enum OsError { + #[cfg(feature = "x11")] XError(XError), + #[cfg(feature = "x11")] XMisc(&'static str), } impl fmt::Display for OsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match self { - OsError::XError(e) => f.pad(&e.description), - OsError::XMisc(e) => f.pad(e), + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match *self { + #[cfg(feature = "x11")] + OsError::XError(ref e) => _f.pad(&e.description), + #[cfg(feature = "x11")] + OsError::XMisc(ref e) => _f.pad(e), } } } pub enum Window { + #[cfg(feature = "x11")] X(x11::Window), + #[cfg(feature = "wayland")] Wayland(wayland::Window), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum WindowId { + #[cfg(feature = "x11")] X(x11::WindowId), + #[cfg(feature = "wayland")] Wayland(wayland::WindowId), } impl WindowId { pub unsafe fn dummy() -> Self { - WindowId::Wayland(wayland::WindowId::dummy()) + #[cfg(feature = "wayland")] + return WindowId::Wayland(wayland::WindowId::dummy()); + #[cfg(all(not(feature = "wayland"), feature = "x11"))] + return WindowId::X(x11::WindowId::dummy()); } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DeviceId { + #[cfg(feature = "x11")] X(x11::DeviceId), + #[cfg(feature = "wayland")] Wayland(wayland::DeviceId), } impl DeviceId { pub unsafe fn dummy() -> Self { - DeviceId::Wayland(wayland::DeviceId::dummy()) + #[cfg(feature = "wayland")] + return DeviceId::Wayland(wayland::DeviceId::dummy()); + #[cfg(all(not(feature = "wayland"), feature = "x11"))] + return DeviceId::X(x11::DeviceId::dummy()); } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum MonitorHandle { + #[cfg(feature = "x11")] X(x11::MonitorHandle), + #[cfg(feature = "wayland")] Wayland(wayland::MonitorHandle), } +/// `x11_or_wayland!(match expr; Enum(foo) => foo.something())` +/// expands to the equivalent of +/// ```ignore +/// match self { +/// Enum::X(foo) => foo.something(), +/// Enum::Wayland(foo) => foo.something(), +/// } +/// ``` +/// The result can be converted to another enum by adding `; as AnotherEnum` +macro_rules! x11_or_wayland { + (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => { + match $what { + #[cfg(feature = "x11")] + $enum::X($($c1)*) => $enum2::X($x), + #[cfg(feature = "wayland")] + $enum::Wayland($($c1)*) => $enum2::Wayland($x), + } + }; + (match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => { + match $what { + #[cfg(feature = "x11")] + $enum::X($($c1)*) => $x, + #[cfg(feature = "wayland")] + $enum::Wayland($($c1)*) => $x, + } + }; +} + impl MonitorHandle { #[inline] pub fn name(&self) -> Option { - match self { - &MonitorHandle::X(ref m) => m.name(), - &MonitorHandle::Wayland(ref m) => m.name(), - } + x11_or_wayland!(match self; MonitorHandle(m) => m.name()) } #[inline] pub fn native_identifier(&self) -> u32 { - match self { - &MonitorHandle::X(ref m) => m.native_identifier(), - &MonitorHandle::Wayland(ref m) => m.native_identifier(), - } + x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier()) } #[inline] pub fn size(&self) -> PhysicalSize { - match self { - &MonitorHandle::X(ref m) => m.size(), - &MonitorHandle::Wayland(ref m) => m.size(), - } + x11_or_wayland!(match self; MonitorHandle(m) => m.size()) } #[inline] pub fn position(&self) -> PhysicalPosition { - match self { - &MonitorHandle::X(ref m) => m.position(), - &MonitorHandle::Wayland(ref m) => m.position(), - } + x11_or_wayland!(match self; MonitorHandle(m) => m.position()) } #[inline] pub fn scale_factor(&self) -> f64 { - match self { - &MonitorHandle::X(ref m) => m.scale_factor(), - &MonitorHandle::Wayland(ref m) => m.scale_factor() as f64, - } + x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64) } #[inline] pub fn video_modes(&self) -> Box> { - match self { - MonitorHandle::X(m) => Box::new(m.video_modes()), - MonitorHandle::Wayland(m) => Box::new(m.video_modes()), - } + x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes())) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum VideoMode { + #[cfg(feature = "x11")] X(x11::VideoMode), + #[cfg(feature = "wayland")] Wayland(wayland::VideoMode), } impl VideoMode { #[inline] pub fn size(&self) -> PhysicalSize { - match self { - &VideoMode::X(ref m) => m.size(), - &VideoMode::Wayland(ref m) => m.size(), - } + x11_or_wayland!(match self; VideoMode(m) => m.size()) } #[inline] pub fn bit_depth(&self) -> u16 { - match self { - &VideoMode::X(ref m) => m.bit_depth(), - &VideoMode::Wayland(ref m) => m.bit_depth(), - } + x11_or_wayland!(match self; VideoMode(m) => m.bit_depth()) } #[inline] pub fn refresh_rate(&self) -> u16 { - match self { - &VideoMode::X(ref m) => m.refresh_rate(), - &VideoMode::Wayland(ref m) => m.refresh_rate(), - } + x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate()) } #[inline] pub fn monitor(&self) -> RootMonitorHandle { - match self { - &VideoMode::X(ref m) => m.monitor(), - &VideoMode::Wayland(ref m) => m.monitor(), - } + x11_or_wayland!(match self; VideoMode(m) => m.monitor()) } } @@ -214,9 +268,11 @@ impl Window { pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { match *window_target { + #[cfg(feature = "wayland")] EventLoopWindowTarget::Wayland(ref window_target) => { wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland) } + #[cfg(feature = "x11")] EventLoopWindowTarget::X(ref window_target) => { x11::Window::new(window_target, attribs, pl_attribs).map(Window::X) } @@ -225,232 +281,166 @@ impl Window { #[inline] pub fn id(&self) -> WindowId { - match self { - &Window::X(ref w) => WindowId::X(w.id()), - &Window::Wayland(ref w) => WindowId::Wayland(w.id()), - } + x11_or_wayland!(match self; Window(w) => w.id(); as WindowId) } #[inline] pub fn set_title(&self, title: &str) { - match self { - &Window::X(ref w) => w.set_title(title), - &Window::Wayland(ref w) => w.set_title(title), - } + x11_or_wayland!(match self; Window(w) => w.set_title(title)); } #[inline] pub fn set_visible(&self, visible: bool) { - match self { - &Window::X(ref w) => w.set_visible(visible), - &Window::Wayland(ref w) => w.set_visible(visible), - } + x11_or_wayland!(match self; Window(w) => w.set_visible(visible)) } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { - match self { - &Window::X(ref w) => w.outer_position(), - &Window::Wayland(ref w) => w.outer_position(), - } + x11_or_wayland!(match self; Window(w) => w.outer_position()) } #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { - match self { - &Window::X(ref m) => m.inner_position(), - &Window::Wayland(ref m) => m.inner_position(), - } + x11_or_wayland!(match self; Window(w) => w.inner_position()) } #[inline] pub fn set_outer_position(&self, position: Position) { - match self { - &Window::X(ref w) => w.set_outer_position(position), - &Window::Wayland(ref w) => w.set_outer_position(position), - } + x11_or_wayland!(match self; Window(w) => w.set_outer_position(position)) } #[inline] pub fn inner_size(&self) -> PhysicalSize { - match self { - &Window::X(ref w) => w.inner_size(), - &Window::Wayland(ref w) => w.inner_size(), - } + x11_or_wayland!(match self; Window(w) => w.inner_size()) } #[inline] pub fn outer_size(&self) -> PhysicalSize { - match self { - &Window::X(ref w) => w.outer_size(), - &Window::Wayland(ref w) => w.outer_size(), - } + x11_or_wayland!(match self; Window(w) => w.outer_size()) } #[inline] pub fn set_inner_size(&self, size: Size) { - match self { - &Window::X(ref w) => w.set_inner_size(size), - &Window::Wayland(ref w) => w.set_inner_size(size), - } + x11_or_wayland!(match self; Window(w) => w.set_inner_size(size)) } #[inline] pub fn set_min_inner_size(&self, dimensions: Option) { - match self { - &Window::X(ref w) => w.set_min_inner_size(dimensions), - &Window::Wayland(ref w) => w.set_min_inner_size(dimensions), - } + x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions)) } #[inline] pub fn set_max_inner_size(&self, dimensions: Option) { - match self { - &Window::X(ref w) => w.set_max_inner_size(dimensions), - &Window::Wayland(ref w) => w.set_max_inner_size(dimensions), - } + x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions)) } #[inline] pub fn set_resizable(&self, resizable: bool) { - match self { - &Window::X(ref w) => w.set_resizable(resizable), - &Window::Wayland(ref w) => w.set_resizable(resizable), - } + x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable)) } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - match self { - &Window::X(ref w) => w.set_cursor_icon(cursor), - &Window::Wayland(ref w) => w.set_cursor_icon(cursor), - } + x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor)) } #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - match self { - &Window::X(ref window) => window.set_cursor_grab(grab), - &Window::Wayland(ref window) => window.set_cursor_grab(grab), - } + x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(grab)) } #[inline] pub fn set_cursor_visible(&self, visible: bool) { - match self { - &Window::X(ref window) => window.set_cursor_visible(visible), - &Window::Wayland(ref window) => window.set_cursor_visible(visible), - } + x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible)) } #[inline] pub fn scale_factor(&self) -> f64 { - match self { - &Window::X(ref w) => w.scale_factor(), - &Window::Wayland(ref w) => w.scale_factor() as f64, - } + x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64) } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - match self { - &Window::X(ref w) => w.set_cursor_position(position), - &Window::Wayland(ref w) => w.set_cursor_position(position), - } + x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position)) } #[inline] pub fn set_maximized(&self, maximized: bool) { - match self { - &Window::X(ref w) => w.set_maximized(maximized), - &Window::Wayland(ref w) => w.set_maximized(maximized), - } + x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized)) } #[inline] pub fn set_minimized(&self, minimized: bool) { - match self { - &Window::X(ref w) => w.set_minimized(minimized), - &Window::Wayland(ref w) => w.set_minimized(minimized), - } + x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized)) } #[inline] pub fn fullscreen(&self) -> Option { - match self { - &Window::X(ref w) => w.fullscreen(), - &Window::Wayland(ref w) => w.fullscreen(), - } + x11_or_wayland!(match self; Window(w) => w.fullscreen()) } #[inline] pub fn set_fullscreen(&self, monitor: Option) { - match self { - &Window::X(ref w) => w.set_fullscreen(monitor), - &Window::Wayland(ref w) => w.set_fullscreen(monitor), - } + x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor)) } #[inline] pub fn set_decorations(&self, decorations: bool) { - match self { - &Window::X(ref w) => w.set_decorations(decorations), - &Window::Wayland(ref w) => w.set_decorations(decorations), - } + x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations)) } #[inline] - pub fn set_always_on_top(&self, always_on_top: bool) { + pub fn set_always_on_top(&self, _always_on_top: bool) { match self { - &Window::X(ref w) => w.set_always_on_top(always_on_top), - &Window::Wayland(_) => (), + #[cfg(feature = "x11")] + &Window::X(ref w) => w.set_always_on_top(_always_on_top), + #[cfg(feature = "wayland")] + _ => (), } } #[inline] - pub fn set_window_icon(&self, window_icon: Option) { + pub fn set_window_icon(&self, _window_icon: Option) { match self { - &Window::X(ref w) => w.set_window_icon(window_icon), - &Window::Wayland(_) => (), + #[cfg(feature = "x11")] + &Window::X(ref w) => w.set_window_icon(_window_icon), + #[cfg(feature = "wayland")] + _ => (), } } #[inline] - pub fn set_ime_position(&self, position: Position) { + pub fn set_ime_position(&self, _position: Position) { match self { - &Window::X(ref w) => w.set_ime_position(position), - &Window::Wayland(_) => (), + #[cfg(feature = "x11")] + &Window::X(ref w) => w.set_ime_position(_position), + #[cfg(feature = "wayland")] + _ => (), } } #[inline] pub fn request_redraw(&self) { - match self { - &Window::X(ref w) => w.request_redraw(), - &Window::Wayland(ref w) => w.request_redraw(), - } + x11_or_wayland!(match self; Window(w) => w.request_redraw()) } #[inline] pub fn current_monitor(&self) -> RootMonitorHandle { - match self { - &Window::X(ref window) => RootMonitorHandle { - inner: MonitorHandle::X(window.current_monitor()), - }, - &Window::Wayland(ref window) => RootMonitorHandle { - inner: MonitorHandle::Wayland(window.current_monitor()), - }, + RootMonitorHandle { + inner: x11_or_wayland!(match self; Window(window) => window.current_monitor(); as MonitorHandle), } } #[inline] pub fn available_monitors(&self) -> VecDeque { match self { + #[cfg(feature = "x11")] &Window::X(ref window) => window .available_monitors() .into_iter() .map(MonitorHandle::X) .collect(), + #[cfg(feature = "wayland")] &Window::Wayland(ref window) => window .available_monitors() .into_iter() @@ -461,20 +451,20 @@ impl Window { #[inline] pub fn primary_monitor(&self) -> MonitorHandle { - match self { - &Window::X(ref window) => MonitorHandle::X(window.primary_monitor()), - &Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()), - } + x11_or_wayland!(match self; Window(window) => window.primary_monitor(); as MonitorHandle) } pub fn raw_window_handle(&self) -> RawWindowHandle { match self { + #[cfg(feature = "x11")] &Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()), + #[cfg(feature = "wayland")] &Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()), } } } +#[cfg(feature = "x11")] unsafe extern "C" fn x_error_callback( display: *mut x11::ffi::Display, event: *mut x11::ffi::XErrorEvent, @@ -508,21 +498,22 @@ unsafe extern "C" fn x_error_callback( } pub enum EventLoop { + #[cfg(feature = "wayland")] Wayland(wayland::EventLoop), + #[cfg(feature = "x11")] X(x11::EventLoop), } pub enum EventLoopProxy { + #[cfg(feature = "x11")] X(x11::EventLoopProxy), + #[cfg(feature = "wayland")] Wayland(wayland::EventLoopProxy), } impl Clone for EventLoopProxy { fn clone(&self) -> Self { - match self { - EventLoopProxy::X(proxy) => EventLoopProxy::X(proxy.clone()), - EventLoopProxy::Wayland(proxy) => EventLoopProxy::Wayland(proxy.clone()), - } + x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy) } } @@ -538,12 +529,18 @@ impl EventLoop { match env_var.as_str() { "x11" => { // TODO: propagate + #[cfg(feature = "x11")] return EventLoop::new_x11_any_thread() .expect("Failed to initialize X11 backend"); + #[cfg(not(feature = "x11"))] + panic!("x11 feature is not enabled") } "wayland" => { + #[cfg(feature = "wayland")] return EventLoop::new_wayland_any_thread() .expect("Failed to initialize Wayland backend"); + #[cfg(not(feature = "wayland"))] + panic!("wayland feature is not enabled"); } _ => panic!( "Unknown environment variable value for {}, try one of `x11`,`wayland`", @@ -552,16 +549,23 @@ impl EventLoop { } } + #[cfg(feature = "wayland")] let wayland_err = match EventLoop::new_wayland_any_thread() { Ok(event_loop) => return event_loop, Err(err) => err, }; + #[cfg(feature = "x11")] let x11_err = match EventLoop::new_x11_any_thread() { Ok(event_loop) => return event_loop, Err(err) => err, }; + #[cfg(not(feature = "wayland"))] + let wayland_err = "backend disabled"; + #[cfg(not(feature = "x11"))] + let x11_err = "backend disabled"; + let err_string = format!( "Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}", wayland_err, x11_err, @@ -569,22 +573,26 @@ impl EventLoop { panic!(err_string); } + #[cfg(feature = "wayland")] pub fn new_wayland() -> Result, ConnectError> { assert_is_main_thread("new_wayland_any_thread"); EventLoop::new_wayland_any_thread() } + #[cfg(feature = "wayland")] pub fn new_wayland_any_thread() -> Result, ConnectError> { wayland::EventLoop::new().map(EventLoop::Wayland) } + #[cfg(feature = "x11")] pub fn new_x11() -> Result, XNotSupported> { assert_is_main_thread("new_x11_any_thread"); EventLoop::new_x11_any_thread() } + #[cfg(feature = "x11")] pub fn new_x11_any_thread() -> Result, XNotSupported> { let xconn = match X11_BACKEND.lock().as_ref() { Ok(xconn) => xconn.clone(), @@ -597,11 +605,13 @@ impl EventLoop { #[inline] pub fn available_monitors(&self) -> VecDeque { match *self { + #[cfg(feature = "wayland")] EventLoop::Wayland(ref evlp) => evlp .available_monitors() .into_iter() .map(MonitorHandle::Wayland) .collect(), + #[cfg(feature = "x11")] EventLoop::X(ref evlp) => evlp .x_connection() .available_monitors() @@ -614,57 +624,46 @@ impl EventLoop { #[inline] pub fn primary_monitor(&self) -> MonitorHandle { match *self { + #[cfg(feature = "wayland")] EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()), + #[cfg(feature = "x11")] EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()), } } pub fn create_proxy(&self) -> EventLoopProxy { - match *self { - EventLoop::Wayland(ref evlp) => EventLoopProxy::Wayland(evlp.create_proxy()), - EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()), - } + x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } pub fn run_return(&mut self, callback: F) where F: FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { - match *self { - EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback), - EventLoop::X(ref mut evlp) => evlp.run_return(callback), - } + x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback)) } pub fn run(self, callback: F) -> ! where F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW, &mut ControlFlow), { - match self { - EventLoop::Wayland(evlp) => evlp.run(callback), - EventLoop::X(evlp) => evlp.run(callback), - } + x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback)) } pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { - match *self { - EventLoop::Wayland(ref evl) => evl.window_target(), - EventLoop::X(ref evl) => evl.window_target(), - } + x11_or_wayland!(match self; EventLoop(evl) => evl.window_target()) } } impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - match *self { - EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event), - EventLoopProxy::X(ref proxy) => proxy.send_event(event), - } + x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event)) } } pub enum EventLoopWindowTarget { + #[cfg(feature = "wayland")] Wayland(wayland::EventLoopWindowTarget), + #[cfg(feature = "x11")] X(x11::EventLoopWindowTarget), } @@ -672,8 +671,10 @@ impl EventLoopWindowTarget { #[inline] pub fn is_wayland(&self) -> bool { match *self { + #[cfg(feature = "wayland")] EventLoopWindowTarget::Wayland(_) => true, - EventLoopWindowTarget::X(_) => false, + #[cfg(feature = "x11")] + _ => false, } } } diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 3262fe1e59..737fcc7862 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -554,6 +554,7 @@ impl EventLoop { let instant_wakeup = { let window_target = match self.window_target.p { crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + #[cfg(feature = "x11")] _ => unreachable!(), }; let dispatched = window_target @@ -662,6 +663,7 @@ impl EventLoop { { let window_target = match self.window_target.p { crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + #[cfg(feature = "x11")] _ => unreachable!(), }; window_target.store.lock().unwrap().for_each_redraw_trigger( @@ -689,6 +691,7 @@ impl EventLoop { { let window_target = match self.window_target.p { crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + #[cfg(feature = "x11")] _ => unreachable!(), }; @@ -803,6 +806,7 @@ impl EventLoop { fn get_target(target: &RootELW) -> &EventLoopWindowTarget { match target.p { crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, + #[cfg(feature = "x11")] _ => unreachable!(), } } diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 5b680999a6..a0be1e9d7c 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -158,6 +158,7 @@ impl Window { Some(Fullscreen::Borderless(RootMonitorHandle { inner: PlatformMonitorHandle::Wayland(ref monitor_id), })) => frame.set_fullscreen(Some(&monitor_id.proxy)), + #[cfg(feature = "x11")] Some(Fullscreen::Borderless(_)) => unreachable!(), None => { if attributes.maximized { @@ -354,6 +355,7 @@ impl Window { .unwrap() .set_fullscreen(Some(&monitor_id.proxy)); } + #[cfg(feature = "x11")] Some(Fullscreen::Borderless(_)) => unreachable!(), None => self.frame.lock().unwrap().unset_fullscreen(), } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 93a386f222..186e9c01fe 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -426,6 +426,7 @@ impl EventLoop { pub(crate) fn get_xtarget(target: &RootELW) -> &EventLoopWindowTarget { match target.p { super::EventLoopWindowTarget::X(ref target) => target, + #[cfg(feature = "wayland")] _ => unreachable!(), } } @@ -493,9 +494,21 @@ impl<'a> Deref for DeviceInfo<'a> { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(ffi::Window); +impl WindowId { + pub unsafe fn dummy() -> Self { + WindowId(0) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId(c_int); +impl DeviceId { + pub unsafe fn dummy() -> Self { + DeviceId(0) + } +} + pub struct Window(Arc); impl Deref for Window { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index f4cb3d4536..a7e3198001 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -653,6 +653,7 @@ impl UnownedWindow { Fullscreen::Borderless(RootMonitorHandle { inner: PlatformMonitorHandle::X(ref monitor), }) => (None, monitor), + #[cfg(feature = "wayland")] _ => unreachable!(), }; From 4b1b314ce20e7f9a504cf920c5367265a6451b70 Mon Sep 17 00:00:00 2001 From: Murarth Date: Mon, 15 Jun 2020 12:49:09 -0700 Subject: [PATCH 151/239] Test x11 and wayland features on CI --- .github/workflows/ci.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b98dbc56b1..1c35451427 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,8 @@ jobs: - { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu } - { target: i686-unknown-linux-gnu, os: ubuntu-latest, } - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 } + - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: wayland } - { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' } - { target: x86_64-apple-darwin, os: macos-latest, } - { target: x86_64-apple-ios, os: macos-latest, } @@ -50,6 +52,7 @@ jobs: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0" + OPTIONS: ${{ matrix.platform.options }} FEATURES: ${{ format(',{0}', matrix.platform.features ) }} CMD: ${{ matrix.platform.cmd }} @@ -82,35 +85,35 @@ jobs: - name: Check documentation shell: bash if: matrix.platform.target != 'wasm32-unknown-unknown' - run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Build shell: bash - run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Build tests shell: bash - run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Run tests shell: bash if: ( !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features $FEATURES + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES - name: Build with serde enabled shell: bash - run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES - name: Build tests with serde enabled shell: bash - run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES - name: Run tests with serde enabled shell: bash if: ( !contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios') && !contains(matrix.platform.target, 'wasm32')) - run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES + run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES From bf62103417039827a9541f1c74793bc071295e19 Mon Sep 17 00:00:00 2001 From: Viktor Zoutman Date: Wed, 17 Jun 2020 15:55:52 +0200 Subject: [PATCH 152/239] Android run return (#1604) * Initial Draft * Minor clean up * cargo fmt * Removed accidental change * Update CHANGELOG.md Co-authored-by: VZout <=> --- CHANGELOG.md | 1 + src/platform/desktop.rs | 1 + src/platform_impl/android/mod.rs | 137 ++++++++++++++++++++++--------- 3 files changed, 100 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c586be8d49..0888252c85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. +- On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. # 0.22.2 (2020-05-16) diff --git a/src/platform/desktop.rs b/src/platform/desktop.rs index df80143162..abf58b29ad 100644 --- a/src/platform/desktop.rs +++ b/src/platform/desktop.rs @@ -1,6 +1,7 @@ #![cfg(any( target_os = "windows", target_os = "macos", + target_os = "android", target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" ))] diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index ce26994b5d..8a5c69409a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -44,6 +44,20 @@ fn poll(poll: Poll) -> Option { pub struct EventLoop { window_target: event_loop::EventLoopWindowTarget, user_queue: Arc>>, + first_event: Option, + start_cause: event::StartCause, + looper: ThreadLooper, + running: bool, +} + +macro_rules! call_event_handler { + ( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{ + if $cf != ControlFlow::Exit { + $event_handler($event, $window_target, &mut $cf); + } else { + $event_handler($event, $window_target, &mut ControlFlow::Exit); + } + }}; } impl EventLoop { @@ -56,42 +70,61 @@ impl EventLoop { _marker: std::marker::PhantomData, }, user_queue: Default::default(), + first_event: None, + start_cause: event::StartCause::Init, + looper: ThreadLooper::for_thread().unwrap(), + running: false, } } - pub fn run(self, mut event_handler: F) -> ! + pub fn run(mut self, event_handler: F) -> ! where F: 'static + FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - let mut cf = ControlFlow::default(); - let mut first_event = None; - let mut start_cause = event::StartCause::Init; - let looper = ThreadLooper::for_thread().unwrap(); - let mut running = false; - - loop { - event_handler( - event::Event::NewEvents(start_cause), + self.run_return(event_handler); + ::std::process::exit(0); + } + + pub fn run_return(&mut self, mut event_handler: F) + where + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + let mut control_flow = ControlFlow::default(); + + 'event_loop: loop { + call_event_handler!( + event_handler, self.window_target(), - &mut cf, + control_flow, + event::Event::NewEvents(self.start_cause) ); let mut redraw = false; let mut resized = false; - match first_event.take() { + match self.first_event.take() { Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { Event::WindowCreated => { - event_handler(event::Event::Resumed, self.window_target(), &mut cf); + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event::Event::Resumed + ); } Event::WindowResized => resized = true, Event::WindowRedrawNeeded => redraw = true, Event::WindowDestroyed => { - event_handler(event::Event::Suspended, self.window_target(), &mut cf); + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event::Event::Suspended + ); } - Event::Pause => running = false, - Event::Resume => running = true, + Event::Pause => self.running = false, + Event::Resume => self.running = true, Event::ConfigChanged => { let am = ndk_glue::native_activity().asset_manager(); let config = Configuration::from_asset_manager(&am); @@ -107,7 +140,12 @@ impl EventLoop { scale_factor, }, }; - event_handler(event, self.window_target(), &mut cf); + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event + ); } } _ => {} @@ -147,7 +185,12 @@ impl EventLoop { force: None, }), }; - event_handler(event, self.window_target(), &mut cf); + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event + ); } } InputEvent::KeyEvent(_) => {} // TODO @@ -160,50 +203,66 @@ impl EventLoop { Some(EventSource::User) => { let mut user_queue = self.user_queue.lock().unwrap(); while let Some(event) = user_queue.pop_front() { - event_handler( - event::Event::UserEvent(event), + call_event_handler!( + event_handler, self.window_target(), - &mut cf, + control_flow, + event::Event::UserEvent(event) ); } } - None => {} + None => { + control_flow = ControlFlow::Exit; + } } - event_handler( - event::Event::MainEventsCleared, + call_event_handler!( + event_handler, self.window_target(), - &mut cf, + control_flow, + event::Event::MainEventsCleared ); - if resized && running { + if resized && self.running { let size = MonitorHandle.size(); let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::Resized(size), }; - event_handler(event, self.window_target(), &mut cf); + call_event_handler!(event_handler, self.window_target(), control_flow, event); } - if redraw && running { + if redraw && self.running { let event = event::Event::RedrawRequested(window::WindowId(WindowId)); - event_handler(event, self.window_target(), &mut cf); + call_event_handler!(event_handler, self.window_target(), control_flow, event); } - event_handler( - event::Event::RedrawEventsCleared, + call_event_handler!( + event_handler, self.window_target(), - &mut cf, + control_flow, + event::Event::RedrawEventsCleared ); - match cf { - ControlFlow::Exit => panic!(), + match control_flow { + ControlFlow::Exit => { + self.first_event = poll( + self.looper + .poll_once_timeout(Duration::from_millis(0)) + .unwrap(), + ); + self.start_cause = event::StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + }; + break 'event_loop; + } ControlFlow::Poll => { - start_cause = event::StartCause::Poll; + self.start_cause = event::StartCause::Poll; } ControlFlow::Wait => { - first_event = poll(looper.poll_all().unwrap()); - start_cause = event::StartCause::WaitCancelled { + self.first_event = poll(self.looper.poll_all().unwrap()); + self.start_cause = event::StartCause::WaitCancelled { start: Instant::now(), requested_resume: None, } @@ -215,8 +274,8 @@ impl EventLoop { } else { instant - start }; - first_event = poll(looper.poll_all_timeout(duration).unwrap()); - start_cause = if first_event.is_some() { + self.first_event = poll(self.looper.poll_all_timeout(duration).unwrap()); + self.start_cause = if self.first_event.is_some() { event::StartCause::WaitCancelled { start, requested_resume: Some(instant), From 2191e9ecd59182e9418e53a3c0cb56421a4a6887 Mon Sep 17 00:00:00 2001 From: Andrey Lesnikov Date: Sat, 20 Jun 2020 03:42:19 +0300 Subject: [PATCH 153/239] macOS: Support click-dragging out of a window (#1607) * macos: Support click-dragging out of a window * macos: Use NSEvent::pressedMouseButtons for click-dragging * macos: Click-dragging: Move pressedMouseButtons inside --- src/platform_impl/macos/view.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 82ee50767e..d764902db0 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -915,8 +915,11 @@ fn mouse_motion(this: &Object, event: id) { || view_point.x > view_rect.size.width || view_point.y > view_rect.size.height { - // Point is outside of the client area (view) - return; + let mouse_buttons_down: NSInteger = msg_send![class!(NSEvent), pressedMouseButtons]; + if mouse_buttons_down == 0 { + // Point is outside of the client area (view) and no buttons are pressed + return; + } } let x = view_point.x as f64; From b1e22aa5597a57882e862ca03ef6440d9e5ec4dd Mon Sep 17 00:00:00 2001 From: Jurgis Date: Mon, 29 Jun 2020 01:17:27 +0300 Subject: [PATCH 154/239] Make drag and drop optional (fixes OleInitialize failure #1255) (#1524) Co-authored-by: Osspial --- CHANGELOG.md | 3 ++- src/platform/windows.rs | 14 ++++++++++++++ src/platform_impl/windows/event_loop.rs | 2 +- src/platform_impl/windows/mod.rs | 14 +++++++++++++- src/platform_impl/windows/window.rs | 13 ++++++++++--- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0888252c85..8c042e1369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas - +- On Windows, drag and drop is now optional and must be enabled with `WindowBuilderExtWindows::with_drag_and_drop(true)`. - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. - On android added support for `run_return`. @@ -23,6 +23,7 @@ - On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling - On macOS, fix `EventLoopProxy` leaking memory for every instance. +- On Windows, drag and drop can now be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`. # 0.22.0 (2020-03-09) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index a356f1e1c0..a040be363f 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -117,6 +117,14 @@ pub trait WindowBuilderExtWindows { /// This sets `WS_EX_NOREDIRECTIONBITMAP`. fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder; + + /// Enables or disables drag and drop support (enabled by default). Will interfere with other crates + /// that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` instead of + /// `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still attempt to initialize + /// COM API regardless of this option. Currently only fullscreen mode does that, but there may be more in the future. + /// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions. + /// See https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks for more information. + fn with_drag_and_drop(self, flag: bool) -> WindowBuilder; } impl WindowBuilderExtWindows for WindowBuilder { @@ -137,6 +145,12 @@ impl WindowBuilderExtWindows for WindowBuilder { self.platform_specific.no_redirection_bitmap = flag; self } + + #[inline] + fn with_drag_and_drop(mut self, flag: bool) -> WindowBuilder { + self.platform_specific.drag_and_drop = flag; + self + } } /// Additional methods on `MonitorHandle` that are specific to Windows. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index a7deac50c5..739990a67b 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -82,7 +82,7 @@ lazy_static! { pub(crate) struct SubclassInput { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, - pub file_drop_handler: FileDropHandler, + pub file_drop_handler: Option, } impl SubclassInput { diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 021c903b8d..498cdfb80c 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -14,11 +14,23 @@ pub use self::icon::WinIcon as PlatformIcon; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; -#[derive(Clone, Default)] +#[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub parent: Option, pub taskbar_icon: Option, pub no_redirection_bitmap: bool, + pub drag_and_drop: bool, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> Self { + Self { + parent: None, + taskbar_icon: None, + no_redirection_bitmap: false, + drag_and_drop: true, + } + } } unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 45a3b5debd..0f4dcf8bb8 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -70,8 +70,9 @@ impl Window { // // done. you owe me -- ossi unsafe { + let drag_and_drop = pl_attr.drag_and_drop; init(w_attr, pl_attr, event_loop).map(|win| { - let file_drop_handler = { + let file_drop_handler = if drag_and_drop { use winapi::shared::winerror::{OLE_E_WRONGCOMPOBJ, RPC_E_CHANGED_MODE, S_OK}; let ole_init_result = ole2::OleInitialize(ptr::null_mut()); @@ -80,7 +81,11 @@ impl Window { if ole_init_result == OLE_E_WRONGCOMPOBJ { panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`"); } else if ole_init_result == RPC_E_CHANGED_MODE { - panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`"); + panic!( + "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. \ + Make sure other crates are not using multithreaded COM library \ + on the same thread or disable drag and drop support." + ); } let file_drop_runner = event_loop.runner_shared.clone(); @@ -99,7 +104,9 @@ impl Window { ole2::RegisterDragDrop(win.window.0, handler_interface_ptr), S_OK ); - file_drop_handler + Some(file_drop_handler) + } else { + None }; let subclass_input = event_loop::SubclassInput { From dd866a74a690d37fdd11385741dee06e9f7cd02f Mon Sep 17 00:00:00 2001 From: Osspial Date: Thu, 2 Jul 2020 16:53:47 -0400 Subject: [PATCH 155/239] On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops (#1615) --- CHANGELOG.md | 2 + src/platform_impl/windows/event_loop.rs | 29 +++++++---- .../windows/event_loop/runner.rs | 49 ++++++++++++++++--- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c042e1369..d28b32c3f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased + - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas @@ -7,6 +8,7 @@ - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. - On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. +- On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 739990a67b..61ec167fc7 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -226,7 +226,7 @@ impl EventLoop { } unsafe { - runner.call_event_handler(Event::LoopDestroyed); + runner.loop_destroyed(); } runner.reset_runner(); } @@ -1933,14 +1933,25 @@ unsafe extern "system" fn thread_event_target_callback( // events, `handling_events` will return false and we won't emit a second // `RedrawEventsCleared` event. if subclass_input.event_loop_runner.handling_events() { - // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` - // doesn't call WM_PAINT for the thread event target (i.e. this window). - assert!(flush_paint_messages( - None, - &subclass_input.event_loop_runner - )); - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + if subclass_input.event_loop_runner.should_buffer() { + // This branch can be triggered when a nested win32 event loop is triggered + // inside of the `event_handler` callback. + winuser::RedrawWindow( + window, + ptr::null(), + ptr::null_mut(), + winuser::RDW_INTERNALPAINT, + ); + } else { + // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` + // doesn't call WM_PAINT for the thread event target (i.e. this window). + assert!(flush_paint_messages( + None, + &subclass_input.event_loop_runner + )); + subclass_input.event_loop_runner.redraw_events_cleared(); + process_control_flow(&subclass_input.event_loop_runner); + } } 0 diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index 258a40c08c..4f90966c43 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -53,6 +53,8 @@ enum RunnerState { /// The event loop is handling the redraw events and sending them to the user's callback. /// `MainEventsCleared` has been sent, and `RedrawEventsCleared` hasn't. HandlingRedrawEvents, + /// The event loop has been destroyed. No other events will be emitted. + Destroyed, } enum BufferedEvent { @@ -229,7 +231,11 @@ impl EventLoopRunner { self.move_state_to(RunnerState::Idle); } - pub(crate) unsafe fn call_event_handler(&self, event: Event<'_, T>) { + pub(crate) unsafe fn loop_destroyed(&self) { + self.move_state_to(RunnerState::Destroyed); + } + + unsafe fn call_event_handler(&self, event: Event<'_, T>) { self.catch_unwind(|| { let mut control_flow = self.control_flow.take(); let mut event_handler = self.event_handler.take() @@ -260,8 +266,8 @@ impl EventLoopRunner { } } - /// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, and `RedrawEventsCleared`) as - /// necessary to bring the internal `RunnerState` to the new runner state. + /// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, `RedrawEventsCleared`, and + /// `LoopDestroyed`) as necessary to bring the internal `RunnerState` to the new runner state. /// /// The state transitions are defined as follows: /// @@ -273,14 +279,20 @@ impl EventLoopRunner { /// ^ | /// | V /// Idle <--- HandlingRedrawEvents + /// | + /// V + /// Destroyed /// ``` /// - /// Attempting to transition back to `Uninitialized` will result in a panic. Transitioning to - /// the current state is a no-op. Even if the `new_runner_state` isn't the immediate next state - /// in the runner state machine (e.g. `self.runner_state == HandlingMainEvents` and + /// Attempting to transition back to `Uninitialized` will result in a panic. Attempting to + /// transition *from* `Destroyed` will also reuslt in a panic. Transitioning to the current + /// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the + /// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and /// `new_runner_state == Idle`), the intermediate state transitions will still be executed. unsafe fn move_state_to(&self, new_runner_state: RunnerState) { - use RunnerState::{HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized}; + use RunnerState::{ + Destroyed, HandlingMainEvents, HandlingRedrawEvents, Idle, Uninitialized, + }; match ( self.runner_state.replace(new_runner_state), @@ -289,7 +301,8 @@ impl EventLoopRunner { (Uninitialized, Uninitialized) | (Idle, Idle) | (HandlingMainEvents, HandlingMainEvents) - | (HandlingRedrawEvents, HandlingRedrawEvents) => (), + | (HandlingRedrawEvents, HandlingRedrawEvents) + | (Destroyed, Destroyed) => (), // State transitions that initialize the event loop. (Uninitialized, HandlingMainEvents) => { @@ -304,6 +317,12 @@ impl EventLoopRunner { self.call_event_handler(Event::MainEventsCleared); self.call_redraw_events_cleared(); } + (Uninitialized, Destroyed) => { + self.call_new_events(true); + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); + self.call_event_handler(Event::LoopDestroyed); + } (_, Uninitialized) => panic!("cannot move state to Uninitialized"), // State transitions that start the event handling process. @@ -314,6 +333,9 @@ impl EventLoopRunner { self.call_new_events(false); self.call_event_handler(Event::MainEventsCleared); } + (Idle, Destroyed) => { + self.call_event_handler(Event::LoopDestroyed); + } (HandlingMainEvents, HandlingRedrawEvents) => { self.call_event_handler(Event::MainEventsCleared); @@ -323,6 +345,11 @@ impl EventLoopRunner { self.call_event_handler(Event::MainEventsCleared); self.call_redraw_events_cleared(); } + (HandlingMainEvents, Destroyed) => { + self.call_event_handler(Event::MainEventsCleared); + self.call_redraw_events_cleared(); + self.call_event_handler(Event::LoopDestroyed); + } (HandlingRedrawEvents, Idle) => { self.call_redraw_events_cleared(); @@ -332,6 +359,12 @@ impl EventLoopRunner { self.call_redraw_events_cleared(); self.call_new_events(false); } + (HandlingRedrawEvents, Destroyed) => { + self.call_redraw_events_cleared(); + self.call_event_handler(Event::LoopDestroyed); + } + + (Destroyed, _) => panic!("cannot move state from Destroyed"), } } From 3d5d05eac7d23c78e4e66789563b3d4285e3a4eb Mon Sep 17 00:00:00 2001 From: Xavier L'Heureux Date: Sat, 4 Jul 2020 15:46:41 -0400 Subject: [PATCH 156/239] Move available_monitors and primary_monitor to EventLoopWindowTarget (#1616) --- CHANGELOG.md | 1 + src/event_loop.rs | 20 +++--- src/monitor.rs | 4 +- src/platform_impl/android/mod.rs | 22 ++++--- src/platform_impl/ios/event_loop.rs | 22 ++++--- src/platform_impl/linux/mod.rs | 62 ++++++++++--------- src/platform_impl/linux/wayland/event_loop.rs | 23 +++---- src/platform_impl/linux/x11/mod.rs | 4 -- src/platform_impl/macos/event_loop.rs | 22 ++++--- src/platform_impl/web/event_loop/mod.rs | 8 --- .../web/event_loop/window_target.rs | 11 +++- src/platform_impl/windows/event_loop.rs | 15 ++++- src/platform_impl/windows/monitor.rs | 13 +--- src/window.rs | 4 +- 14 files changed, 121 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d28b32c3f9..2fb9be9ec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop. - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas diff --git a/src/event_loop.rs b/src/event_loop.rs index 224292a564..a523c01aed 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -155,11 +155,20 @@ impl EventLoop { event_loop_proxy: self.event_loop.create_proxy(), } } +} + +impl Deref for EventLoop { + type Target = EventLoopWindowTarget; + fn deref(&self) -> &EventLoopWindowTarget { + self.event_loop.window_target() + } +} +impl EventLoopWindowTarget { /// Returns the list of all the monitors available on the system. #[inline] pub fn available_monitors(&self) -> impl Iterator { - self.event_loop + self.p .available_monitors() .into_iter() .map(|inner| MonitorHandle { inner }) @@ -169,18 +178,11 @@ impl EventLoop { #[inline] pub fn primary_monitor(&self) -> MonitorHandle { MonitorHandle { - inner: self.event_loop.primary_monitor(), + inner: self.p.primary_monitor(), } } } -impl Deref for EventLoop { - type Target = EventLoopWindowTarget; - fn deref(&self) -> &EventLoopWindowTarget { - self.event_loop.window_target() - } -} - /// Used to send custom events to `EventLoop`. pub struct EventLoopProxy { event_loop_proxy: platform_impl::EventLoopProxy, diff --git a/src/monitor.rs b/src/monitor.rs index 8977c11a4a..126b03e2bf 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -3,11 +3,11 @@ //! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle] //! type. This is retreived from one of the following methods, which return an iterator of //! [`MonitorHandle`][monitor_handle]: -//! - [`EventLoop::available_monitors`][loop_get] +//! - [`EventLoopWindowTarget::available_monitors`][loop_get] //! - [`Window::available_monitors`][window_get]. //! //! [monitor_handle]: crate::monitor::MonitorHandle -//! [loop_get]: crate::event_loop::EventLoop::available_monitors +//! [loop_get]: crate::event_loop::EventLoopWindowTarget::available_monitors //! [window_get]: crate::window::Window::available_monitors use crate::{ dpi::{PhysicalPosition, PhysicalSize}, diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 8a5c69409a..d42b942083 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -295,16 +295,6 @@ impl EventLoop { &self.window_target } - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } - - pub fn available_monitors(&self) -> VecDeque { - let mut v = VecDeque::with_capacity(1); - v.push_back(self.primary_monitor()); - v - } - pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { queue: self.user_queue.clone(), @@ -339,6 +329,18 @@ pub struct EventLoopWindowTarget { _marker: std::marker::PhantomData, } +impl EventLoopWindowTarget { + pub fn primary_monitor(&self) -> MonitorHandle { + MonitorHandle + } + + pub fn available_monitors(&self) -> VecDeque { + let mut v = VecDeque::with_capacity(1); + v.push_back(self.primary_monitor()); + v + } +} + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct WindowId; diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 61b389d43a..2e7c64d2a3 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -49,6 +49,18 @@ pub struct EventLoopWindowTarget { sender_to_clone: Sender, } +impl EventLoopWindowTarget { + pub fn available_monitors(&self) -> VecDeque { + // guaranteed to be on main thread + unsafe { monitor::uiscreens() } + } + + pub fn primary_monitor(&self) -> MonitorHandle { + // guaranteed to be on main thread + unsafe { monitor::main_uiscreen() } + } +} + pub struct EventLoop { window_target: RootEventLoopWindowTarget, } @@ -115,16 +127,6 @@ impl EventLoop { EventLoopProxy::new(self.window_target.p.sender_to_clone.clone()) } - pub fn available_monitors(&self) -> VecDeque { - // guaranteed to be on main thread - unsafe { monitor::uiscreens() } - } - - pub fn primary_monitor(&self) -> MonitorHandle { - // guaranteed to be on main thread - unsafe { monitor::main_uiscreen() } - } - pub fn window_target(&self) -> &RootEventLoopWindowTarget { &self.window_target } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 613d0e231b..0fee68324e 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -602,35 +602,6 @@ impl EventLoop { Ok(EventLoop::X(x11::EventLoop::new(xconn))) } - #[inline] - pub fn available_monitors(&self) -> VecDeque { - match *self { - #[cfg(feature = "wayland")] - EventLoop::Wayland(ref evlp) => evlp - .available_monitors() - .into_iter() - .map(MonitorHandle::Wayland) - .collect(), - #[cfg(feature = "x11")] - EventLoop::X(ref evlp) => evlp - .x_connection() - .available_monitors() - .into_iter() - .map(MonitorHandle::X) - .collect(), - } - } - - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - match *self { - #[cfg(feature = "wayland")] - EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()), - #[cfg(feature = "x11")] - EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()), - } - } - pub fn create_proxy(&self) -> EventLoopProxy { x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy) } @@ -677,6 +648,39 @@ impl EventLoopWindowTarget { _ => false, } } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + match *self { + #[cfg(feature = "wayland")] + EventLoopWindowTarget::Wayland(ref evlp) => evlp + .available_monitors() + .into_iter() + .map(MonitorHandle::Wayland) + .collect(), + #[cfg(feature = "x11")] + EventLoopWindowTarget::X(ref evlp) => evlp + .x_connection() + .available_monitors() + .into_iter() + .map(MonitorHandle::X) + .collect(), + } + } + + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + match *self { + #[cfg(feature = "wayland")] + EventLoopWindowTarget::Wayland(ref evlp) => { + MonitorHandle::Wayland(evlp.primary_monitor()) + } + #[cfg(feature = "x11")] + EventLoopWindowTarget::X(ref evlp) => { + MonitorHandle::X(evlp.x_connection().primary_monitor()) + } + } + } } fn sticky_exit_callback( diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 737fcc7862..77215b9ce9 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -245,8 +245,6 @@ pub struct EventLoop { poll: Poll, // The wayland display pub display: Arc, - // The output manager - pub outputs: OutputMgr, // The cursor manager cursor_manager: Arc>, kbd_channel: Receiver>, @@ -277,6 +275,8 @@ pub struct EventLoopWindowTarget { pub display: Arc, // The list of seats pub seats: Arc>>, + // The output manager + pub outputs: OutputMgr, _marker: ::std::marker::PhantomData, } @@ -418,10 +418,10 @@ impl EventLoop { .unwrap(); let cursor_manager_clone = cursor_manager.clone(); + let outputs = env.outputs.clone(); Ok(EventLoop { poll, display: display.clone(), - outputs: env.outputs.clone(), user_sender, user_channel, kbd_channel, @@ -435,6 +435,7 @@ impl EventLoop { cleanup_needed: Arc::new(Mutex::new(false)), seats, display, + outputs, _marker: ::std::marker::PhantomData, }), _marker: ::std::marker::PhantomData, @@ -633,14 +634,6 @@ impl EventLoop { callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); } - pub fn primary_monitor(&self) -> MonitorHandle { - primary_monitor(&self.outputs) - } - - pub fn available_monitors(&self) -> VecDeque { - available_monitors(&self.outputs) - } - pub fn window_target(&self) -> &RootELW { &self.window_target } @@ -650,6 +643,14 @@ impl EventLoopWindowTarget { pub fn display(&self) -> &Display { &*self.display } + + pub fn available_monitors(&self) -> VecDeque { + available_monitors(&self.outputs) + } + + pub fn primary_monitor(&self) -> MonitorHandle { + primary_monitor(&self.outputs) + } } /* diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 186e9c01fe..0849c0f919 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -258,10 +258,6 @@ impl EventLoop { &self.target } - pub(crate) fn x_connection(&self) -> &Arc { - get_xtarget(&self.target).x_connection() - } - pub fn run_return(&mut self, mut callback: F) where F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 01437b845e..0d88d97984 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -34,6 +34,18 @@ impl Default for EventLoopWindowTarget { } } +impl EventLoopWindowTarget { + #[inline] + pub fn available_monitors(&self) -> VecDeque { + monitor::available_monitors() + } + + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + monitor::primary_monitor() + } +} + pub struct EventLoop { window_target: Rc>, _delegate: IdRef, @@ -68,16 +80,6 @@ impl EventLoop { } } - #[inline] - pub fn available_monitors(&self) -> VecDeque { - monitor::available_monitors() - } - - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - monitor::primary_monitor() - } - pub fn window_target(&self) -> &RootWindowTarget { &self.window_target } diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index 50640a1faf..fa01b5df05 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -27,14 +27,6 @@ impl EventLoop { } } - pub fn available_monitors(&self) -> VecDequeIter { - VecDeque::new().into_iter() - } - - pub fn primary_monitor(&self) -> monitor::Handle { - monitor::Handle - } - pub fn run(self, mut event_handler: F) -> ! where F: 'static diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index db485a16d3..3ba5e1d2b5 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,9 +1,10 @@ -use super::{backend, device, proxy::Proxy, runner, window}; +use super::{super::monitor, backend, device, proxy::Proxy, runner, window}; use crate::dpi::{PhysicalSize, Size}; use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; use crate::window::{Theme, WindowId}; use std::clone::Clone; +use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; pub struct WindowTarget { pub(crate) runner: runner::Shared, @@ -213,4 +214,12 @@ impl WindowTarget { }); }); } + + pub fn available_monitors(&self) -> VecDequeIter { + VecDeque::new().into_iter() + } + + pub fn primary_monitor(&self) -> monitor::Handle { + monitor::Handle + } } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 61ec167fc7..eb0a65762b 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -4,6 +4,7 @@ mod runner; use parking_lot::Mutex; use std::{ + collections::VecDeque, marker::PhantomData, mem, panic, ptr, rc::Rc, @@ -38,7 +39,8 @@ use crate::{ dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, drop_handler::FileDropHandler, event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, - monitor, raw_input, util, + monitor::{self, MonitorHandle}, + raw_input, util, window_state::{CursorFlags, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, }, @@ -247,6 +249,15 @@ impl EventLoopWindowTarget { target_window: self.thread_msg_target, } } + + // TODO: Investigate opportunities for caching + pub fn available_monitors(&self) -> VecDeque { + monitor::available_monitors() + } + + pub fn primary_monitor(&self) -> MonitorHandle { + monitor::primary_monitor() + } } fn main_thread_id() -> DWORD { @@ -851,7 +862,7 @@ unsafe extern "system" fn public_window_callback( window_pos.cy = new_monitor_rect.bottom - new_monitor_rect.top; } *fullscreen_monitor = crate::monitor::MonitorHandle { - inner: monitor::MonitorHandle::new(new_monitor), + inner: MonitorHandle::new(new_monitor), }; } } diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 7136f35dfe..7fa4c73dc5 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -11,7 +11,7 @@ use std::{ io, mem, ptr, }; -use super::{util, EventLoop}; +use super::util; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, @@ -126,17 +126,6 @@ pub fn current_monitor(hwnd: HWND) -> MonitorHandle { MonitorHandle::new(hmonitor) } -impl EventLoop { - // TODO: Investigate opportunities for caching - pub fn available_monitors(&self) -> VecDeque { - available_monitors() - } - - pub fn primary_monitor(&self) -> MonitorHandle { - primary_monitor() - } -} - impl Window { pub fn available_monitors(&self) -> VecDeque { available_monitors() diff --git a/src/window.rs b/src/window.rs index 113e7c042b..000adef735 100644 --- a/src/window.rs +++ b/src/window.rs @@ -758,7 +758,7 @@ impl Window { /// Returns the list of all the monitors available on the system. /// - /// This is the same as `EventLoop::available_monitors`, and is provided for convenience. + /// This is the same as `EventLoopWindowTarget::available_monitors`, and is provided for convenience. /// /// ## Platform-specific /// @@ -773,7 +773,7 @@ impl Window { /// Returns the primary monitor of the system. /// - /// This is the same as `EventLoop::primary_monitor`, and is provided for convenience. + /// This is the same as `EventLoopWindowTarget::primary_monitor`, and is provided for convenience. /// /// ## Platform-specific /// From 6919c2fb2d45dc4063c9ef52a69d857ed30657c7 Mon Sep 17 00:00:00 2001 From: Matt Kraai Date: Thu, 9 Jul 2020 15:08:26 +0000 Subject: [PATCH 157/239] Fix misspellings in comments (#1618) --- src/lib.rs | 2 +- src/monitor.rs | 2 +- src/platform_impl/linux/x11/window.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ce66665616..4a45022c36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ //! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a //! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired. //! -//! You can retreive events by calling [`EventLoop::run`][event_loop_run]. This function will +//! You can retrieve events by calling [`EventLoop::run`][event_loop_run]. This function will //! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and //! will run until the `control_flow` argument given to the closure is set to //! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the diff --git a/src/monitor.rs b/src/monitor.rs index 126b03e2bf..5cf680ff21 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,7 +1,7 @@ //! Types useful for interacting with a user's monitors. //! //! If you want to get basic information about a monitor, you can use the [`MonitorHandle`][monitor_handle] -//! type. This is retreived from one of the following methods, which return an iterator of +//! type. This is retrieved from one of the following methods, which return an iterator of //! [`MonitorHandle`][monitor_handle]: //! - [`EventLoopWindowTarget::available_monitors`][loop_get] //! - [`Window::available_monitors`][window_get]. diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index a7e3198001..ae63f436a4 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -227,7 +227,7 @@ impl UnownedWindow { // is > 0, like we do in glutin. // // It is non obvious which masks, if any, we should pass to - // `XGetVisualInfo`. winit doesn't recieve any info about what + // `XGetVisualInfo`. winit doesn't receive any info about what // properties the user wants. Users should consider choosing the // visual themselves as glutin does. match pl_attribs.visual_infos { From 55dff53a98097824db0460d9d7083a842f923783 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 26 Jul 2020 21:13:17 +0000 Subject: [PATCH 158/239] Fix Window platform support documentation This resolves various problems with the documentation about platform support on the Window struct. It also completely removes pointless runtime errors in favor of consistent no-ops on all platforms that do not support a certain features. --- CHANGELOG.md | 4 ++ src/platform_impl/android/mod.rs | 4 +- src/platform_impl/ios/window.rs | 2 +- src/platform_impl/web/window.rs | 6 +-- src/platform_impl/windows/window.rs | 2 +- src/window.rs | 60 ++++++++++++----------------- 6 files changed, 35 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb9be9ec4..9eba304dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,10 @@ - on macOS, fix incorrect ReceivedCharacter events for some key combinations. - **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. - On macOS, a mouse motion event is now generated before every mouse click. +- On Windows, `set_ime_position` is now a no-op instead of a runtime crash. +- On Android, `set_fullscreen` is now a no-op instead of a runtime crash. +- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. +- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. # 0.21.0 (2020-02-04) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index d42b942083..e4bdef8229 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -419,7 +419,7 @@ impl Window { } pub fn set_inner_size(&self, _size: Size) { - panic!("Cannot set window size on Android"); + warn!("Cannot set window size on Android"); } pub fn outer_size(&self) -> PhysicalSize { @@ -441,7 +441,7 @@ impl Window { pub fn set_maximized(&self, _maximized: bool) {} pub fn set_fullscreen(&self, _monitor: Option) { - panic!("Cannot set fullscreen on Android"); + warn!("Cannot set fullscreen on Android"); } pub fn fullscreen(&self) -> Option { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index ec943116db..5fa83e9027 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -142,7 +142,7 @@ impl Inner { } pub fn set_inner_size(&self, _size: Size) { - unimplemented!("not clear what `Window::set_inner_size` means on iOS"); + warn!("not clear what `Window::set_inner_size` means on iOS"); } pub fn set_min_inner_size(&self, _dimensions: Option) { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index bd525056fa..58d551354b 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -171,14 +171,12 @@ impl Window { #[inline] pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> { - // Intentionally a no-op, as the web does not support setting cursor positions - Ok(()) + Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> { - // Intentionally a no-op, as the web does not (properly) support grabbing the cursor - Ok(()) + Err(ExternalError::NotSupported(NotSupportedError::new())) } #[inline] diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 0f4dcf8bb8..3b8dc98f51 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -609,7 +609,7 @@ impl Window { #[inline] pub fn set_ime_position(&self, _position: Position) { - unimplemented!(); + warn!("`Window::set_ime_position` is ignored on Windows") } #[inline] diff --git a/src/window.rs b/src/window.rs index 000adef735..d6b55830e9 100644 --- a/src/window.rs +++ b/src/window.rs @@ -401,6 +401,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. + /// - **Android:** Unsupported. #[inline] pub fn request_redraw(&self) { self.window.request_redraw() @@ -420,6 +421,7 @@ impl Window { /// window's [safe area] in the screen space coordinate system. /// - **Web:** Returns the top-left coordinates relative to the viewport. _Note: this returns the /// same value as `outer_position`._ + /// - **Android / Wayland:** Always returns [`NotSupportedError`]. /// /// [safe area]: https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc #[inline] @@ -442,6 +444,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window in the screen space coordinate system. /// - **Web:** Returns the top-left coordinates relative to the viewport. + /// - **Android / Wayland:** Always returns [`NotSupportedError`]. #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { self.window.outer_position() @@ -457,6 +460,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the /// window in the screen space coordinate system. /// - **Web:** Sets the top-left coordinates relative to the viewport. + /// - **Android / Wayland:** Unsupported. #[inline] pub fn set_outer_position>(&self, position: P) { self.window.set_outer_position(position.into()) @@ -485,8 +489,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Unimplemented. Currently this panics, as it's not clear what `set_inner_size` - /// would mean for iOS. + /// - **iOS / Android:** Unsupported. /// - **Web:** Sets the size of the canvas element. #[inline] pub fn set_inner_size>(&self, size: S) { @@ -513,8 +516,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn set_min_inner_size>(&self, min_size: Option) { self.window.set_min_inner_size(min_size.map(|s| s.into())) @@ -524,8 +526,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Andraid / Web:** Unsupported. #[inline] pub fn set_max_inner_size>(&self, max_size: Option) { self.window.set_max_inner_size(max_size.map(|s| s.into())) @@ -538,7 +539,7 @@ impl Window { /// /// ## Platform-specific /// - /// - Has no effect on iOS. + /// - **iOS / Android:** Unsupported. #[inline] pub fn set_title(&self, title: &str) { self.window.set_title(title) @@ -549,9 +550,8 @@ impl Window { /// If `false`, this will hide the window. If `true`, this will show the window. /// ## Platform-specific /// - /// - **Android:** Has no effect. + /// - **Android / Wayland / Web:** Unsupported. /// - **iOS:** Can only be called on the main thread. - /// - **Web:** Has no effect. #[inline] pub fn set_visible(&self, visible: bool) { self.window.set_visible(visible) @@ -570,8 +570,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn set_resizable(&self, resizable: bool) { self.window.set_resizable(resizable) @@ -581,7 +580,8 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect + /// - **iOS / Android / Web:** Unsupported. + /// - **Wayland:** Un-minimize is unsupported. #[inline] pub fn set_minimized(&self, minimized: bool) { self.window.set_minimized(minimized); @@ -591,8 +591,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn set_maximized(&self, maximized: bool) { self.window.set_maximized(maximized) @@ -617,6 +616,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. /// - **Wayland:** Does not support exclusive fullscreen mode. /// - **Windows:** Screen saver is disabled in fullscreen mode. + /// - **Android:** Unsupported. #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { self.window.set_fullscreen(fullscreen) @@ -627,6 +627,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. + /// - **Android:** Will always return `None`. #[inline] pub fn fullscreen(&self) -> Option { self.window.fullscreen() @@ -635,9 +636,8 @@ impl Window { /// Turn window decorations on or off. /// /// ## Platform-specific - /// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden - /// via [`setPrefersStatusBarHidden`]. - /// - **Web:** Has no effect. + /// + /// - **iOS / Android / Web:** Unsupported. /// /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc #[inline] @@ -649,8 +649,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web / Wayland:** Unsupported. #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { self.window.set_always_on_top(always_on_top) @@ -661,7 +660,7 @@ impl Window { /// /// ## Platform-specific /// - /// This only has an effect on Windows and X11. + /// - **iOS / Android / Web / Wayland / macOS:** Unsupported. /// /// On Windows, this sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32. @@ -677,8 +676,7 @@ impl Window { /// /// ## Platform-specific /// - /// **iOS:** Has no effect. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web / Wayland / Windows:** Unsupported. #[inline] pub fn set_ime_position>(&self, position: P) { self.window.set_ime_position(position.into()) @@ -691,8 +689,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Has no effect. - /// - **Android:** Has no effect. + /// - **iOS / Android:** Unsupported. #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { self.window.set_cursor_icon(cursor); @@ -702,8 +699,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Always returns an `Err`. - /// - **Web:** Has no effect. + /// - **iOS / Android / Web / Wayland:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_position>(&self, position: P) -> Result<(), ExternalError> { self.window.set_cursor_position(position.into()) @@ -713,13 +709,8 @@ impl Window { /// /// ## Platform-specific /// - /// - **macOS:** This presently merely locks the cursor in a fixed location, which looks visually - /// awkward. - /// - **Wayland:** This presently merely locks the cursor in a fixed location, which looks visually - /// awkward. - /// - **Android:** Has no effect. - /// - **iOS:** Always returns an Err. - /// - **Web:** Has no effect. + /// - **macOS / Wayland:** This locks the cursor in a fixed location, which looks visually awkward. + /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { self.window.set_cursor_grab(grab) @@ -736,8 +727,7 @@ impl Window { /// - **Wayland:** The cursor is only hidden within the confines of the window. /// - **macOS:** The cursor is hidden as long as the window has input focus, even if the cursor is /// outside of the window. - /// - **iOS:** Has no effect. - /// - **Android:** Has no effect. + /// - **iOS / Android:** Unsupported. #[inline] pub fn set_cursor_visible(&self, visible: bool) { self.window.set_cursor_visible(visible) From 40232d48ba3c80ba076ea1dc1fbb7d1c6fc80aac Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 26 Jul 2020 22:16:21 +0000 Subject: [PATCH 159/239] Use `PhysicalPosition` in `PixelDelta` event This removes the `LogicalPosition` from the `PixelDelta`, since all other APIs have been switched to use `PhysicalPosition` instead. Fixes #1406. --- CHANGELOG.md | 9 +++++---- src/event.rs | 4 ++-- src/platform_impl/linux/wayland/pointer.rs | 21 +++++++++++---------- src/platform_impl/macos/view.rs | 6 +++++- src/platform_impl/web/stdweb/event.rs | 5 ++++- src/platform_impl/web/web_sys/event.rs | 5 ++++- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eba304dd3..dc9a6b3809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ - On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. - On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops. +- On Windows, `set_ime_position` is now a no-op instead of a runtime crash. +- On Android, `set_fullscreen` is now a no-op instead of a runtime crash. +- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. +- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. +- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. # 0.22.2 (2020-05-16) @@ -49,10 +54,6 @@ - on macOS, fix incorrect ReceivedCharacter events for some key combinations. - **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`. - On macOS, a mouse motion event is now generated before every mouse click. -- On Windows, `set_ime_position` is now a no-op instead of a runtime crash. -- On Android, `set_fullscreen` is now a no-op instead of a runtime crash. -- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. -- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. # 0.21.0 (2020-02-04) diff --git a/src/event.rs b/src/event.rs index 47119fa807..9e9320c2da 100644 --- a/src/event.rs +++ b/src/event.rs @@ -37,7 +37,7 @@ use instant::Instant; use std::path::PathBuf; use crate::{ - dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}, + dpi::{PhysicalPosition, PhysicalSize}, platform_impl, window::{Theme, WindowId}, }; @@ -764,7 +764,7 @@ pub enum MouseScrollDelta { /// Scroll events are expressed as a PixelDelta if /// supported by the device (eg. a touchpad) and /// platform. - PixelDelta(LogicalPosition), + PixelDelta(PhysicalPosition), } /// Symbolic name for a keyboard key. diff --git a/src/platform_impl/linux/wayland/pointer.rs b/src/platform_impl/linux/wayland/pointer.rs index 5f93099f4a..4f2f4c2eaa 100644 --- a/src/platform_impl/linux/wayland/pointer.rs +++ b/src/platform_impl/linux/wayland/pointer.rs @@ -170,14 +170,15 @@ pub fn implement_pointer( wl_pointer::Axis::HorizontalScroll => x += value as f32, _ => unreachable!(), } + let scale_factor = surface::get_dpi_factor(&surface) as f64; + let delta = LogicalPosition::new(x as f64, y as f64) + .to_physical(scale_factor); sink.send_window_event( WindowEvent::MouseWheel { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - delta: MouseScrollDelta::PixelDelta( - (x as f64, y as f64).into(), - ), + delta: MouseScrollDelta::PixelDelta(delta), phase: TouchPhase::Moved, modifiers: modifiers_tracker.lock().unwrap().clone(), }, @@ -210,21 +211,21 @@ pub fn implement_pointer( device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - delta: MouseScrollDelta::LineDelta(x as f32, y as f32), + delta: MouseScrollDelta::LineDelta(x, y), phase: axis_state, modifiers: modifiers_tracker.lock().unwrap().clone(), }, wid, ); } else if let Some((x, y)) = axis_buffer { + let scale_factor = surface::get_dpi_factor(&surface) as f64; + let delta = LogicalPosition::new(x, y).to_physical(scale_factor); sink.send_window_event( WindowEvent::MouseWheel { device_id: crate::event::DeviceId( crate::platform_impl::DeviceId::Wayland(DeviceId), ), - delta: MouseScrollDelta::PixelDelta( - (x as f64, y as f64).into(), - ), + delta: MouseScrollDelta::PixelDelta(delta), phase: axis_state, modifiers: modifiers_tracker.lock().unwrap().clone(), }, @@ -238,11 +239,11 @@ pub fn implement_pointer( axis_state = TouchPhase::Ended; } PtrEvent::AxisDiscrete { axis, discrete } => { - let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0, 0)); + let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0.0, 0.0)); match axis { // wayland vertical sign convention is the inverse of winit - wl_pointer::Axis::VerticalScroll => y -= discrete, - wl_pointer::Axis::HorizontalScroll => x += discrete, + wl_pointer::Axis::VerticalScroll => y -= discrete as f32, + wl_pointer::Axis::HorizontalScroll => x += discrete as f32, _ => unreachable!(), } axis_discrete_buffer = Some((x, y)); diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index d764902db0..357515007a 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -999,10 +999,14 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { mouse_motion(this, event); unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + let delta = { let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); if event.hasPreciseScrollingDeltas() == YES { - MouseScrollDelta::PixelDelta((x as f64, y as f64).into()) + let delta = LogicalPosition::new(x, y).to_physical(state.get_scale_factor()); + MouseScrollDelta::PixelDelta(delta) } else { MouseScrollDelta::LineDelta(x as f32, y as f32) } diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index 8c534dc053..8464c616bb 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -36,7 +36,10 @@ pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option { match event.delta_mode() { MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), - MouseWheelDeltaMode::Pixel => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })), + MouseWheelDeltaMode::Pixel => { + let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor()); + Some(MouseScrollDelta::PixelDelta(delta)) + } MouseWheelDeltaMode::Page => None, } } diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 1c1bdba41c..a06418410c 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -35,7 +35,10 @@ pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { match event.delta_mode() { WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), - WheelEvent::DOM_DELTA_PIXEL => Some(MouseScrollDelta::PixelDelta(LogicalPosition { x, y })), + WheelEvent::DOM_DELTA_PIXEL => { + let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor()); + Some(MouseScrollDelta::PixelDelta(delta)) + } _ => None, } } From 7a49c88200fe2a9559c8cc9098f38e209567f691 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 1 Aug 2020 23:10:33 +0000 Subject: [PATCH 160/239] Fix with_fullscreen signature --- src/window.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/window.rs b/src/window.rs index d6b55830e9..a00cd7c028 100644 --- a/src/window.rs +++ b/src/window.rs @@ -251,8 +251,8 @@ impl WindowBuilder { /// /// [`Window::set_fullscreen`]: crate::window::Window::set_fullscreen #[inline] - pub fn with_fullscreen(mut self, monitor: Option) -> Self { - self.window.fullscreen = monitor; + pub fn with_fullscreen(mut self, fullscreen: Option) -> Self { + self.window.fullscreen = fullscreen; self } From 05fdcb5b270062b4bc85d3433ed12551f8a6741d Mon Sep 17 00:00:00 2001 From: josh65536 Date: Tue, 4 Aug 2020 21:39:09 -0400 Subject: [PATCH 161/239] Web: Use mouse events instead of pointer events if the latter isn't supported (#1630) * Fixed Safari not getting mouse events * Edited changelog * Addressed compiler warnings Co-authored-by: Ryan G --- CHANGELOG.md | 1 + src/platform_impl/web/web_sys/canvas.rs | 146 ++++++++++++++++++------ 2 files changed, 109 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc9a6b3809..d9257f7afe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - On android added support for `run_return`. - On MacOS, Fixed fullscreen and dialog support for `run_return`. - On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops. +- On Web, use mouse events if pointer events aren't supported. This affects Safari. - On Windows, `set_ime_position` is now a no-op instead of a runtime crash. - On Android, `set_fullscreen` is now a no-op instead of a runtime crash. - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index e8fd3a2bba..617f6ed61a 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -9,8 +9,8 @@ use std::rc::Rc; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ - Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, PointerEvent, - WheelEvent, + Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, MouseEvent, + PointerEvent, WheelEvent, }; pub struct Canvas { @@ -24,8 +24,14 @@ pub struct Canvas { on_cursor_leave: Option>, on_cursor_enter: Option>, on_cursor_move: Option>, - on_mouse_press: Option>, - on_mouse_release: Option>, + on_pointer_press: Option>, + on_pointer_release: Option>, + // Fallback events when pointer event support is missing + on_mouse_leave: Option>, + on_mouse_enter: Option>, + on_mouse_move: Option>, + on_mouse_press: Option>, + on_mouse_release: Option>, on_mouse_wheel: Option>, on_fullscreen_change: Option>, wants_fullscreen: Rc>, @@ -76,8 +82,13 @@ impl Canvas { on_cursor_leave: None, on_cursor_enter: None, on_cursor_move: None, - on_mouse_release: None, + on_pointer_release: None, + on_pointer_press: None, + on_mouse_leave: None, + on_mouse_enter: None, + on_mouse_move: None, on_mouse_press: None, + on_mouse_release: None, on_mouse_wheel: None, on_fullscreen_change: None, wants_fullscreen: Rc::new(RefCell::new(false)), @@ -180,63 +191,110 @@ impl Canvas { where F: 'static + FnMut(i32), { - self.on_cursor_leave = Some(self.add_event("pointerout", move |event: PointerEvent| { - handler(event.pointer_id()); - })); + if has_pointer_event() { + self.on_cursor_leave = + Some(self.add_event("pointerout", move |event: PointerEvent| { + handler(event.pointer_id()); + })); + } else { + self.on_mouse_leave = Some(self.add_event("mouseout", move |_: MouseEvent| { + handler(0); + })); + } } pub fn on_cursor_enter(&mut self, mut handler: F) where F: 'static + FnMut(i32), { - self.on_cursor_enter = Some(self.add_event("pointerover", move |event: PointerEvent| { - handler(event.pointer_id()); - })); + if has_pointer_event() { + self.on_cursor_enter = + Some(self.add_event("pointerover", move |event: PointerEvent| { + handler(event.pointer_id()); + })); + } else { + self.on_mouse_enter = Some(self.add_event("mouseover", move |_: MouseEvent| { + handler(0); + })); + } } pub fn on_mouse_release(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_release = Some(self.add_user_event( - "pointerup", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); + if has_pointer_event() { + self.on_pointer_release = Some(self.add_user_event( + "pointerup", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } else { + self.on_mouse_release = + Some(self.add_user_event("mouseup", move |event: MouseEvent| { + handler( + 0, + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + })); + } } pub fn on_mouse_press(&mut self, mut handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - self.on_mouse_press = Some(self.add_user_event( - "pointerdown", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); + if has_pointer_event() { + self.on_pointer_press = Some(self.add_user_event( + "pointerdown", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } else { + self.on_mouse_press = + Some(self.add_user_event("mousedown", move |event: MouseEvent| { + handler( + 0, + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + })); + } } pub fn on_cursor_move(&mut self, mut handler: F) where F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), { - self.on_cursor_move = Some(self.add_event("pointermove", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_modifiers(&event), - ); - })); + if has_pointer_event() { + self.on_cursor_move = + Some(self.add_event("pointermove", move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event).to_physical(super::scale_factor()), + event::mouse_modifiers(&event), + ); + })); + } else { + self.on_mouse_move = Some(self.add_event("mousemove", move |event: MouseEvent| { + handler( + 0, + event::mouse_position(&event).to_physical(super::scale_factor()), + event::mouse_modifiers(&event), + ); + })); + } } pub fn on_mouse_wheel(&mut self, mut handler: F) @@ -334,3 +392,15 @@ impl Canvas { super::is_fullscreen(&self.raw) } } + +/// Returns whether pointer events are supported. +/// Used to decide whether to use pointer events +/// or plain mouse events. Note that Safari +/// doesn't support pointer events now. +fn has_pointer_event() -> bool { + if let Some(window) = web_sys::window() { + window.get("PointerEvent").is_some() + } else { + false + } +} From 68100102be588aea3dd42cb64647e1f8624615b2 Mon Sep 17 00:00:00 2001 From: msiglreith Date: Wed, 12 Aug 2020 20:56:28 +0200 Subject: [PATCH 162/239] android: fix event loop polling (#1638) * android: poll looper for ControlFlow::Poll and don't exit when no new event received * Add Android ControlFlow:Poll fix to CHANGELOG --- CHANGELOG.md | 1 + src/platform_impl/android/mod.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9257f7afe..ae81ae6f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - On Windows, `set_ime_position` is now a no-op instead of a runtime crash. - On Android, `set_fullscreen` is now a no-op instead of a runtime crash. - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. +- On Android, fix `ControlFlow::Poll` not polling the Android event queue. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index e4bdef8229..453815d872 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -211,9 +211,7 @@ impl EventLoop { ); } } - None => { - control_flow = ControlFlow::Exit; - } + None => {} } call_event_handler!( @@ -258,6 +256,11 @@ impl EventLoop { break 'event_loop; } ControlFlow::Poll => { + self.first_event = poll( + self.looper + .poll_all_timeout(Duration::from_millis(0)) + .unwrap(), + ); self.start_cause = event::StartCause::Poll; } ControlFlow::Wait => { From 514ab043f21447ec1fd6f5cc09ee9b5e65663517 Mon Sep 17 00:00:00 2001 From: TakWolf <6064962+TakWolf@users.noreply.github.com> Date: Fri, 14 Aug 2020 02:10:34 +0800 Subject: [PATCH 163/239] [macos] add NSWindow.hasShadow support (#1637) * [macos] add NSWindow.hasShadow * change log * cargo fmt * Update CHANGELOG.md * Update src/platform_impl/macos/window.rs * Update src/platform/macos.rs * set_has_shadow() with cuter format * adjust code * cargo fmt * changelog --- CHANGELOG.md | 1 + src/platform/macos.rs | 23 +++++++++++++++++++ src/platform_impl/macos/window.rs | 38 ++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae81ae6f3b..c109c9c085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - On Android, `set_fullscreen` is now a no-op instead of a runtime crash. - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. - On Android, fix `ControlFlow::Poll` not polling the Android event queue. +- On macOS, add `NSWindow.hasShadow` support. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. diff --git a/src/platform/macos.rs b/src/platform/macos.rs index f07e2f6e77..f6f18d448e 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -56,6 +56,12 @@ pub trait WindowExtMacOS { /// And allows the user to have a fullscreen window without using another /// space or taking control over the entire monitor. fn set_simple_fullscreen(&self, fullscreen: bool) -> bool; + + /// Returns whether or not the window has shadow. + fn has_shadow(&self) -> bool; + + /// Sets whether or not the window has shadow. + fn set_has_shadow(&self, has_shadow: bool); } impl WindowExtMacOS for Window { @@ -83,6 +89,16 @@ impl WindowExtMacOS for Window { fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { self.window.set_simple_fullscreen(fullscreen) } + + #[inline] + fn has_shadow(&self) -> bool { + self.window.has_shadow() + } + + #[inline] + fn set_has_shadow(&self, has_shadow: bool) { + self.window.set_has_shadow(has_shadow) + } } /// Corresponds to `NSApplicationActivationPolicy`. @@ -131,6 +147,7 @@ pub trait WindowBuilderExtMacOS { /// Build window with `resizeIncrements` property. Values must not be 0. fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder; + fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder; } impl WindowBuilderExtMacOS for WindowBuilder { @@ -190,6 +207,12 @@ impl WindowBuilderExtMacOS for WindowBuilder { self.platform_specific.disallow_hidpi = disallow_hidpi; self } + + #[inline] + fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder { + self.platform_specific.has_shadow = has_shadow; + self + } } /// Additional methods on `MonitorHandle` that are specific to MacOS. diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 5505b661af..175cf73345 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -60,7 +60,7 @@ pub fn get_window_id(window_cocoa_id: id) -> Id { Id(window_cocoa_id as *const Object as _) } -#[derive(Clone, Default)] +#[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub activation_policy: ActivationPolicy, pub movable_by_window_background: bool, @@ -71,6 +71,25 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub fullsize_content_view: bool, pub resize_increments: Option>, pub disallow_hidpi: bool, + pub has_shadow: bool, +} + +impl Default for PlatformSpecificWindowBuilderAttributes { + #[inline] + fn default() -> Self { + Self { + activation_policy: Default::default(), + movable_by_window_background: false, + titlebar_transparent: false, + title_hidden: false, + titlebar_hidden: false, + titlebar_buttons_hidden: false, + fullsize_content_view: false, + resize_increments: None, + disallow_hidpi: false, + has_shadow: true, + } + } } fn create_app(activation_policy: ActivationPolicy) -> Option { @@ -224,6 +243,10 @@ fn create_window( } } + if !pl_attrs.has_shadow { + ns_window.setHasShadow_(NO); + } + ns_window.center(); ns_window }); @@ -1094,6 +1117,19 @@ impl WindowExtMacOS for UnownedWindow { } } } + + #[inline] + fn has_shadow(&self) -> bool { + unsafe { self.ns_window.hasShadow() == YES } + } + + #[inline] + fn set_has_shadow(&self, has_shadow: bool) { + unsafe { + self.ns_window + .setHasShadow_(if has_shadow { YES } else { NO }) + } + } } impl Drop for UnownedWindow { From 412bd94ea473c7b175f94a190d66f4be3e92aaab Mon Sep 17 00:00:00 2001 From: simlay Date: Fri, 14 Aug 2020 12:26:16 -0700 Subject: [PATCH 164/239] Renamed NSString to NSStringRust to support Debug View Heirarchy in Xcode (#1631) * Renamed NSString to NSStringRust to support Debug View Heirarchy * Updated from comments * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/platform_impl/ios/event_loop.rs | 5 +++-- src/platform_impl/ios/ffi.rs | 7 +++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c109c9c085..c875fe4193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode. - On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop. - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 2e7c64d2a3..9536e94250 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -24,7 +24,8 @@ use crate::platform_impl::platform::{ CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, - CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom, + CFRunLoopSourceSignal, CFRunLoopWakeUp, NSStringRust, UIApplicationMain, + UIUserInterfaceIdiom, }, monitor, view, MonitorHandle, }; @@ -117,7 +118,7 @@ impl EventLoop { 0, ptr::null(), nil, - NSString::alloc(nil).init_str("AppDelegate"), + NSStringRust::alloc(nil).init_str("AppDelegate"), ); unreachable!() } diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 1c464b066a..071b586795 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -359,7 +359,10 @@ pub struct CFRunLoopSourceContext { pub perform: Option, } -pub trait NSString: Sized { +// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of +// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy +// so please test if you change the name back to NSString. +pub trait NSStringRust: Sized { unsafe fn alloc(_: Self) -> id { msg_send![class!(NSString), alloc] } @@ -370,7 +373,7 @@ pub trait NSString: Sized { unsafe fn UTF8String(self) -> *const c_char; } -impl NSString for id { +impl NSStringRust for id { unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id { msg_send![self, initWithUTF8String: c_string as id] } From 9c72cc2a983401ad96f40555751bbfa1335259b6 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 17 Aug 2020 16:48:29 -0700 Subject: [PATCH 165/239] Fix HiDPI vs. set_cursor_icon for web (#1652) PhysicalSize is recorded as canvas.size, whereas LogicalSize is stored as canvas.style.size. The previous cursor behavior on stdweb clobbered all style - thus losing the LogicalSize. --- CHANGELOG.md | 1 + src/platform_impl/web/event_loop/mod.rs | 3 +-- src/platform_impl/web/stdweb/mod.rs | 8 ++++++-- src/platform_impl/web/web_sys/mod.rs | 12 +++++++----- src/platform_impl/web/window.rs | 3 +-- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c875fe4193..e27cf5ffa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On Unix, X11 and Wayland are now optional features (enabled by default) - On X11, fix deadlock when calling `set_fullscreen_inner`. - On Web, prevent the webpage from scrolling when the user is focused on a winit canvas +- On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling - On Windows, drag and drop is now optional and must be enabled with `WindowBuilderExtWindows::with_drag_and_drop(true)`. - On Wayland, fix deadlock when calling to `set_inner_size` from a callback. - On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`. diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index fa01b5df05..bf9806bde2 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -6,11 +6,10 @@ mod window_target; pub use self::proxy::Proxy; pub use self::window_target::WindowTarget; -use super::{backend, device, monitor, window}; +use super::{backend, device, window}; use crate::event::Event; use crate::event_loop as root; -use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; use std::marker::PhantomData; pub struct EventLoop { diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 3632307ca7..e3dafb1be3 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -67,9 +67,13 @@ pub fn set_canvas_size(raw: &CanvasElement, size: Size) { raw.set_width(physical_size.width); raw.set_height(physical_size.height); + set_canvas_style_property(raw, "width", &format!("{}px", logical_size.width)); + set_canvas_style_property(raw, "height", &format!("{}px", logical_size.height)); +} + +pub fn set_canvas_style_property(raw: &CanvasElement, style_attribute: &str, value: &str) { js! { - @{raw.as_ref()}.style.width = @{logical_size.width} + "px"; - @{raw.as_ref()}.style.height = @{logical_size.height} + "px"; + @{raw.as_ref()}.style[@{style_attribute}] = @{value}; } } diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 94efe1ec27..a4268b5272 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -81,13 +81,15 @@ pub fn set_canvas_size(raw: &HtmlCanvasElement, size: Size) { raw.set_width(physical_size.width); raw.set_height(physical_size.height); + set_canvas_style_property(raw, "width", &format!("{}px", logical_size.width)); + set_canvas_style_property(raw, "height", &format!("{}px", logical_size.height)); +} + +pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) { let style = raw.style(); style - .set_property("width", &format!("{}px", logical_size.width)) - .expect("Failed to set canvas width"); - style - .set_property("height", &format!("{}px", logical_size.height)) - .expect("Failed to set canvas height"); + .set_property(property, value) + .expect(&format!("Failed to set {}", property)); } pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 58d551354b..bcf701164e 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -165,8 +165,7 @@ impl Window { CursorIcon::RowResize => "row-resize", }; *self.previous_pointer.borrow_mut() = text; - self.canvas - .set_attribute("style", &format!("cursor: {}", text)); + backend::set_canvas_style_property(self.canvas.raw(), "cursor", text); } #[inline] From 89d4c06decf95bfcc0c11d86bb8eb0f9f634cb36 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Thu, 20 Aug 2020 18:12:01 +0000 Subject: [PATCH 166/239] Fix crash on NetBSD The `_lwp_self` function cannot be used to reliably determine the main thread, see https://github.com/alacritty/alacritty/issues/2631#issuecomment-676723289. It might always be equal to the PID, but it's certainly not always 1 when the thread is the main thread. However, Rust's built in `Thread::id` and `Thread::name` function will always return `ThreadId(1)` and `Some("main")`. Since converting the thread's ID to a number is not supported on stable Rust, checking that the thread is labeled `Some("main")` seems like the most reliable option. It should also be a good fallback in general. --- CHANGELOG.md | 1 + src/platform_impl/linux/mod.rs | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e27cf5ffa9..2b3b767ffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - On macOS, add `NSWindow.hasShadow` support. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. +- On NetBSD, fixed crash due to incorrect detection of the main thread. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 0fee68324e..73b8477760 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -730,7 +730,5 @@ fn is_main_thread() -> bool { #[cfg(target_os = "netbsd")] fn is_main_thread() -> bool { - use libc::_lwp_self; - - unsafe { _lwp_self() == 1 } + std::thread::current().name() == Some("main") } From 6ba583d19896172f5eca9d8551119906e4eff2b2 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Fri, 21 Aug 2020 09:09:04 +0800 Subject: [PATCH 167/239] Fix vertical scroll being inverted on web targets (#1665) --- CHANGELOG.md | 1 + src/platform_impl/web/stdweb/event.rs | 2 +- src/platform_impl/web/web_sys/event.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3b767ffc..2bb4519768 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash. - On Android, fix `ControlFlow::Poll` not polling the Android event queue. - On macOS, add `NSWindow.hasShadow` support. +- On Web, fix vertical mouse wheel scrolling being inverted. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index 8464c616bb..d57f9bb5bc 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -32,7 +32,7 @@ pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition { pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option { let x = event.delta_x(); - let y = event.delta_y(); + let y = -event.delta_y(); match event.delta_mode() { MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index a06418410c..316b4f271d 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -31,7 +31,7 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { let x = event.delta_x(); - let y = event.delta_y(); + let y = -event.delta_y(); match event.delta_mode() { WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), From 0f7c82d38fbe52831a51d5b2523e838d1da02407 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Sat, 22 Aug 2020 08:23:08 +0800 Subject: [PATCH 168/239] Send CursorMove before mouse press event and note that touch is unimplemented on web target (#1668) * Change to send CursorMove before mouse press event on web target * Fix feature matrix to indicate touch being unimplemented on web --- FEATURES.md | 6 +++--- src/platform_impl/web/event_loop/window_target.rs | 13 ++++++++++++- src/platform_impl/web/stdweb/canvas.rs | 3 ++- src/platform_impl/web/web_sys/canvas.rs | 4 +++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index cdd8cc9f13..d7791f5b76 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -198,9 +198,9 @@ Legend: |Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**| |Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|❓ | |Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ | -|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | -|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ | -|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |✔️ | +|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ | +|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ | +|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❌ | |Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | |Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ | diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 3ba5e1d2b5..d1851ea0cb 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -133,7 +133,18 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_mouse_press(move |pointer_id, button, modifiers| { + canvas.on_mouse_press(move |pointer_id, position, button, modifiers| { + // A mouse down event may come in without any prior CursorMoved events, + // therefore we should send a CursorMoved event to make sure that the + // user code has the correct cursor position. + runner.send_event(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::CursorMoved { + device_id: DeviceId(device::Id(pointer_id)), + position, + modifiers, + }, + }); runner.send_event(Event::WindowEvent { window_id: WindowId(id), event: WindowEvent::MouseInput { diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 64f6a18f10..5122183710 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -200,11 +200,12 @@ impl Canvas { pub fn on_mouse_press(&mut self, mut handler: F) where - F: 'static + FnMut(i32, MouseButton, ModifiersState), + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| { handler( event.pointer_id(), + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_button(&event), event::mouse_modifiers(&event), ); diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 617f6ed61a..ae7e8091c2 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -248,7 +248,7 @@ impl Canvas { pub fn on_mouse_press(&mut self, mut handler: F) where - F: 'static + FnMut(i32, MouseButton, ModifiersState), + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { if has_pointer_event() { self.on_pointer_press = Some(self.add_user_event( @@ -256,6 +256,7 @@ impl Canvas { move |event: PointerEvent| { handler( event.pointer_id(), + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_button(&event), event::mouse_modifiers(&event), ); @@ -266,6 +267,7 @@ impl Canvas { Some(self.add_user_event("mousedown", move |event: MouseEvent| { handler( 0, + event::mouse_position(&event).to_physical(super::scale_factor()), event::mouse_button(&event), event::mouse_modifiers(&event), ); From bea60930b685641756d441fc83d06e4730d332b2 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Thu, 27 Aug 2020 00:11:27 +0800 Subject: [PATCH 169/239] Use send_events instead of send_event in web backend (#1681) --- .../web/event_loop/window_target.rs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index d1851ea0cb..03fe2110ae 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -137,23 +137,25 @@ impl WindowTarget { // A mouse down event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. - runner.send_event(Event::WindowEvent { - window_id: WindowId(id), - event: WindowEvent::CursorMoved { - device_id: DeviceId(device::Id(pointer_id)), - position, - modifiers, - }, - }); - runner.send_event(Event::WindowEvent { - window_id: WindowId(id), - event: WindowEvent::MouseInput { - device_id: DeviceId(device::Id(pointer_id)), - state: ElementState::Pressed, - button, - modifiers, - }, - }); + runner.send_events( + std::iter::once(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::CursorMoved { + device_id: DeviceId(device::Id(pointer_id)), + position, + modifiers, + }, + }) + .chain(std::iter::once(Event::WindowEvent { + window_id: WindowId(id), + event: WindowEvent::MouseInput { + device_id: DeviceId(device::Id(pointer_id)), + state: ElementState::Pressed, + button, + modifiers, + }, + })), + ); }); let runner = self.runner.clone(); From 02a34a167ab281d7cca9908f67928b659d428b39 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Sat, 29 Aug 2020 21:34:33 +0800 Subject: [PATCH 170/239] Impl. mouse capturing on web target (#1672) * Impl. mouse capturing for web-sys with PointerEvent * Impl. mouse capturing for web-sys with MouseEvent by manual tracking * Reorganize web-sys backend mouse and pointer handling code * Impl. mouse capturing for stdweb with PointerEvent * Add mouse capturing for web target to changelog --- CHANGELOG.md | 1 + Cargo.toml | 1 + src/platform_impl/web/stdweb/canvas.rs | 4 + src/platform_impl/web/web_sys/canvas.rs | 250 +++++++++--------- .../web/web_sys/canvas/mouse_handler.rs | 203 ++++++++++++++ .../web/web_sys/canvas/pointer_handler.rs | 103 ++++++++ src/platform_impl/web/web_sys/event.rs | 13 +- 7 files changed, 446 insertions(+), 129 deletions(-) create mode 100644 src/platform_impl/web/web_sys/canvas/mouse_handler.rs create mode 100644 src/platform_impl/web/web_sys/canvas/pointer_handler.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bb4519768..b8077d4a75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - On Android, fix `ControlFlow::Poll` not polling the Android event queue. - On macOS, add `NSWindow.hasShadow` support. - On Web, fix vertical mouse wheel scrolling being inverted. +- On Web, implement mouse capturing for click-dragging out of the canvas. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. diff --git a/Cargo.toml b/Cargo.toml index 017e5927ab..9659f7cf72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ version = "0.3.22" optional = true features = [ 'console', + "AddEventListenerOptions", 'CssStyleDeclaration', 'BeforeUnloadEvent', 'Document', diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 5122183710..1fdc7182da 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -202,6 +202,7 @@ impl Canvas { where F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { + let canvas = self.raw.clone(); self.on_mouse_press = Some(self.add_user_event(move |event: PointerDownEvent| { handler( event.pointer_id(), @@ -209,6 +210,9 @@ impl Canvas { event::mouse_button(&event), event::mouse_modifiers(&event), ); + canvas + .set_pointer_capture(event.pointer_id()) + .expect("Failed to set pointer capture"); })); } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index ae7e8091c2..81e152c1e9 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -9,36 +9,33 @@ use std::rc::Rc; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ - Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, MouseEvent, - PointerEvent, WheelEvent, + AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, + MediaQueryListEvent, MouseEvent, WheelEvent, }; +mod mouse_handler; +mod pointer_handler; + pub struct Canvas { - /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. - raw: HtmlCanvasElement, + common: Common, on_focus: Option>, on_blur: Option>, on_keyboard_release: Option>, on_keyboard_press: Option>, on_received_character: Option>, - on_cursor_leave: Option>, - on_cursor_enter: Option>, - on_cursor_move: Option>, - on_pointer_press: Option>, - on_pointer_release: Option>, - // Fallback events when pointer event support is missing - on_mouse_leave: Option>, - on_mouse_enter: Option>, - on_mouse_move: Option>, - on_mouse_press: Option>, - on_mouse_release: Option>, on_mouse_wheel: Option>, on_fullscreen_change: Option>, - wants_fullscreen: Rc>, on_dark_mode: Option>, + mouse_state: MouseState, +} + +struct Common { + /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. + raw: HtmlCanvasElement, + wants_fullscreen: Rc>, } -impl Drop for Canvas { +impl Drop for Common { fn drop(&mut self) { self.raw.remove(); } @@ -72,38 +69,38 @@ impl Canvas { .set_attribute("tabindex", "0") .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; + let mouse_state = if has_pointer_event() { + MouseState::HasPointerEvent(pointer_handler::PointerHandler::new()) + } else { + MouseState::NoPointerEvent(mouse_handler::MouseHandler::new()) + }; + Ok(Canvas { - raw: canvas, + common: Common { + raw: canvas, + wants_fullscreen: Rc::new(RefCell::new(false)), + }, on_blur: None, on_focus: None, on_keyboard_release: None, on_keyboard_press: None, on_received_character: None, - on_cursor_leave: None, - on_cursor_enter: None, - on_cursor_move: None, - on_pointer_release: None, - on_pointer_press: None, - on_mouse_leave: None, - on_mouse_enter: None, - on_mouse_move: None, - on_mouse_press: None, - on_mouse_release: None, on_mouse_wheel: None, on_fullscreen_change: None, - wants_fullscreen: Rc::new(RefCell::new(false)), on_dark_mode: None, + mouse_state, }) } pub fn set_attribute(&self, attribute: &str, value: &str) { - self.raw + self.common + .raw .set_attribute(attribute, value) .expect(&format!("Set attribute: {}", attribute)); } pub fn position(&self) -> LogicalPosition { - let bounds = self.raw.get_bounding_client_rect(); + let bounds = self.common.raw.get_bounding_client_rect(); LogicalPosition { x: bounds.x(), @@ -113,20 +110,20 @@ impl Canvas { pub fn size(&self) -> PhysicalSize { PhysicalSize { - width: self.raw.width(), - height: self.raw.height(), + width: self.common.raw.width(), + height: self.common.raw.height(), } } pub fn raw(&self) -> &HtmlCanvasElement { - &self.raw + &self.common.raw } pub fn on_blur(&mut self, mut handler: F) where F: 'static + FnMut(), { - self.on_blur = Some(self.add_event("blur", move |_: FocusEvent| { + self.on_blur = Some(self.common.add_event("blur", move |_: FocusEvent| { handler(); })); } @@ -135,7 +132,7 @@ impl Canvas { where F: 'static + FnMut(), { - self.on_focus = Some(self.add_event("focus", move |_: FocusEvent| { + self.on_focus = Some(self.common.add_event("focus", move |_: FocusEvent| { handler(); })); } @@ -144,30 +141,34 @@ impl Canvas { where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_release = - Some(self.add_user_event("keyup", move |event: KeyboardEvent| { + self.on_keyboard_release = Some(self.common.add_user_event( + "keyup", + move |event: KeyboardEvent| { event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), event::keyboard_modifiers(&event), ); - })); + }, + )); } pub fn on_keyboard_press(&mut self, mut handler: F) where F: 'static + FnMut(ScanCode, Option, ModifiersState), { - self.on_keyboard_press = - Some(self.add_user_event("keydown", move |event: KeyboardEvent| { + self.on_keyboard_press = Some(self.common.add_user_event( + "keydown", + move |event: KeyboardEvent| { event.prevent_default(); handler( event::scan_code(&event), event::virtual_key_code(&event), event::keyboard_modifiers(&event), ); - })); + }, + )); } pub fn on_received_character(&mut self, mut handler: F) @@ -179,7 +180,7 @@ impl Canvas { // The `keypress` event is deprecated, but there does not seem to be a // viable/compatible alternative as of now. `beforeinput` is still widely // unsupported. - self.on_received_character = Some(self.add_user_event( + self.on_received_character = Some(self.common.add_user_event( "keypress", move |event: KeyboardEvent| { handler(event::codepoint(&event)); @@ -187,115 +188,53 @@ impl Canvas { )); } - pub fn on_cursor_leave(&mut self, mut handler: F) + pub fn on_cursor_leave(&mut self, handler: F) where F: 'static + FnMut(i32), { - if has_pointer_event() { - self.on_cursor_leave = - Some(self.add_event("pointerout", move |event: PointerEvent| { - handler(event.pointer_id()); - })); - } else { - self.on_mouse_leave = Some(self.add_event("mouseout", move |_: MouseEvent| { - handler(0); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_cursor_leave(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_cursor_leave(&self.common, handler), } } - pub fn on_cursor_enter(&mut self, mut handler: F) + pub fn on_cursor_enter(&mut self, handler: F) where F: 'static + FnMut(i32), { - if has_pointer_event() { - self.on_cursor_enter = - Some(self.add_event("pointerover", move |event: PointerEvent| { - handler(event.pointer_id()); - })); - } else { - self.on_mouse_enter = Some(self.add_event("mouseover", move |_: MouseEvent| { - handler(0); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_cursor_enter(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_cursor_enter(&self.common, handler), } } - pub fn on_mouse_release(&mut self, mut handler: F) + pub fn on_mouse_release(&mut self, handler: F) where F: 'static + FnMut(i32, MouseButton, ModifiersState), { - if has_pointer_event() { - self.on_pointer_release = Some(self.add_user_event( - "pointerup", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); - } else { - self.on_mouse_release = - Some(self.add_user_event("mouseup", move |event: MouseEvent| { - handler( - 0, - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_mouse_release(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_mouse_release(&self.common, handler), } } - pub fn on_mouse_press(&mut self, mut handler: F) + pub fn on_mouse_press(&mut self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), { - if has_pointer_event() { - self.on_pointer_press = Some(self.add_user_event( - "pointerdown", - move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - }, - )); - } else { - self.on_mouse_press = - Some(self.add_user_event("mousedown", move |event: MouseEvent| { - handler( - 0, - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_mouse_press(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_mouse_press(&self.common, handler), } } - pub fn on_cursor_move(&mut self, mut handler: F) + pub fn on_cursor_move(&mut self, handler: F) where F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), { - if has_pointer_event() { - self.on_cursor_move = - Some(self.add_event("pointermove", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_modifiers(&event), - ); - })); - } else { - self.on_mouse_move = Some(self.add_event("mousemove", move |event: MouseEvent| { - handler( - 0, - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_modifiers(&event), - ); - })); + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.on_cursor_move(&self.common, handler), + MouseState::NoPointerEvent(h) => h.on_cursor_move(&self.common, handler), } } @@ -303,7 +242,7 @@ impl Canvas { where F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), { - self.on_mouse_wheel = Some(self.add_event("wheel", move |event: WheelEvent| { + self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { event.prevent_default(); if let Some(delta) = event::mouse_scroll_delta(&event) { handler(0, delta, event::mouse_modifiers(&event)); @@ -315,8 +254,10 @@ impl Canvas { where F: 'static + FnMut(), { - self.on_fullscreen_change = - Some(self.add_event("fullscreenchange", move |_: Event| handler())); + self.on_fullscreen_change = Some( + self.common + .add_event("fullscreenchange", move |_: Event| handler()), + ); } pub fn on_dark_mode(&mut self, mut handler: F) @@ -341,6 +282,16 @@ impl Canvas { }); } + pub fn request_fullscreen(&self) { + self.common.request_fullscreen() + } + + pub fn is_fullscreen(&self) -> bool { + self.common.is_fullscreen() + } +} + +impl Common { fn add_event(&self, event_name: &str, mut handler: F) -> Closure where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, @@ -386,6 +337,44 @@ impl Canvas { }) } + // This function is used exclusively for mouse events (not pointer events). + // Due to the need for mouse capturing, the mouse event handlers are added + // to the window instead of the canvas element, which requires special + // handling to control event propagation. + fn add_window_mouse_event( + &self, + event_name: &str, + mut handler: F, + ) -> Closure + where + F: 'static + FnMut(MouseEvent), + { + let wants_fullscreen = self.wants_fullscreen.clone(); + let canvas = self.raw.clone(); + let window = web_sys::window().expect("Failed to obtain window"); + + let closure = Closure::wrap(Box::new(move |event: MouseEvent| { + handler(event); + + if *wants_fullscreen.borrow() { + canvas + .request_fullscreen() + .expect("Failed to enter fullscreen"); + *wants_fullscreen.borrow_mut() = false; + } + }) as Box); + + window + .add_event_listener_with_callback_and_add_event_listener_options( + event_name, + &closure.as_ref().unchecked_ref(), + AddEventListenerOptions::new().capture(true), + ) + .expect("Failed to add event listener with callback and options"); + + closure + } + pub fn request_fullscreen(&self) { *self.wants_fullscreen.borrow_mut() = true; } @@ -395,6 +384,11 @@ impl Canvas { } } +enum MouseState { + HasPointerEvent(pointer_handler::PointerHandler), + NoPointerEvent(mouse_handler::MouseHandler), +} + /// Returns whether pointer events are supported. /// Used to decide whether to use pointer events /// or plain mouse events. Note that Safari diff --git a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs new file mode 100644 index 0000000000..37fdd018e3 --- /dev/null +++ b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs @@ -0,0 +1,203 @@ +use super::event; +use crate::dpi::PhysicalPosition; +use crate::event::{ModifiersState, MouseButton}; + +use std::cell::RefCell; +use std::rc::Rc; + +use wasm_bindgen::closure::Closure; +use web_sys::{EventTarget, MouseEvent}; + +pub(super) struct MouseHandler { + on_mouse_leave: Option>, + on_mouse_enter: Option>, + on_mouse_move: Option>, + on_mouse_press: Option>, + on_mouse_release: Option>, + on_mouse_leave_handler: Rc>>>, + mouse_capture_state: Rc>, +} + +#[derive(PartialEq, Eq)] +pub(super) enum MouseCaptureState { + NotCaptured, + Captured, + OtherElement, +} + +impl MouseHandler { + pub fn new() -> Self { + Self { + on_mouse_leave: None, + on_mouse_enter: None, + on_mouse_move: None, + on_mouse_press: None, + on_mouse_release: None, + on_mouse_leave_handler: Rc::new(RefCell::new(None)), + mouse_capture_state: Rc::new(RefCell::new(MouseCaptureState::NotCaptured)), + } + } + pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, handler: F) + where + F: 'static + FnMut(i32), + { + *self.on_mouse_leave_handler.borrow_mut() = Some(Box::new(handler)); + let on_mouse_leave_handler = self.on_mouse_leave_handler.clone(); + let mouse_capture_state = self.mouse_capture_state.clone(); + self.on_mouse_leave = Some(canvas_common.add_event("mouseout", move |_: MouseEvent| { + // If the mouse is being captured, it is always considered + // to be "within" the the canvas, until the capture has been + // released, therefore we don't send cursor leave events. + if *mouse_capture_state.borrow() != MouseCaptureState::Captured { + if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { + handler(0); + } + } + })); + } + + pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32), + { + let mouse_capture_state = self.mouse_capture_state.clone(); + self.on_mouse_enter = Some(canvas_common.add_event("mouseover", move |_: MouseEvent| { + // We don't send cursor leave events when the mouse is being + // captured, therefore we do the same with cursor enter events. + if *mouse_capture_state.borrow() != MouseCaptureState::Captured { + handler(0); + } + })); + } + + pub fn on_mouse_release(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + let on_mouse_leave_handler = self.on_mouse_leave_handler.clone(); + let mouse_capture_state = self.mouse_capture_state.clone(); + let canvas = canvas_common.raw.clone(); + self.on_mouse_release = Some(canvas_common.add_window_mouse_event( + "mouseup", + move |event: MouseEvent| { + let canvas = canvas.clone(); + let mut mouse_capture_state = mouse_capture_state.borrow_mut(); + match &*mouse_capture_state { + // Shouldn't happen but we'll just ignore it. + MouseCaptureState::NotCaptured => return, + MouseCaptureState::OtherElement => { + if event.buttons() == 0 { + // No buttons are pressed anymore so reset + // the capturing state. + *mouse_capture_state = MouseCaptureState::NotCaptured; + } + return; + } + MouseCaptureState::Captured => {} + } + event.stop_propagation(); + handler( + 0, + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + if event + .target() + .map_or(false, |target| target != EventTarget::from(canvas)) + { + // Since we do not send cursor leave events while the + // cursor is being captured, we instead send it after + // the capture has been released. + if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { + handler(0); + } + } + if event.buttons() == 0 { + // No buttons are pressed anymore so reset + // the capturing state. + *mouse_capture_state = MouseCaptureState::NotCaptured; + } + }, + )); + } + + pub fn on_mouse_press(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), + { + let mouse_capture_state = self.mouse_capture_state.clone(); + let canvas = canvas_common.raw.clone(); + self.on_mouse_press = Some(canvas_common.add_window_mouse_event( + "mousedown", + move |event: MouseEvent| { + let canvas = canvas.clone(); + let mut mouse_capture_state = mouse_capture_state.borrow_mut(); + match &*mouse_capture_state { + MouseCaptureState::NotCaptured + if event + .target() + .map_or(false, |target| target != EventTarget::from(canvas)) => + { + // The target isn't our canvas which means the + // mouse is pressed outside of it. + *mouse_capture_state = MouseCaptureState::OtherElement; + return; + } + MouseCaptureState::OtherElement => return, + _ => {} + } + *mouse_capture_state = MouseCaptureState::Captured; + event.stop_propagation(); + handler( + 0, + event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } + + pub fn on_cursor_move(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + { + let mouse_capture_state = self.mouse_capture_state.clone(); + let canvas = canvas_common.raw.clone(); + self.on_mouse_move = Some(canvas_common.add_window_mouse_event( + "mousemove", + move |event: MouseEvent| { + let canvas = canvas.clone(); + let mouse_capture_state = mouse_capture_state.borrow(); + let is_over_canvas = event + .target() + .map_or(false, |target| target == EventTarget::from(canvas.clone())); + match &*mouse_capture_state { + // Don't handle hover events outside of canvas. + MouseCaptureState::NotCaptured if !is_over_canvas => return, + MouseCaptureState::OtherElement if !is_over_canvas => return, + // If hovering over the canvas, just send the cursor move event. + MouseCaptureState::NotCaptured + | MouseCaptureState::OtherElement + | MouseCaptureState::Captured => { + if *mouse_capture_state == MouseCaptureState::Captured { + event.stop_propagation(); + } + let mouse_pos = if is_over_canvas { + event::mouse_position(&event) + } else { + // Since the mouse is not on the canvas, we cannot + // use `offsetX`/`offsetY`. + event::mouse_position_by_client(&event, &canvas) + }; + handler( + 0, + mouse_pos.to_physical(super::super::scale_factor()), + event::mouse_modifiers(&event), + ); + } + } + }, + )); + } +} diff --git a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs new file mode 100644 index 0000000000..f9787dd5d5 --- /dev/null +++ b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs @@ -0,0 +1,103 @@ +use super::event; +use crate::dpi::PhysicalPosition; +use crate::event::{ModifiersState, MouseButton}; + +use wasm_bindgen::closure::Closure; +use web_sys::PointerEvent; + +pub(super) struct PointerHandler { + on_cursor_leave: Option>, + on_cursor_enter: Option>, + on_cursor_move: Option>, + on_pointer_press: Option>, + on_pointer_release: Option>, +} + +impl PointerHandler { + pub fn new() -> Self { + Self { + on_cursor_leave: None, + on_cursor_enter: None, + on_cursor_move: None, + on_pointer_press: None, + on_pointer_release: None, + } + } + + pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_cursor_leave = Some(canvas_common.add_event( + "pointerout", + move |event: PointerEvent| { + handler(event.pointer_id()); + }, + )); + } + + pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32), + { + self.on_cursor_enter = Some(canvas_common.add_event( + "pointerover", + move |event: PointerEvent| { + handler(event.pointer_id()); + }, + )); + } + + pub fn on_mouse_release(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, MouseButton, ModifiersState), + { + self.on_pointer_release = Some(canvas_common.add_user_event( + "pointerup", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + }, + )); + } + + pub fn on_mouse_press(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, MouseButton, ModifiersState), + { + let canvas = canvas_common.raw.clone(); + self.on_pointer_press = Some(canvas_common.add_user_event( + "pointerdown", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_button(&event), + event::mouse_modifiers(&event), + ); + canvas + .set_pointer_capture(event.pointer_id()) + .expect("Failed to set pointer capture"); + }, + )); + } + + pub fn on_cursor_move(&mut self, canvas_common: &super::Common, mut handler: F) + where + F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + { + self.on_cursor_move = Some(canvas_common.add_event( + "pointermove", + move |event: PointerEvent| { + handler( + event.pointer_id(), + event::mouse_position(&event).to_physical(super::super::scale_factor()), + event::mouse_modifiers(&event), + ); + }, + )); + } +} diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 316b4f271d..18927d401f 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -2,7 +2,7 @@ use crate::dpi::LogicalPosition; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; use std::convert::TryInto; -use web_sys::{KeyboardEvent, MouseEvent, WheelEvent}; +use web_sys::{HtmlCanvasElement, KeyboardEvent, MouseEvent, WheelEvent}; pub fn mouse_button(event: &MouseEvent) -> MouseButton { match event.button() { @@ -29,6 +29,17 @@ pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { } } +pub fn mouse_position_by_client( + event: &MouseEvent, + canvas: &HtmlCanvasElement, +) -> LogicalPosition { + let bounding_client_rect = canvas.get_bounding_client_rect(); + LogicalPosition { + x: event.client_x() as f64 - bounding_client_rect.x(), + y: event.client_y() as f64 - bounding_client_rect.y(), + } +} + pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { let x = event.delta_x(); let y = -event.delta_y(); From a2db4c0a320aafc10d240c432fe5ef4e4d84629d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 29 Aug 2020 13:38:41 +0000 Subject: [PATCH 171/239] Unify Minus/Subtract virtual keycodes On all platforms other than Linux/X11, the Subtract key was uniformly used only for the Numpad. To make this cross-platform compatible, the `-` key will now map to `Minus` on X11 instead of `Subtract`. Since people have been confused about the difference between `Minus` and `Subtract` in the past, the `Subtract` key has also been renamed to `NumpadSubtract`. This is a breaking change that might be annoying to downstream since there's no direct improvement, but it should help new users in the future. Alternatively this could just be documented, rather than explicitly mentioning the Numpad in the name. --- CHANGELOG.md | 2 ++ src/event.rs | 8 ++++---- src/platform_impl/linux/wayland/keyboard.rs | 3 +-- src/platform_impl/linux/x11/events.rs | 4 ++-- src/platform_impl/macos/event.rs | 2 +- src/platform_impl/web/stdweb/event.rs | 2 +- src/platform_impl/web/web_sys/event.rs | 2 +- src/platform_impl/windows/event.rs | 2 +- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8077d4a75..cc4386eaa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. +- **Breaking:** The virtual key code `Subtract` has been renamed to `NumpadSubtract` +- **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract` # 0.22.2 (2020-05-16) diff --git a/src/event.rs b/src/event.rs index 9e9320c2da..134421aca1 100644 --- a/src/event.rs +++ b/src/event.rs @@ -892,6 +892,10 @@ pub enum VirtualKeyCode { Numpad7, Numpad8, Numpad9, + NumpadComma, + NumpadEnter, + NumpadEquals, + NumpadSubtract, AbntC1, AbntC2, @@ -930,9 +934,6 @@ pub enum VirtualKeyCode { NavigateBackward, NextTrack, NoConvert, - NumpadComma, - NumpadEnter, - NumpadEquals, OEM102, Period, PlayPause, @@ -947,7 +948,6 @@ pub enum VirtualKeyCode { Slash, Sleep, Stop, - Subtract, Sysrq, Tab, Underline, diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index 8f911888f5..fb9219e0d3 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -340,7 +340,7 @@ fn keysym_to_vkey(keysym: u32) -> Option { keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add), - keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract), + keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide), keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), @@ -360,7 +360,6 @@ fn keysym_to_vkey(keysym: u32) -> Option { keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash), // => Some(VirtualKeyCode::Sleep), // => Some(VirtualKeyCode::Stop), - // => Some(VirtualKeyCode::Subtract), // => Some(VirtualKeyCode::Sysrq), keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab), keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), diff --git a/src/platform_impl/linux/x11/events.rs b/src/platform_impl/linux/x11/events.rs index 1b4996e745..f7d98b98da 100644 --- a/src/platform_impl/linux/x11/events.rs +++ b/src/platform_impl/linux/x11/events.rs @@ -84,7 +84,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option { //ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply, ffi::XK_KP_Add => VirtualKeyCode::Add, //ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator, - ffi::XK_KP_Subtract => VirtualKeyCode::Subtract, + ffi::XK_KP_Subtract => VirtualKeyCode::NumpadSubtract, //ffi::XK_KP_Decimal => VirtualKeyCode::Kp_decimal, ffi::XK_KP_Divide => VirtualKeyCode::Divide, ffi::XK_KP_0 => VirtualKeyCode::Numpad0, @@ -186,7 +186,7 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option { //ffi::XK_asterisk => VirtualKeyCode::Asterisk, ffi::XK_plus => VirtualKeyCode::Add, ffi::XK_comma => VirtualKeyCode::Comma, - ffi::XK_minus => VirtualKeyCode::Subtract, + ffi::XK_minus => VirtualKeyCode::Minus, ffi::XK_period => VirtualKeyCode::Period, ffi::XK_slash => VirtualKeyCode::Slash, ffi::XK_0 => VirtualKeyCode::Key0, diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 6e231940f2..dd12c9737e 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -169,7 +169,7 @@ pub fn scancode_to_keycode(scancode: c_ushort) -> Option { 0x4b => VirtualKeyCode::Divide, 0x4c => VirtualKeyCode::NumpadEnter, //0x4d => unkown, - 0x4e => VirtualKeyCode::Subtract, + 0x4e => VirtualKeyCode::NumpadSubtract, 0x4f => VirtualKeyCode::F18, 0x50 => VirtualKeyCode::F19, 0x51 => VirtualKeyCode::NumpadEquals, diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index d57f9bb5bc..e9bb1b38c5 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -195,7 +195,7 @@ pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option { "Slash" => VirtualKeyCode::Slash, "Sleep" => VirtualKeyCode::Sleep, "Stop" => VirtualKeyCode::Stop, - "NumpadSubtract" => VirtualKeyCode::Subtract, + "NumpadSubtract" => VirtualKeyCode::NumpadSubtract, "Sysrq" => VirtualKeyCode::Sysrq, "Tab" => VirtualKeyCode::Tab, "Underline" => VirtualKeyCode::Underline, diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 18927d401f..802915683b 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -204,7 +204,7 @@ pub fn virtual_key_code(event: &KeyboardEvent) -> Option { "Slash" => VirtualKeyCode::Slash, "Sleep" => VirtualKeyCode::Sleep, "Stop" => VirtualKeyCode::Stop, - "NumpadSubtract" => VirtualKeyCode::Subtract, + "NumpadSubtract" => VirtualKeyCode::NumpadSubtract, "Sysrq" => VirtualKeyCode::Sysrq, "Tab" => VirtualKeyCode::Tab, "Underline" => VirtualKeyCode::Underline, diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 49501f1a3c..40312418bc 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -262,7 +262,7 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option { winuser::VK_MULTIPLY => Some(VirtualKeyCode::Multiply), winuser::VK_ADD => Some(VirtualKeyCode::Add), //winuser::VK_SEPARATOR => Some(VirtualKeyCode::Separator), - winuser::VK_SUBTRACT => Some(VirtualKeyCode::Subtract), + winuser::VK_SUBTRACT => Some(VirtualKeyCode::NumpadSubtract), winuser::VK_DECIMAL => Some(VirtualKeyCode::Decimal), winuser::VK_DIVIDE => Some(VirtualKeyCode::Divide), winuser::VK_F1 => Some(VirtualKeyCode::F1), From 658a9a4ea8b50c5279042aa2a23f27fc583bb878 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Sun, 30 Aug 2020 21:15:44 +0800 Subject: [PATCH 172/239] Handle scale factor change on web-sys backend (#1690) * Change web backend `event_handler` to without 'static lifetime * Refactor web runner and fix ControlFlow::Exit not being sticky * Impl. scaling change event for web-sys backend * Improve `dpi` docs regarding the web backend * Add changes to changelog * Update features.md --- CHANGELOG.md | 2 + FEATURES.md | 4 +- src/dpi.rs | 3 + src/event_loop.rs | 7 +- src/platform_impl/web/event_loop/mod.rs | 3 +- src/platform_impl/web/event_loop/runner.rs | 185 ++++++++++++++---- .../web/event_loop/window_target.rs | 7 +- src/platform_impl/web/mod.rs | 6 + src/platform_impl/web/stdweb/mod.rs | 2 + src/platform_impl/web/stdweb/scaling.rs | 13 ++ src/platform_impl/web/web_sys/mod.rs | 2 + src/platform_impl/web/web_sys/scaling.rs | 110 +++++++++++ 12 files changed, 301 insertions(+), 43 deletions(-) create mode 100644 src/platform_impl/web/stdweb/scaling.rs create mode 100644 src/platform_impl/web/web_sys/scaling.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cc4386eaa8..2eb312ca80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ - On macOS, add `NSWindow.hasShadow` support. - On Web, fix vertical mouse wheel scrolling being inverted. - On Web, implement mouse capturing for click-dragging out of the canvas. +- On Web, fix `ControlFlow::Exit` not properly handled. +- On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed. - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. diff --git a/FEATURES.md b/FEATURES.md index d7791f5b76..ca28c417f1 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -182,9 +182,11 @@ Legend: |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | |Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**| -|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |**N/A**| +|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ \*1| |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| +\*1: `WindowEvent::ScaleFactorChanged` is not sent on `stdweb` backend. + ### System information |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | |---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | diff --git a/src/dpi.rs b/src/dpi.rs index 8a56ae6e06..ed30abb79b 100644 --- a/src/dpi.rs +++ b/src/dpi.rs @@ -89,6 +89,8 @@ //! - **Android:** Scale factors are set by the manufacturer to the value that best suits the //! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information. //! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels. +//! In other words, it is the value of [`window.devicePixelRatio`][web_1]. It is affected by +//! both the screen scaling and the browser zoom level and can go below `1.0`. //! //! [points]: https://en.wikipedia.org/wiki/Point_(typography) //! [picas]: https://en.wikipedia.org/wiki/Pica_(typography) @@ -96,6 +98,7 @@ //! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html //! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/ //! [android_1]: https://developer.android.com/training/multiscreen/screendensities +//! [web_1]: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio pub trait Pixel: Copy + Into { fn from_f64(f: f64) -> Self; diff --git a/src/event_loop.rs b/src/event_loop.rs index a523c01aed..02402930d7 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -72,8 +72,11 @@ impl fmt::Debug for EventLoopWindowTarget { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of - /// whether or not new events are available to process. For web, events are sent when - /// `requestAnimationFrame` fires. + /// whether or not new events are available to process. + /// + /// For web, events are queued and usually sent when `requestAnimationFrame` fires but sometimes + /// the events in the queue may be sent before the next `requestAnimationFrame` callback, for + /// example when the scaling of the page has changed. Poll, /// When the current loop iteration finishes, suspend the thread until another event arrives. Wait, diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index bf9806bde2..24be4d3dcc 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -28,8 +28,7 @@ impl EventLoop { pub fn run(self, mut event_handler: F) -> ! where - F: 'static - + FnMut(Event<'static, T>, &root::EventLoopWindowTarget, &mut root::ControlFlow), + F: 'static + FnMut(Event<'_, T>, &root::EventLoopWindowTarget, &mut root::ControlFlow), { let target = root::EventLoopWindowTarget { p: self.elw.p.clone(), diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index d381b942b2..0ccd7862c2 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -1,4 +1,4 @@ -use super::{backend, state::State}; +use super::{super::ScaleChangeArgs, backend, state::State}; use crate::event::{Event, StartCause}; use crate::event_loop as root; use crate::window::WindowId; @@ -24,23 +24,60 @@ pub struct Execution { runner: RefCell>>, events: RefCell>>, id: RefCell, + all_canvases: RefCell>, redraw_pending: RefCell>, + scale_change_detector: RefCell>, } struct Runner { state: State, is_busy: bool, - event_handler: Box, &mut root::ControlFlow)>, + event_handler: Box, &mut root::ControlFlow)>, } impl Runner { - pub fn new(event_handler: Box, &mut root::ControlFlow)>) -> Self { + pub fn new(event_handler: Box, &mut root::ControlFlow)>) -> Self { Runner { state: State::Init, is_busy: false, event_handler, } } + + /// Returns the cooresponding `StartCause` for the current `state`, or `None` + /// when in `Exit` state. + fn maybe_start_cause(&self) -> Option { + Some(match self.state { + State::Init => StartCause::Init, + State::Poll { .. } => StartCause::Poll, + State::Wait { start } => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + State::WaitUntil { start, end, .. } => StartCause::WaitCancelled { + start, + requested_resume: Some(end), + }, + State::Exit => return None, + }) + } + + fn handle_single_event(&mut self, event: Event<'_, T>, control: &mut root::ControlFlow) { + let is_closed = *control == root::ControlFlow::Exit; + + // An event is being processed, so the runner should be marked busy + self.is_busy = true; + + (self.event_handler)(event, control); + + // Maintain closed state, even if the callback changes it + if is_closed { + *control = root::ControlFlow::Exit; + } + + // An event is no longer being processed + self.is_busy = false; + } } impl Shared { @@ -49,16 +86,22 @@ impl Shared { runner: RefCell::new(None), events: RefCell::new(VecDeque::new()), id: RefCell::new(0), + all_canvases: RefCell::new(Vec::new()), redraw_pending: RefCell::new(HashSet::new()), + scale_change_detector: RefCell::new(None), })) } + pub fn add_canvas(&self, id: WindowId, canvas: backend::RawCanvasType) { + self.0.all_canvases.borrow_mut().push((id, canvas)); + } + // Set the event callback to use for the event loop runner // This the event callback is a fairly thin layer over the user-provided callback that closes // over a RootEventLoopWindowTarget reference pub fn set_listener( &self, - event_handler: Box, &mut root::ControlFlow)>, + event_handler: Box, &mut root::ControlFlow)>, ) { self.0.runner.replace(Some(Runner::new(event_handler))); self.init(); @@ -67,6 +110,14 @@ impl Shared { backend::on_unload(move || close_instance.handle_unload()); } + pub(crate) fn set_on_scale_change(&self, handler: F) + where + F: 'static + FnMut(ScaleChangeArgs), + { + *self.0.scale_change_detector.borrow_mut() = + Some(backend::ScaleChangeDetector::new(handler)); + } + // Generate a strictly increasing ID // This is used to differentiate windows when handling events pub fn generate_id(&self) -> u32 { @@ -138,25 +189,15 @@ impl Shared { } // At this point, we know this is a fresh set of events // Now we determine why new events are incoming, and handle the events - let start_cause = if let Some(runner) = &*self.0.runner.borrow() { - match runner.state { - State::Init => StartCause::Init, - State::Poll { .. } => StartCause::Poll, - State::Wait { start } => StartCause::WaitCancelled { - start, - requested_resume: None, - }, - State::WaitUntil { start, end, .. } => StartCause::WaitCancelled { - start, - requested_resume: Some(end), - }, - State::Exit => { - // If we're in the exit state, don't do event processing - return; - } - } - } else { - unreachable!("The runner cannot process events when it is not attached"); + let start_cause = match (self.0.runner.borrow().as_ref()) + .unwrap_or_else(|| { + unreachable!("The runner cannot process events when it is not attached") + }) + .maybe_start_cause() + { + Some(c) => c, + // If we're in the exit state, don't do event processing + None => return, }; // Take the start event, then the events provided to this function, and run an iteration of // the event loop @@ -191,37 +232,107 @@ impl Shared { } } + pub fn handle_scale_changed(&self, old_scale: f64, new_scale: f64) { + let start_cause = match (self.0.runner.borrow().as_ref()) + .unwrap_or_else(|| unreachable!("`scale_changed` should not happen without a runner")) + .maybe_start_cause() + { + Some(c) => c, + // If we're in the exit state, don't do event processing + None => return, + }; + let mut control = self.current_control_flow(); + + // Handle the start event and all other events in the queue. + self.handle_event(Event::NewEvents(start_cause), &mut control); + + // Now handle the `ScaleFactorChanged` events. + for &(id, ref canvas) in &*self.0.all_canvases.borrow() { + // First, we send the `ScaleFactorChanged` event: + let current_size = crate::dpi::PhysicalSize { + width: canvas.width() as u32, + height: canvas.height() as u32, + }; + let logical_size = current_size.to_logical::(old_scale); + let mut new_size = logical_size.to_physical(new_scale); + self.handle_single_event_sync( + Event::WindowEvent { + window_id: id, + event: crate::event::WindowEvent::ScaleFactorChanged { + scale_factor: new_scale, + new_inner_size: &mut new_size, + }, + }, + &mut control, + ); + + // Then we resize the canvas to the new size and send a `Resized` event: + backend::set_canvas_size(canvas, crate::dpi::Size::Physical(new_size)); + self.handle_single_event_sync( + Event::WindowEvent { + window_id: id, + event: crate::event::WindowEvent::Resized(new_size), + }, + &mut control, + ); + } + + self.handle_event(Event::MainEventsCleared, &mut control); + + // Discard all the pending redraw as we shall just redraw all windows. + self.0.redraw_pending.borrow_mut().clear(); + for &(window_id, _) in &*self.0.all_canvases.borrow() { + self.handle_event(Event::RedrawRequested(window_id), &mut control); + } + self.handle_event(Event::RedrawEventsCleared, &mut control); + + self.apply_control_flow(control); + // If the event loop is closed, it has been closed this iteration and now the closing + // event should be emitted + if self.is_closed() { + self.handle_event(Event::LoopDestroyed, &mut control); + } + } + fn handle_unload(&self) { self.apply_control_flow(root::ControlFlow::Exit); let mut control = self.current_control_flow(); self.handle_event(Event::LoopDestroyed, &mut control); } + // handle_single_event_sync takes in an event and handles it synchronously. + // + // It should only ever be called from `scale_changed`. + fn handle_single_event_sync(&self, event: Event<'_, T>, control: &mut root::ControlFlow) { + if self.is_closed() { + *control = root::ControlFlow::Exit; + } + match *self.0.runner.borrow_mut() { + Some(ref mut runner) => { + runner.handle_single_event(event, control); + } + _ => panic!("Cannot handle event synchronously without a runner"), + } + } + // handle_event takes in events and either queues them or applies a callback // - // It should only ever be called from send_event + // It should only ever be called from `run_until_cleared` and `scale_changed`. fn handle_event(&self, event: Event<'static, T>, control: &mut root::ControlFlow) { - let is_closed = self.is_closed(); + if self.is_closed() { + *control = root::ControlFlow::Exit; + } match *self.0.runner.borrow_mut() { Some(ref mut runner) => { - // An event is being processed, so the runner should be marked busy - runner.is_busy = true; - - (runner.event_handler)(event, control); - - // Maintain closed state, even if the callback changes it - if is_closed { - *control = root::ControlFlow::Exit; - } - - // An event is no longer being processed - runner.is_busy = false; + runner.handle_single_event(event, control); } // If an event is being handled without a runner somehow, add it to the event queue so // it will eventually be processed _ => self.0.events.borrow_mut().push_back(event), } + let is_closed = *control == root::ControlFlow::Exit; + // Don't take events out of the queue if the loop is closed or the runner doesn't exist // If the runner doesn't exist and this method recurses, it will recurse infinitely if !is_closed && self.0.runner.borrow().is_some() { diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 03fe2110ae..0815bd40b7 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -29,8 +29,12 @@ impl WindowTarget { Proxy::new(self.runner.clone()) } - pub fn run(&self, event_handler: Box, &mut ControlFlow)>) { + pub fn run(&self, event_handler: Box, &mut ControlFlow)>) { self.runner.set_listener(event_handler); + let runner = self.runner.clone(); + self.runner.set_on_scale_change(move |arg| { + runner.handle_scale_changed(arg.old_scale, arg.new_scale) + }); } pub fn generate_id(&self) -> window::Id { @@ -40,6 +44,7 @@ impl WindowTarget { pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) { let runner = self.runner.clone(); canvas.set_attribute("data-raw-handle", &id.0.to_string()); + runner.add_canvas(WindowId(id), canvas.raw().clone()); canvas.on_blur(move || { runner.send_event(Event::WindowEvent { diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 43bbb2afff..ed170b83b2 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -46,3 +46,9 @@ pub use self::window::{ }; pub(crate) use crate::icon::NoIcon as PlatformIcon; + +#[derive(Clone, Copy)] +pub(crate) struct ScaleChangeArgs { + old_scale: f64, + new_scale: f64, +} diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index e3dafb1be3..2f38c178a8 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -1,8 +1,10 @@ mod canvas; mod event; +mod scaling; mod timeout; pub use self::canvas::Canvas; +pub use self::scaling::ScaleChangeDetector; pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; diff --git a/src/platform_impl/web/stdweb/scaling.rs b/src/platform_impl/web/stdweb/scaling.rs new file mode 100644 index 0000000000..28024735f1 --- /dev/null +++ b/src/platform_impl/web/stdweb/scaling.rs @@ -0,0 +1,13 @@ +use super::super::ScaleChangeArgs; + +pub struct ScaleChangeDetector(()); + +impl ScaleChangeDetector { + pub(crate) fn new(_handler: F) -> Self + where + F: 'static + FnMut(ScaleChangeArgs), + { + // TODO: Stub, unimplemented (see web_sys for reference). + Self(()) + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index a4268b5272..338869acd3 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -1,8 +1,10 @@ mod canvas; mod event; +mod scaling; mod timeout; pub use self::canvas::Canvas; +pub use self::scaling::ScaleChangeDetector; pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; diff --git a/src/platform_impl/web/web_sys/scaling.rs b/src/platform_impl/web/web_sys/scaling.rs new file mode 100644 index 0000000000..5018257fa5 --- /dev/null +++ b/src/platform_impl/web/web_sys/scaling.rs @@ -0,0 +1,110 @@ +use super::super::ScaleChangeArgs; + +use std::{cell::RefCell, rc::Rc}; +use wasm_bindgen::{prelude::Closure, JsCast}; +use web_sys::{MediaQueryList, MediaQueryListEvent}; + +pub struct ScaleChangeDetector(Rc>); + +impl ScaleChangeDetector { + pub(crate) fn new(handler: F) -> Self + where + F: 'static + FnMut(ScaleChangeArgs), + { + Self(ScaleChangeDetectorInternal::new(handler)) + } +} + +/// This is a helper type to help manage the `MediaQueryList` used for detecting +/// changes of the `devicePixelRatio`. +struct ScaleChangeDetectorInternal { + callback: Box, + closure: Option>, + mql: Option, + last_scale: f64, +} + +impl ScaleChangeDetectorInternal { + fn new(handler: F) -> Rc> + where + F: 'static + FnMut(ScaleChangeArgs), + { + let current_scale = super::scale_factor(); + let new_self = Rc::new(RefCell::new(Self { + callback: Box::new(handler), + closure: None, + mql: None, + last_scale: current_scale, + })); + + let cloned_self = new_self.clone(); + let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| { + cloned_self.borrow_mut().handler(event) + }) as Box); + + let mql = Self::create_mql(&closure); + { + let mut borrowed_self = new_self.borrow_mut(); + borrowed_self.closure = Some(closure); + borrowed_self.mql = mql; + } + new_self + } + + fn create_mql(closure: &Closure) -> Option { + let window = web_sys::window().expect("Failed to obtain window"); + let current_scale = super::scale_factor(); + // This media query initially matches the current `devicePixelRatio`. + // We add 0.0001 to the lower and upper bounds such that it won't fail + // due to floating point precision limitations. + let media_query = format!( + "(min-resolution: {:.4}dppx) and (max-resolution: {:.4}dppx)", + current_scale - 0.0001, + current_scale + 0.0001, + ); + window + .match_media(&media_query) + .ok() + .flatten() + .and_then(|mql| { + assert_eq!(mql.matches(), true); + mql.add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref())) + .map(|_| mql) + .ok() + }) + } + + fn handler(&mut self, event: MediaQueryListEvent) { + assert_eq!(event.matches(), false); + let closure = self + .closure + .as_ref() + .expect("DevicePixelRatioChangeDetector::closure should not be None"); + let mql = self + .mql + .take() + .expect("DevicePixelRatioChangeDetector::mql should not be None"); + mql.remove_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref())) + .expect("Failed to remove listener from MediaQueryList"); + let new_scale = super::scale_factor(); + (self.callback)(ScaleChangeArgs { + old_scale: self.last_scale, + new_scale, + }); + let new_mql = Self::create_mql(closure); + self.mql = new_mql; + self.last_scale = new_scale; + } +} + +impl Drop for ScaleChangeDetectorInternal { + fn drop(&mut self) { + match (self.closure.as_ref(), self.mql.as_ref()) { + (Some(closure), Some(mql)) => { + let _ = + mql.remove_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref())); + } + _ => {} + } + } +} From e2cf2a5754f6ee51a37fbe6e1f8b5ff8bd98da49 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sun, 6 Sep 2020 07:41:19 -0700 Subject: [PATCH 173/239] Fix inverted horizontal scroll on macOS In winit the swipe from left to right on touchpad should generate positive horizontal delta change, however on macOS it was the other way around without natural scrolling. This commit inverses the horizontal scrolling delta in 'MouseScrollDelta' events to match other platforms. Fixes #1695. --- CHANGELOG.md | 1 + src/platform_impl/macos/view.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb312ca80..3658c23da8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - On NetBSD, fixed crash due to incorrect detection of the main thread. - **Breaking:** The virtual key code `Subtract` has been renamed to `NumpadSubtract` - **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract` +- On macOS, fix inverted horizontal scroll. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 357515007a..2c379e0ac6 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -1003,7 +1003,8 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { let state = &mut *(state_ptr as *mut ViewState); let delta = { - let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); + // macOS horizontal sign convention is the inverse of winit. + let (x, y) = (event.scrollingDeltaX() * -1.0, event.scrollingDeltaY()); if event.hasPreciseScrollingDeltas() == YES { let delta = LogicalPosition::new(x, y).to_physical(state.get_scale_factor()); MouseScrollDelta::PixelDelta(delta) From cac627ed05588ef6cbbce7a1e837a0b4016e61fe Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 7 Sep 2020 20:09:24 +0300 Subject: [PATCH 174/239] Make 'current_monitor' return 'Option' On certain platforms window couldn't be on any monitor resulting in failures of 'current_monitor' function. Such issue was happening on Wayland, since the window isn't on any monitor, unless the user has drawn something into it. Returning 'Option' will give an ability to handle such situations gracefully by properly indicating that there's no current monitor. Fixes #793. --- CHANGELOG.md | 5 +++-- examples/multithreaded.rs | 6 +++--- examples/window_debug.rs | 4 ++-- src/platform_impl/android/mod.rs | 6 +++--- src/platform_impl/ios/window.rs | 9 +++++++-- src/platform_impl/linux/mod.rs | 19 ++++++++++++++++--- src/platform_impl/linux/wayland/window.rs | 12 +++++++----- src/platform_impl/macos/window.rs | 8 +++++++- src/platform_impl/macos/window_delegate.rs | 3 ++- src/platform_impl/web/window.rs | 10 ++++++++-- src/platform_impl/windows/window.rs | 6 +++--- src/window.rs | 6 ++++-- 12 files changed, 65 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3658c23da8..3acb79f4ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,9 +25,10 @@ - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. -- **Breaking:** The virtual key code `Subtract` has been renamed to `NumpadSubtract` -- **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract` +- **Breaking:** The virtual key code `Subtract` has been renamed to `NumpadSubtract`. +- **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`. - On macOS, fix inverted horizontal scroll. +- **Breaking:** `current_monitor` now returns `Option`. # 0.22.2 (2020-05-16) diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 60f9d802de..4141775e47 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -21,7 +21,7 @@ fn main() { .build(&event_loop) .unwrap(); - let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect(); + let mut video_modes: Vec<_> = window.current_monitor().unwrap().video_modes().collect(); let mut video_mode_id = 0usize; let (tx, rx) = mpsc::channel(); @@ -34,7 +34,7 @@ fn main() { // was moved to an another monitor, so that the window // appears on this monitor instead when we go fullscreen let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id); - video_modes = window.current_monitor().video_modes().collect(); + video_modes = window.current_monitor().unwrap().video_modes().collect(); video_mode_id = video_mode_id.min(video_modes.len()); let video_mode = video_modes.iter().nth(video_mode_id); @@ -83,7 +83,7 @@ fn main() { } F => window.set_fullscreen(match (state, modifiers.alt()) { (true, false) => { - Some(Fullscreen::Borderless(window.current_monitor())) + Some(Fullscreen::Borderless(window.current_monitor().unwrap())) } (true, true) => Some(Fullscreen::Exclusive( video_modes.iter().nth(video_mode_id).unwrap().clone(), diff --git a/examples/window_debug.rs b/examples/window_debug.rs index f6e960a794..e8de3cf011 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -70,7 +70,7 @@ fn main() { size.width * size.height } - let monitor = window.current_monitor(); + let monitor = window.current_monitor().unwrap(); if let Some(mode) = monitor .video_modes() .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) @@ -84,7 +84,7 @@ fn main() { if window.fullscreen().is_some() { window.set_fullscreen(None); } else { - let monitor = window.current_monitor(); + let monitor = window.current_monitor().unwrap(); window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 453815d872..db759ed50e 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -391,10 +391,10 @@ impl Window { v } - pub fn current_monitor(&self) -> monitor::MonitorHandle { - monitor::MonitorHandle { + pub fn current_monitor(&self) -> Option { + Some(monitor::MonitorHandle { inner: MonitorHandle, - } + }) } pub fn scale_factor(&self) -> f64 { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 5fa83e9027..2be710e29a 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -224,7 +224,7 @@ impl Inner { pub fn fullscreen(&self) -> Option { unsafe { - let monitor = self.current_monitor(); + let monitor = self.current_monitor_inner(); let uiscreen = monitor.inner.ui_screen(); let screen_space_bounds = self.screen_frame(); let screen_bounds: CGRect = msg_send![uiscreen, bounds]; @@ -258,7 +258,8 @@ impl Inner { warn!("`Window::set_ime_position` is ignored on iOS") } - pub fn current_monitor(&self) -> RootMonitorHandle { + // Allow directly accessing the current monitor internally without unwrapping. + fn current_monitor_inner(&self) -> RootMonitorHandle { unsafe { let uiscreen: id = msg_send![self.window, screen]; RootMonitorHandle { @@ -267,6 +268,10 @@ impl Inner { } } + pub fn current_monitor(&self) -> Option { + Some(self.current_monitor_inner()) + } + pub fn available_monitors(&self) -> VecDeque { unsafe { monitor::uiscreens() } } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 73b8477760..c2214aceb8 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -425,9 +425,22 @@ impl Window { } #[inline] - pub fn current_monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: x11_or_wayland!(match self; Window(window) => window.current_monitor(); as MonitorHandle), + pub fn current_monitor(&self) -> Option { + match self { + #[cfg(feature = "x11")] + &Window::X(ref window) => { + let current_monitor = MonitorHandle::X(window.current_monitor()); + Some(RootMonitorHandle { + inner: current_monitor, + }) + } + #[cfg(feature = "wayland")] + &Window::Wayland(ref window) => { + let current_monitor = MonitorHandle::Wayland(window.current_monitor()?); + Some(RootMonitorHandle { + inner: current_monitor, + }) + } } } diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index a0be1e9d7c..11c38466f4 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -334,8 +334,10 @@ impl Window { pub fn fullscreen(&self) -> Option { if *(self.fullscreen.lock().unwrap()) { + // If the monitor cannot be determined, we cannot report any fullscreen mode. + let current_monitor = self.current_monitor()?; Some(Fullscreen::Borderless(RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(self.current_monitor()), + inner: PlatformMonitorHandle::Wayland(current_monitor), })) } else { None @@ -396,12 +398,12 @@ impl Window { &self.surface } - pub fn current_monitor(&self) -> MonitorHandle { - let output = get_outputs(&self.surface).last().unwrap().clone(); - MonitorHandle { + pub fn current_monitor(&self) -> Option { + let output = get_outputs(&self.surface).last()?.clone(); + Some(MonitorHandle { proxy: output, mgr: self.outputs.clone(), - } + }) } pub fn available_monitors(&self) -> VecDeque { diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 175cf73345..919fc2c781 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -972,7 +972,8 @@ impl UnownedWindow { } #[inline] - pub fn current_monitor(&self) -> RootMonitorHandle { + // Allow directly accessing the current monitor internally without unwrapping. + pub(crate) fn current_monitor_inner(&self) -> RootMonitorHandle { unsafe { let screen: id = msg_send![*self.ns_window, screen]; let desc = NSScreen::deviceDescription(screen); @@ -985,6 +986,11 @@ impl UnownedWindow { } } + #[inline] + pub fn current_monitor(&self) -> Option { + Some(self.current_monitor_inner()) + } + #[inline] pub fn available_monitors(&self) -> VecDeque { monitor::available_monitors() diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 8c49e773be..9d106e1518 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -450,7 +450,8 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { // Otherwise, we must've reached fullscreen by the user clicking // on the green fullscreen button. Update state! None => { - shared_state.fullscreen = Some(Fullscreen::Borderless(window.current_monitor())) + let current_monitor = window.current_monitor_inner(); + shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) } } shared_state.in_fullscreen_transition = true; diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index bcf701164e..5f2cae628a 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -201,7 +201,7 @@ impl Window { #[inline] pub fn fullscreen(&self) -> Option { if self.canvas.is_fullscreen() { - Some(Fullscreen::Borderless(self.current_monitor())) + Some(Fullscreen::Borderless(self.current_monitor_inner())) } else { None } @@ -237,12 +237,18 @@ impl Window { } #[inline] - pub fn current_monitor(&self) -> RootMH { + // Allow directly accessing the current monitor internally without unwrapping. + fn current_monitor_inner(&self) -> RootMH { RootMH { inner: monitor::Handle, } } + #[inline] + pub fn current_monitor(&self) -> Option { + Some(self.current_monitor_inner()) + } + #[inline] pub fn available_monitors(&self) -> VecDequeIter { VecDeque::new().into_iter() diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 3b8dc98f51..34406310df 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -577,10 +577,10 @@ impl Window { } #[inline] - pub fn current_monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { + pub fn current_monitor(&self) -> Option { + Some(RootMonitorHandle { inner: monitor::current_monitor(self.window.0), - } + }) } #[inline] diff --git a/src/window.rs b/src/window.rs index a00cd7c028..b8bec50bf0 100644 --- a/src/window.rs +++ b/src/window.rs @@ -736,13 +736,15 @@ impl Window { /// Monitor info functions. impl Window { - /// Returns the monitor on which the window currently resides + /// Returns the monitor on which the window currently resides. + /// + /// Returns `None` if current monitor can't be detected. /// /// ## Platform-specific /// /// **iOS:** Can only be called on the main thread. #[inline] - pub fn current_monitor(&self) -> MonitorHandle { + pub fn current_monitor(&self) -> Option { self.window.current_monitor() } From d103dc263104c4ceb8607dc3f8d150f80f915a4e Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Mon, 7 Sep 2020 20:20:47 +0300 Subject: [PATCH 175/239] Make 'primary_monitor' return 'Option' Certain platforms like Wayland don't have a concept of primary Monitor in particular. To indicate that 'primary_monitor' will return 'None' as well as in cases where the primary monitor can't be detected. Fixes #1683. --- CHANGELOG.md | 1 + examples/video_modes.rs | 8 +++++- src/event_loop.rs | 12 ++++++--- src/platform_impl/android/mod.rs | 14 +++++++---- src/platform_impl/ios/event_loop.rs | 7 ++++-- src/platform_impl/ios/window.rs | 5 ++-- src/platform_impl/linux/mod.rs | 25 +++++++++++++------ src/platform_impl/linux/wayland/event_loop.rs | 18 +++---------- src/platform_impl/linux/wayland/window.rs | 8 +++--- src/platform_impl/macos/event_loop.rs | 6 +++-- src/platform_impl/macos/window.rs | 5 ++-- .../web/event_loop/window_target.rs | 7 ++++-- src/platform_impl/web/window.rs | 6 +++-- src/platform_impl/windows/event_loop.rs | 6 +++-- src/platform_impl/windows/monitor.rs | 5 ++-- src/window.rs | 9 ++++--- 16 files changed, 86 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3acb79f4ac..3690e2af97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`. - On macOS, fix inverted horizontal scroll. - **Breaking:** `current_monitor` now returns `Option`. +- **Breaking:** `primary_monitor` now returns `Option`. # 0.22.2 (2020-05-16) diff --git a/examples/video_modes.rs b/examples/video_modes.rs index 366c519591..e19a0b03f6 100644 --- a/examples/video_modes.rs +++ b/examples/video_modes.rs @@ -3,7 +3,13 @@ use winit::event_loop::EventLoop; fn main() { simple_logger::init().unwrap(); let event_loop = EventLoop::new(); - let monitor = event_loop.primary_monitor(); + let monitor = match event_loop.primary_monitor() { + Some(monitor) => monitor, + None => { + println!("No primary monitor detected."); + return; + } + }; println!("Listing available video modes:"); diff --git a/src/event_loop.rs b/src/event_loop.rs index 02402930d7..efc67efd0b 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -178,11 +178,15 @@ impl EventLoopWindowTarget { } /// Returns the primary monitor of the system. + /// + /// Returns `None` if it can't identify any monitor as a primary one. + /// + /// ## Platform-specific + /// + /// **Wayland:** Always returns `None`. #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle { - inner: self.p.primary_monitor(), - } + pub fn primary_monitor(&self) -> Option { + self.p.primary_monitor() } } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index db759ed50e..471dd15d54 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -333,13 +333,15 @@ pub struct EventLoopWindowTarget { } impl EventLoopWindowTarget { - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle + pub fn primary_monitor(&self) -> Option { + Some(monitor::MonitorHandle { + inner: MonitorHandle, + }) } pub fn available_monitors(&self) -> VecDeque { let mut v = VecDeque::with_capacity(1); - v.push_back(self.primary_monitor()); + v.push_back(MonitorHandle); v } } @@ -381,8 +383,10 @@ impl Window { WindowId } - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle + pub fn primary_monitor(&self) -> Option { + Some(monitor::MonitorHandle { + inner: MonitorHandle, + }) } pub fn available_monitors(&self) -> VecDeque { diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 9536e94250..1680c98a8e 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -13,6 +13,7 @@ use crate::{ event_loop::{ ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget, }, + monitor::MonitorHandle as RootMonitorHandle, platform::ios::Idiom, }; @@ -56,9 +57,11 @@ impl EventLoopWindowTarget { unsafe { monitor::uiscreens() } } - pub fn primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> Option { // guaranteed to be on main thread - unsafe { monitor::main_uiscreen() } + let monitor = unsafe { monitor::main_uiscreen() }; + + Some(RootMonitorHandle { inner: monitor }) } } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 2be710e29a..a7deb61b10 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -276,8 +276,9 @@ impl Inner { unsafe { monitor::uiscreens() } } - pub fn primary_monitor(&self) -> MonitorHandle { - unsafe { monitor::main_uiscreen() } + pub fn primary_monitor(&self) -> Option { + let monitor = unsafe { monitor::main_uiscreen() }; + Some(RootMonitorHandle { inner: monitor }) } pub fn id(&self) -> WindowId { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index c2214aceb8..43fd84518a 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -463,8 +463,18 @@ impl Window { } #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - x11_or_wayland!(match self; Window(window) => window.primary_monitor(); as MonitorHandle) + pub fn primary_monitor(&self) -> Option { + match self { + #[cfg(feature = "x11")] + &Window::X(ref window) => { + let primary_monitor = MonitorHandle::X(window.primary_monitor()); + Some(RootMonitorHandle { + inner: primary_monitor, + }) + } + #[cfg(feature = "wayland")] + &Window::Wayland(ref window) => window.primary_monitor(), + } } pub fn raw_window_handle(&self) -> RawWindowHandle { @@ -682,15 +692,16 @@ impl EventLoopWindowTarget { } #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { + pub fn primary_monitor(&self) -> Option { match *self { #[cfg(feature = "wayland")] - EventLoopWindowTarget::Wayland(ref evlp) => { - MonitorHandle::Wayland(evlp.primary_monitor()) - } + EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(), #[cfg(feature = "x11")] EventLoopWindowTarget::X(ref evlp) => { - MonitorHandle::X(evlp.x_connection().primary_monitor()) + let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor()); + Some(RootMonitorHandle { + inner: primary_monitor, + }) } } } diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 77215b9ce9..4dd66f1223 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -648,8 +648,9 @@ impl EventLoopWindowTarget { available_monitors(&self.outputs) } - pub fn primary_monitor(&self) -> MonitorHandle { - primary_monitor(&self.outputs) + pub fn primary_monitor(&self) -> Option { + // Wayland doesn't have a notion of primary monitor. + None } } @@ -1121,19 +1122,6 @@ impl MonitorHandle { } } -pub fn primary_monitor(outputs: &OutputMgr) -> MonitorHandle { - outputs.with_all(|list| { - if let Some(&(_, ref proxy, _)) = list.first() { - MonitorHandle { - proxy: proxy.clone(), - mgr: outputs.clone(), - } - } else { - panic!("No monitor is available.") - } - }) -} - pub fn available_monitors(outputs: &OutputMgr) -> VecDeque { outputs.with_all(|list| { list.iter() diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 11c38466f4..c61ff19e0c 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -10,8 +10,7 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, monitor::MonitorHandle as RootMonitorHandle, platform_impl::{ - platform::wayland::event_loop::{available_monitors, primary_monitor}, - MonitorHandle as PlatformMonitorHandle, + platform::wayland::event_loop::available_monitors, MonitorHandle as PlatformMonitorHandle, PlatformSpecificWindowBuilderAttributes as PlAttributes, }, window::{CursorIcon, Fullscreen, WindowAttributes}, @@ -410,8 +409,9 @@ impl Window { available_monitors(&self.outputs) } - pub fn primary_monitor(&self) -> MonitorHandle { - primary_monitor(&self.outputs) + pub fn primary_monitor(&self) -> Option { + // Wayland doesn't have a notion of primary monitor. + None } pub fn raw_window_handle(&self) -> WaylandHandle { diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 0d88d97984..5b3d96b068 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -12,6 +12,7 @@ use cocoa::{ use crate::{ event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, + monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ app::APP_CLASS, app_delegate::APP_DELEGATE_CLASS, @@ -41,8 +42,9 @@ impl EventLoopWindowTarget { } #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - monitor::primary_monitor() + pub fn primary_monitor(&self) -> Option { + let monitor = monitor::primary_monitor(); + Some(RootMonitorHandle { inner: monitor }) } } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 919fc2c781..d2b014038a 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -997,8 +997,9 @@ impl UnownedWindow { } #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - monitor::primary_monitor() + pub fn primary_monitor(&self) -> Option { + let monitor = monitor::primary_monitor(); + Some(RootMonitorHandle { inner: monitor }) } #[inline] diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 0815bd40b7..f26f984a65 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -2,6 +2,7 @@ use super::{super::monitor, backend, device, proxy::Proxy, runner, window}; use crate::dpi::{PhysicalSize, Size}; use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; +use crate::monitor::MonitorHandle as RootMH; use crate::window::{Theme, WindowId}; use std::clone::Clone; use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; @@ -237,7 +238,9 @@ impl WindowTarget { VecDeque::new().into_iter() } - pub fn primary_monitor(&self) -> monitor::Handle { - monitor::Handle + pub fn primary_monitor(&self) -> Option { + Some(RootMH { + inner: monitor::Handle, + }) } } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 5f2cae628a..165661fafa 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -255,8 +255,10 @@ impl Window { } #[inline] - pub fn primary_monitor(&self) -> monitor::Handle { - monitor::Handle + pub fn primary_monitor(&self) -> Option { + Some(RootMH { + inner: monitor::Handle, + }) } #[inline] diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index eb0a65762b..9227bceb5f 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -34,6 +34,7 @@ use crate::{ dpi::{PhysicalPosition, PhysicalSize}, event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, + monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ dark_mode::try_dark_mode, dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, @@ -255,8 +256,9 @@ impl EventLoopWindowTarget { monitor::available_monitors() } - pub fn primary_monitor(&self) -> MonitorHandle { - monitor::primary_monitor() + pub fn primary_monitor(&self) -> Option { + let monitor = monitor::primary_monitor(); + Some(RootMonitorHandle { inner: monitor }) } } diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index 7fa4c73dc5..ff88d102db 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -131,8 +131,9 @@ impl Window { available_monitors() } - pub fn primary_monitor(&self) -> MonitorHandle { - primary_monitor() + pub fn primary_monitor(&self) -> Option { + let monitor = primary_monitor(); + Some(RootMonitorHandle { inner: monitor }) } } diff --git a/src/window.rs b/src/window.rs index b8bec50bf0..1977d32cb4 100644 --- a/src/window.rs +++ b/src/window.rs @@ -765,16 +765,17 @@ impl Window { /// Returns the primary monitor of the system. /// + /// Returns `None` if it can't identify any monitor as a primary one. + /// /// This is the same as `EventLoopWindowTarget::primary_monitor`, and is provided for convenience. /// /// ## Platform-specific /// /// **iOS:** Can only be called on the main thread. + /// **Wayland:** Always returns `None`. #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle { - inner: self.window.primary_monitor(), - } + pub fn primary_monitor(&self) -> Option { + self.window.primary_monitor() } } From 21f9aefc7ed4a564f20985dcac032d504cc7ce3b Mon Sep 17 00:00:00 2001 From: Josh Groves Date: Mon, 7 Sep 2020 18:13:51 -0230 Subject: [PATCH 176/239] Update macOS dependencies Fixes #1658. --- CHANGELOG.md | 1 + Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3690e2af97..638b3a29a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - On macOS, fix inverted horizontal scroll. - **Breaking:** `current_monitor` now returns `Option`. - **Breaking:** `primary_monitor` now returns `Option`. +- On macOS, updated core-* dependencies and cocoa. # 0.22.2 (2020-05-16) diff --git a/Cargo.toml b/Cargo.toml index 9659f7cf72..31b060d535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,9 +45,9 @@ ndk-glue = "0.1.0" objc = "0.2.3" [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.20" -core-foundation = "0.7" -core-graphics = "0.19" +cocoa = "0.23" +core-foundation = "0.9" +core-graphics = "0.22" dispatch = "0.2.0" objc = "0.2.6" From c66489dbb111ed5a4d27265edbcd5d965382ac94 Mon Sep 17 00:00:00 2001 From: Logan Magee Date: Wed, 9 Sep 2020 12:56:48 -0800 Subject: [PATCH 177/239] Bump parking_lot to 0.11 Fixes #1657. --- CHANGELOG.md | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 638b3a29a2..e45560f816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - **Breaking:** `current_monitor` now returns `Option`. - **Breaking:** `primary_monitor` now returns `Option`. - On macOS, updated core-* dependencies and cocoa. +- Bump `parking_lot` to 0.11 # 0.22.2 (2020-05-16) diff --git a/Cargo.toml b/Cargo.toml index 31b060d535..189585e630 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ x11-dl = { version = "2.18.5", optional = true } percent-encoding = "2.0" [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot] -version = "0.10" +version = "0.11" [target.'cfg(target_arch = "wasm32")'.dependencies.web_sys] package = "web-sys" From e4754999b7e7f27786092a62eda5275672d74130 Mon Sep 17 00:00:00 2001 From: Logan Magee Date: Wed, 9 Sep 2020 17:58:30 -0800 Subject: [PATCH 178/239] Replace deprecated simple_logger initialization --- examples/control_flow.rs | 3 ++- examples/cursor.rs | 3 ++- examples/cursor_grab.rs | 3 ++- examples/custom_events.rs | 3 ++- examples/fullscreen.rs | 4 +++- examples/handling_close.rs | 3 ++- examples/min_max_size.rs | 3 ++- examples/minimize.rs | 3 ++- examples/monitor_list.rs | 3 ++- examples/multithreaded.rs | 3 ++- examples/multiwindow.rs | 4 +++- examples/request_redraw.rs | 3 ++- examples/request_redraw_threaded.rs | 3 ++- examples/resizable.rs | 3 ++- examples/timer.rs | 4 +++- examples/transparent.rs | 3 ++- examples/video_modes.rs | 3 ++- examples/window.rs | 3 ++- examples/window_debug.rs | 3 ++- examples/window_icon.rs | 4 +++- examples/window_run_return.rs | 4 +++- 21 files changed, 47 insertions(+), 21 deletions(-) diff --git a/examples/control_flow.rs b/examples/control_flow.rs index 6d71541216..64372a9bc8 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -1,5 +1,6 @@ use std::{thread, time}; +use simple_logger::SimpleLogger; use winit::{ event::{Event, KeyboardInput, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -17,7 +18,7 @@ const WAIT_TIME: time::Duration = time::Duration::from_millis(100); const POLL_SLEEP_TIME: time::Duration = time::Duration::from_millis(100); fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); println!("Press '1' to switch to Wait mode."); println!("Press '2' to switch to WaitUntil mode."); diff --git a/examples/cursor.rs b/examples/cursor.rs index de45adae33..a466e889a8 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -1,3 +1,4 @@ +use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyboardInput, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -5,7 +6,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 317577e3c5..90a94764de 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -1,3 +1,4 @@ +use simple_logger::SimpleLogger; use winit::{ event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -5,7 +6,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/custom_events.rs b/examples/custom_events.rs index c59299cab4..016754b590 100644 --- a/examples/custom_events.rs +++ b/examples/custom_events.rs @@ -1,5 +1,6 @@ #[cfg(not(target_arch = "wasm32"))] fn main() { + use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -11,7 +12,7 @@ fn main() { Timer, } - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::::with_user_event(); let _window = WindowBuilder::new() diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 4cdb7b63ed..3c5ed185b0 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,11 +1,13 @@ use std::io::{stdin, stdout, Write}; + +use simple_logger::SimpleLogger; use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::monitor::{MonitorHandle, VideoMode}; use winit::window::{Fullscreen, WindowBuilder}; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: "); diff --git a/examples/handling_close.rs b/examples/handling_close.rs index cbd5705369..8334c1773f 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -1,3 +1,4 @@ +use simple_logger::SimpleLogger; use winit::{ event::{Event, KeyboardInput, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -5,7 +6,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let _window = WindowBuilder::new() diff --git a/examples/min_max_size.rs b/examples/min_max_size.rs index f4fd00c1bb..9a58ed6c69 100644 --- a/examples/min_max_size.rs +++ b/examples/min_max_size.rs @@ -1,3 +1,4 @@ +use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, event::{Event, WindowEvent}, @@ -6,7 +7,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); diff --git a/examples/minimize.rs b/examples/minimize.rs index 871cfe2edb..eb02a752c9 100644 --- a/examples/minimize.rs +++ b/examples/minimize.rs @@ -1,11 +1,12 @@ extern crate winit; +use simple_logger::SimpleLogger; use winit::event::{Event, VirtualKeyCode, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::WindowBuilder; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/monitor_list.rs b/examples/monitor_list.rs index 66f48bafeb..9c8b77e61c 100644 --- a/examples/monitor_list.rs +++ b/examples/monitor_list.rs @@ -1,7 +1,8 @@ +use simple_logger::SimpleLogger; use winit::{event_loop::EventLoop, window::WindowBuilder}; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 4141775e47..5134fbfb2f 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -2,6 +2,7 @@ fn main() { use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; + use simple_logger::SimpleLogger; use winit::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, @@ -12,7 +13,7 @@ fn main() { const WINDOW_COUNT: usize = 3; const WINDOW_SIZE: PhysicalSize = PhysicalSize::new(600, 400); - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); for _ in 0..WINDOW_COUNT { diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 01c91bb646..ec97eee097 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -1,4 +1,6 @@ use std::collections::HashMap; + +use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, KeyboardInput, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -6,7 +8,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let mut windows = HashMap::new(); diff --git a/examples/request_redraw.rs b/examples/request_redraw.rs index 5d761ae479..163f6a14d2 100644 --- a/examples/request_redraw.rs +++ b/examples/request_redraw.rs @@ -1,3 +1,4 @@ +use simple_logger::SimpleLogger; use winit::{ event::{ElementState, Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -5,7 +6,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/request_redraw_threaded.rs b/examples/request_redraw_threaded.rs index 341612ca64..1456f25448 100644 --- a/examples/request_redraw_threaded.rs +++ b/examples/request_redraw_threaded.rs @@ -1,5 +1,6 @@ use std::{thread, time}; +use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -7,7 +8,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/resizable.rs b/examples/resizable.rs index 6a90391937..17892d8741 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -1,3 +1,4 @@ +use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, @@ -6,7 +7,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let mut resizable = false; diff --git a/examples/timer.rs b/examples/timer.rs index 52a1444ec2..7bbb9685f4 100644 --- a/examples/timer.rs +++ b/examples/timer.rs @@ -1,5 +1,7 @@ use instant::Instant; use std::time::Duration; + +use simple_logger::SimpleLogger; use winit::{ event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -7,7 +9,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let _window = WindowBuilder::new() diff --git a/examples/transparent.rs b/examples/transparent.rs index 78e3c4da33..c9937cd4cd 100644 --- a/examples/transparent.rs +++ b/examples/transparent.rs @@ -1,3 +1,4 @@ +use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -5,7 +6,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/video_modes.rs b/examples/video_modes.rs index e19a0b03f6..341f43855b 100644 --- a/examples/video_modes.rs +++ b/examples/video_modes.rs @@ -1,7 +1,8 @@ +use simple_logger::SimpleLogger; use winit::event_loop::EventLoop; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let monitor = match event_loop.primary_monitor() { Some(monitor) => monitor, diff --git a/examples/window.rs b/examples/window.rs index c028a50f3f..783578de65 100644 --- a/examples/window.rs +++ b/examples/window.rs @@ -1,3 +1,4 @@ +use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -5,7 +6,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/window_debug.rs b/examples/window_debug.rs index e8de3cf011..2771b37072 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -1,5 +1,6 @@ // This example is used by developers to test various window functions. +use simple_logger::SimpleLogger; use winit::{ dpi::{LogicalSize, PhysicalSize}, event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, @@ -8,7 +9,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let event_loop = EventLoop::new(); let window = WindowBuilder::new() diff --git a/examples/window_icon.rs b/examples/window_icon.rs index 734debf4d4..aaa6bc0ba7 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -1,5 +1,7 @@ extern crate image; use std::path::Path; + +use simple_logger::SimpleLogger; use winit::{ event::Event, event_loop::{ControlFlow, EventLoop}, @@ -7,7 +9,7 @@ use winit::{ }; fn main() { - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); // You'll have to choose an icon size at your own discretion. On X11, the desired size varies // by WM, and on Windows, you still have to account for screen scaling. Here we use 32px, diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index 0112eb3e5b..066aa05452 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -10,6 +10,8 @@ ))] fn main() { use std::{thread::sleep, time::Duration}; + + use simple_logger::SimpleLogger; use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -18,7 +20,7 @@ fn main() { }; let mut event_loop = EventLoop::new(); - simple_logger::init().unwrap(); + SimpleLogger::new().init().unwrap(); let _window = WindowBuilder::new() .with_title("A fantastic window!") .build(&event_loop) From 83c95e774d6ee3dc005ffdafb0b4a68d1bfb146b Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Thu, 17 Sep 2020 21:58:53 +0800 Subject: [PATCH 179/239] Explicitly require simple_logger 1.9 for examples --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 189585e630..abc0aa80e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ bitflags = "1" [dev-dependencies] image = "0.23" -simple_logger = "1" +simple_logger = "1.9" [target.'cfg(target_os = "android")'.dependencies] ndk = "0.1.0" From 386ead15a3e3fef5c0c02bd7491d3cc9a7a6e0aa Mon Sep 17 00:00:00 2001 From: msiglreith Date: Fri, 18 Sep 2020 20:14:56 +0200 Subject: [PATCH 180/239] Android: bump ndk versions (#1708) * Bump ndk versions * Update README for new ndk proc attribute * android: add CHANGELOG entry to ndk vesion bump --- CHANGELOG.md | 1 + Cargo.toml | 6 +++--- README.md | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e45560f816..dce45aa1a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - **Breaking:** `primary_monitor` now returns `Option`. - On macOS, updated core-* dependencies and cocoa. - Bump `parking_lot` to 0.11 +- On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute. # 0.22.2 (2020-05-16) diff --git a/Cargo.toml b/Cargo.toml index abc0aa80e6..f5c6205235 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,9 +37,9 @@ image = "0.23" simple_logger = "1.9" [target.'cfg(target_os = "android")'.dependencies] -ndk = "0.1.0" -ndk-sys = "0.1.0" -ndk-glue = "0.1.0" +ndk = "0.2.0" +ndk-sys = "0.2.0" +ndk-glue = "0.2.0" [target.'cfg(target_os = "ios")'.dependencies] objc = "0.2.3" diff --git a/README.md b/README.md index c7b312f6b8..fc2ed91943 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,10 @@ crate-type = ["cdylib"] And add this to the example file to add the native activity glue: ```rust -#[cfg(target_os = "android")] -ndk_glue::ndk_glue!(main); +#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] +fn main() { + ... +} ``` And run the application with `cargo apk run --example request_redraw_threaded` From d612a1b5a10cc63413b0d7026eedcdab78d2d181 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 20 Sep 2020 12:58:24 +0300 Subject: [PATCH 181/239] Prefix numpad virtual key codes with Numpad This commit is a follow up to a2db4c0a320aafc10d240c432fe5ef4e4d84629d to make it clear which virtual key codes are located on numeric pad. It also adds Asterisk and Plus virtual key codes. --- CHANGELOG.md | 3 ++- src/event.rs | 10 ++++++---- src/platform_impl/linux/wayland/keyboard.rs | 12 ++++++------ src/platform_impl/linux/x11/events.rs | 12 ++++++------ src/platform_impl/macos/event.rs | 8 ++++---- src/platform_impl/web/stdweb/event.rs | 8 ++++---- src/platform_impl/web/web_sys/event.rs | 8 ++++---- src/platform_impl/windows/event.rs | 8 ++++---- 8 files changed, 36 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dce45aa1a3..0cc84b1aa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,6 @@ - **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error. - **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`. - On NetBSD, fixed crash due to incorrect detection of the main thread. -- **Breaking:** The virtual key code `Subtract` has been renamed to `NumpadSubtract`. - **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`. - On macOS, fix inverted horizontal scroll. - **Breaking:** `current_monitor` now returns `Option`. @@ -33,6 +32,8 @@ - On macOS, updated core-* dependencies and cocoa. - Bump `parking_lot` to 0.11 - On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute. +- **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`. +- Added `Asterisk` and `Plus` virtual key codes. # 0.22.2 (2020-05-16) diff --git a/src/event.rs b/src/event.rs index 134421aca1..5d89c7db0b 100644 --- a/src/event.rs +++ b/src/event.rs @@ -892,16 +892,20 @@ pub enum VirtualKeyCode { Numpad7, Numpad8, Numpad9, + NumpadAdd, + NumpadDivide, + NumpadDecimal, NumpadComma, NumpadEnter, NumpadEquals, + NumpadMultiply, NumpadSubtract, AbntC1, AbntC2, - Add, Apostrophe, Apps, + Asterisk, At, Ax, Backslash, @@ -910,8 +914,6 @@ pub enum VirtualKeyCode { Colon, Comma, Convert, - Decimal, - Divide, Equals, Grave, Kana, @@ -925,7 +927,6 @@ pub enum VirtualKeyCode { MediaSelect, MediaStop, Minus, - Multiply, Mute, MyComputer, // also called "Next" @@ -937,6 +938,7 @@ pub enum VirtualKeyCode { OEM102, Period, PlayPause, + Plus, Power, PrevTrack, RAlt, diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index fb9219e0d3..7c58e7c42e 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -305,7 +305,7 @@ fn keysym_to_vkey(keysym: u32) -> Option { // misc // => Some(VirtualKeyCode::AbntC1), // => Some(VirtualKeyCode::AbntC2), - keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Add), + keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus), keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe), // => Some(VirtualKeyCode::Apps), // => Some(VirtualKeyCode::At), @@ -316,8 +316,6 @@ fn keysym_to_vkey(keysym: u32) -> Option { keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon), keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma), // => Some(VirtualKeyCode::Convert), - // => Some(VirtualKeyCode::Decimal), - // => Some(VirtualKeyCode::Divide), keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals), // => Some(VirtualKeyCode::Grave), // => Some(VirtualKeyCode::Kana), @@ -331,7 +329,7 @@ fn keysym_to_vkey(keysym: u32) -> Option { // => Some(VirtualKeyCode::MediaSelect), // => Some(VirtualKeyCode::MediaStop), keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus), - keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Multiply), + keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk), // => Some(VirtualKeyCode::Mute), // => Some(VirtualKeyCode::MyComputer), // => Some(VirtualKeyCode::NextTrack), @@ -339,9 +337,11 @@ fn keysym_to_vkey(keysym: u32) -> Option { keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), - keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add), + keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd), keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), - keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide), + keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply), + keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal), + keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide), keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), diff --git a/src/platform_impl/linux/x11/events.rs b/src/platform_impl/linux/x11/events.rs index f7d98b98da..0c02ca0c82 100644 --- a/src/platform_impl/linux/x11/events.rs +++ b/src/platform_impl/linux/x11/events.rs @@ -81,12 +81,12 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option { ffi::XK_KP_Insert => VirtualKeyCode::Insert, ffi::XK_KP_Delete => VirtualKeyCode::Delete, ffi::XK_KP_Equal => VirtualKeyCode::NumpadEquals, - //ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply, - ffi::XK_KP_Add => VirtualKeyCode::Add, + ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply, + ffi::XK_KP_Add => VirtualKeyCode::NumpadAdd, //ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator, ffi::XK_KP_Subtract => VirtualKeyCode::NumpadSubtract, - //ffi::XK_KP_Decimal => VirtualKeyCode::Kp_decimal, - ffi::XK_KP_Divide => VirtualKeyCode::Divide, + ffi::XK_KP_Decimal => VirtualKeyCode::NumpadDecimal, + ffi::XK_KP_Divide => VirtualKeyCode::NumpadDivide, ffi::XK_KP_0 => VirtualKeyCode::Numpad0, ffi::XK_KP_1 => VirtualKeyCode::Numpad1, ffi::XK_KP_2 => VirtualKeyCode::Numpad2, @@ -183,8 +183,8 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option { //ffi::XK_quoteright => VirtualKeyCode::Quoteright, //ffi::XK_parenleft => VirtualKeyCode::Parenleft, //ffi::XK_parenright => VirtualKeyCode::Parenright, - //ffi::XK_asterisk => VirtualKeyCode::Asterisk, - ffi::XK_plus => VirtualKeyCode::Add, + ffi::XK_asterisk => VirtualKeyCode::Asterisk, + ffi::XK_plus => VirtualKeyCode::Plus, ffi::XK_comma => VirtualKeyCode::Comma, ffi::XK_minus => VirtualKeyCode::Minus, ffi::XK_period => VirtualKeyCode::Period, diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index dd12c9737e..343aaeb286 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -156,17 +156,17 @@ pub fn scancode_to_keycode(scancode: c_ushort) -> Option { 0x3e => VirtualKeyCode::RControl, //0x3f => Fn key, 0x40 => VirtualKeyCode::F17, - 0x41 => VirtualKeyCode::Decimal, + 0x41 => VirtualKeyCode::NumpadDecimal, //0x42 -> unkown, - 0x43 => VirtualKeyCode::Multiply, + 0x43 => VirtualKeyCode::NumpadMultiply, //0x44 => unkown, - 0x45 => VirtualKeyCode::Add, + 0x45 => VirtualKeyCode::NumpadAdd, //0x46 => unkown, 0x47 => VirtualKeyCode::Numlock, //0x48 => KeypadClear, 0x49 => VirtualKeyCode::VolumeUp, 0x4a => VirtualKeyCode::VolumeDown, - 0x4b => VirtualKeyCode::Divide, + 0x4b => VirtualKeyCode::NumpadDivide, 0x4c => VirtualKeyCode::NumpadEnter, //0x4d => unkown, 0x4e => VirtualKeyCode::NumpadSubtract, diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/event.rs index e9bb1b38c5..d04298af3d 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/event.rs @@ -146,7 +146,7 @@ pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option { "Numpad9" => VirtualKeyCode::Numpad9, "AbntC1" => VirtualKeyCode::AbntC1, "AbntC2" => VirtualKeyCode::AbntC2, - "NumpadAdd" => VirtualKeyCode::Add, + "NumpadAdd" => VirtualKeyCode::NumpadAdd, "Quote" => VirtualKeyCode::Apostrophe, "Apps" => VirtualKeyCode::Apps, "At" => VirtualKeyCode::At, @@ -157,8 +157,8 @@ pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option { "Semicolon" => VirtualKeyCode::Semicolon, "Comma" => VirtualKeyCode::Comma, "Convert" => VirtualKeyCode::Convert, - "NumpadDecimal" => VirtualKeyCode::Decimal, - "NumpadDivide" => VirtualKeyCode::Divide, + "NumpadDecimal" => VirtualKeyCode::NumpadDecimal, + "NumpadDivide" => VirtualKeyCode::NumpadDivide, "Equal" => VirtualKeyCode::Equals, "Backquote" => VirtualKeyCode::Grave, "Kana" => VirtualKeyCode::Kana, @@ -172,7 +172,7 @@ pub fn virtual_key_code(event: &impl IKeyboardEvent) -> Option { "MediaSelect" => VirtualKeyCode::MediaSelect, "MediaStop" => VirtualKeyCode::MediaStop, "Minus" => VirtualKeyCode::Minus, - "NumpadMultiply" => VirtualKeyCode::Multiply, + "NumpadMultiply" => VirtualKeyCode::NumpadMultiply, "Mute" => VirtualKeyCode::Mute, "LaunchMyComputer" => VirtualKeyCode::MyComputer, "NavigateForward" => VirtualKeyCode::NavigateForward, diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 802915683b..3d2b77b127 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -155,7 +155,7 @@ pub fn virtual_key_code(event: &KeyboardEvent) -> Option { "Numpad9" => VirtualKeyCode::Numpad9, "AbntC1" => VirtualKeyCode::AbntC1, "AbntC2" => VirtualKeyCode::AbntC2, - "NumpadAdd" => VirtualKeyCode::Add, + "NumpadAdd" => VirtualKeyCode::NumpadAdd, "Quote" => VirtualKeyCode::Apostrophe, "Apps" => VirtualKeyCode::Apps, "At" => VirtualKeyCode::At, @@ -166,8 +166,8 @@ pub fn virtual_key_code(event: &KeyboardEvent) -> Option { "Semicolon" => VirtualKeyCode::Semicolon, "Comma" => VirtualKeyCode::Comma, "Convert" => VirtualKeyCode::Convert, - "NumpadDecimal" => VirtualKeyCode::Decimal, - "NumpadDivide" => VirtualKeyCode::Divide, + "NumpadDecimal" => VirtualKeyCode::NumpadDecimal, + "NumpadDivide" => VirtualKeyCode::NumpadDivide, "Equal" => VirtualKeyCode::Equals, "Backquote" => VirtualKeyCode::Grave, "Kana" => VirtualKeyCode::Kana, @@ -181,7 +181,7 @@ pub fn virtual_key_code(event: &KeyboardEvent) -> Option { "MediaSelect" => VirtualKeyCode::MediaSelect, "MediaStop" => VirtualKeyCode::MediaStop, "Minus" => VirtualKeyCode::Minus, - "NumpadMultiply" => VirtualKeyCode::Multiply, + "NumpadMultiply" => VirtualKeyCode::NumpadMultiply, "Mute" => VirtualKeyCode::Mute, "LaunchMyComputer" => VirtualKeyCode::MyComputer, "NavigateForward" => VirtualKeyCode::NavigateForward, diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 40312418bc..4b3b68d919 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -259,12 +259,12 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option { winuser::VK_NUMPAD7 => Some(VirtualKeyCode::Numpad7), winuser::VK_NUMPAD8 => Some(VirtualKeyCode::Numpad8), winuser::VK_NUMPAD9 => Some(VirtualKeyCode::Numpad9), - winuser::VK_MULTIPLY => Some(VirtualKeyCode::Multiply), - winuser::VK_ADD => Some(VirtualKeyCode::Add), + winuser::VK_MULTIPLY => Some(VirtualKeyCode::NumpadMultiply), + winuser::VK_ADD => Some(VirtualKeyCode::NumpadAdd), //winuser::VK_SEPARATOR => Some(VirtualKeyCode::Separator), winuser::VK_SUBTRACT => Some(VirtualKeyCode::NumpadSubtract), - winuser::VK_DECIMAL => Some(VirtualKeyCode::Decimal), - winuser::VK_DIVIDE => Some(VirtualKeyCode::Divide), + winuser::VK_DECIMAL => Some(VirtualKeyCode::NumpadDecimal), + winuser::VK_DIVIDE => Some(VirtualKeyCode::NumpadDivide), winuser::VK_F1 => Some(VirtualKeyCode::F1), winuser::VK_F2 => Some(VirtualKeyCode::F2), winuser::VK_F3 => Some(VirtualKeyCode::F3), From 1c97a310b10df2c6557cd8cc54cfe08881e5ed78 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Sun, 20 Sep 2020 18:41:44 -0400 Subject: [PATCH 182/239] Deprecate the stdweb backend (#1712) * Deprecate the stdweb backend * Add a changelog entry * Fmt * Move the deprecation notice --- CHANGELOG.md | 1 + src/platform_impl/web/stdweb/mod.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc84b1aa5..d9cf92998c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - On macOS, updated core-* dependencies and cocoa. - Bump `parking_lot` to 0.11 - On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute. +- Deprecate the stdweb backend, to be removed in a future release - **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`. - Added `Asterisk` and `Plus` virtual key codes. diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 2f38c178a8..6d517247dd 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -1,3 +1,5 @@ +#![deprecated(since = "0.23.0", note = "Please migrate to web-sys over stdweb")] + mod canvas; mod event; mod scaling; From 47e7aa4209af71bd9d549abbbfd78b1771e60fa7 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Mon, 21 Sep 2020 06:42:07 +0800 Subject: [PATCH 183/239] Add cleanup code to web backend, mostly web-sys (#1715) * web-sys: Impl. event listeners removal for canvas * web-sys: Impl. media query listeners cleanup * web: Emit WindowEvent::Destroyed after Window is dropped * web-sys: Fix unload event closure being dropped early * web: Impl. cleanup on ControlFlow::Exit - Drops the Runner, which causes the event handler closure to be dropped. - (web-sys only:) Remove event listeners from DOM. * web: Do not remove canvas from DOM when dropping Window The canvas was inserted by the user, so it should be up to the user whether the canvas should be removed. * Update changelog --- CHANGELOG.md | 5 + src/platform_impl/web/event_loop/runner.rs | 179 ++++++++++++++---- .../web/event_loop/window_target.rs | 9 +- src/platform_impl/web/stdweb/canvas.rs | 10 +- src/platform_impl/web/stdweb/mod.rs | 4 +- src/platform_impl/web/web_sys/canvas.rs | 98 +++++----- .../web/web_sys/canvas/mouse_handler.rs | 21 +- .../web/web_sys/canvas/pointer_handler.rs | 20 +- src/platform_impl/web/web_sys/event_handle.rs | 65 +++++++ .../web/web_sys/media_query_handle.rs | 56 ++++++ src/platform_impl/web/web_sys/mod.rs | 17 +- src/platform_impl/web/web_sys/scaling.rs | 57 ++---- src/platform_impl/web/window.rs | 55 ++++-- 13 files changed, 435 insertions(+), 161 deletions(-) create mode 100644 src/platform_impl/web/web_sys/event_handle.rs create mode 100644 src/platform_impl/web/web_sys/media_query_handle.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d9cf92998c..faed9c335e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,11 @@ - Deprecate the stdweb backend, to be removed in a future release - **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`. - Added `Asterisk` and `Plus` virtual key codes. +- On Web (web-sys only), the `Event::LoopDestroyed` event is correctly emitted when leaving the page. +- On Web, the `WindowEvent::Destroyed` event now gets emitted when a `Window` is dropped. +- On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed. +- On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed. +- **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 0ccd7862c2..af46e02472 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -9,7 +9,7 @@ use std::{ clone::Clone, collections::{HashSet, VecDeque}, iter, - rc::Rc, + rc::{Rc, Weak}, }; pub struct Shared(Rc>); @@ -21,12 +21,34 @@ impl Clone for Shared { } pub struct Execution { - runner: RefCell>>, + runner: RefCell>, events: RefCell>>, id: RefCell, - all_canvases: RefCell>, + all_canvases: RefCell>)>>, redraw_pending: RefCell>, + destroy_pending: RefCell>, scale_change_detector: RefCell>, + unload_event_handle: RefCell>, +} + +enum RunnerEnum { + /// The `EventLoop` is created but not being run. + Pending, + /// The `EventLoop` is being run. + Running(Runner), + /// The `EventLoop` is exited after being started with `EventLoop::run`. Since + /// `EventLoop::run` takes ownership of the `EventLoop`, we can be certain + /// that this event loop will never be run again. + Destroyed, +} + +impl RunnerEnum { + fn maybe_runner(&self) -> Option<&Runner> { + match self { + RunnerEnum::Running(runner) => Some(runner), + _ => None, + } + } } struct Runner { @@ -83,17 +105,26 @@ impl Runner { impl Shared { pub fn new() -> Self { Shared(Rc::new(Execution { - runner: RefCell::new(None), + runner: RefCell::new(RunnerEnum::Pending), events: RefCell::new(VecDeque::new()), id: RefCell::new(0), all_canvases: RefCell::new(Vec::new()), redraw_pending: RefCell::new(HashSet::new()), + destroy_pending: RefCell::new(VecDeque::new()), scale_change_detector: RefCell::new(None), + unload_event_handle: RefCell::new(None), })) } - pub fn add_canvas(&self, id: WindowId, canvas: backend::RawCanvasType) { - self.0.all_canvases.borrow_mut().push((id, canvas)); + pub fn add_canvas(&self, id: WindowId, canvas: &Rc>) { + self.0 + .all_canvases + .borrow_mut() + .push((id, Rc::downgrade(canvas))); + } + + pub fn notify_destroy_window(&self, id: WindowId) { + self.0.destroy_pending.borrow_mut().push_back(id); } // Set the event callback to use for the event loop runner @@ -103,11 +134,16 @@ impl Shared { &self, event_handler: Box, &mut root::ControlFlow)>, ) { - self.0.runner.replace(Some(Runner::new(event_handler))); + { + let mut runner = self.0.runner.borrow_mut(); + assert!(matches!(*runner, RunnerEnum::Pending)); + *runner = RunnerEnum::Running(Runner::new(event_handler)); + } self.init(); let close_instance = self.clone(); - backend::on_unload(move || close_instance.handle_unload()); + *self.0.unload_event_handle.borrow_mut() = + Some(backend::on_unload(move || close_instance.handle_unload())); } pub(crate) fn set_on_scale_change(&self, handler: F) @@ -169,18 +205,23 @@ impl Shared { } // If we can run the event processing right now, or need to queue this and wait for later let mut process_immediately = true; - if let Some(ref runner) = &*self.0.runner.borrow() { - // If we're currently polling, queue this and wait for the poll() method to be called - if let State::Poll { .. } = runner.state { - process_immediately = false; + match &*self.0.runner.borrow() { + RunnerEnum::Running(ref runner) => { + // If we're currently polling, queue this and wait for the poll() method to be called + if let State::Poll { .. } = runner.state { + process_immediately = false; + } + // If the runner is busy, queue this and wait for it to process it later + if runner.is_busy { + process_immediately = false; + } } - // If the runner is busy, queue this and wait for it to process it later - if runner.is_busy { + RunnerEnum::Pending => { + // The runner still hasn't been attached: queue this event and wait for it to be process_immediately = false; } - } else { - // The runner still hasn't been attached: queue this event and wait for it to be - process_immediately = false; + // This is unreachable since `self.is_closed() == true`. + RunnerEnum::Destroyed => unreachable!(), } if !process_immediately { // Queue these events to look at later @@ -189,7 +230,7 @@ impl Shared { } // At this point, we know this is a fresh set of events // Now we determine why new events are incoming, and handle the events - let start_cause = match (self.0.runner.borrow().as_ref()) + let start_cause = match (self.0.runner.borrow().maybe_runner()) .unwrap_or_else(|| { unreachable!("The runner cannot process events when it is not attached") }) @@ -206,6 +247,26 @@ impl Shared { self.run_until_cleared(events); } + // Process the destroy-pending windows. This should only be called from + // `run_until_cleared` and `handle_scale_changed`, somewhere between emitting + // `NewEvents` and `MainEventsCleared`. + fn process_destroy_pending_windows(&self, control: &mut root::ControlFlow) { + while let Some(id) = self.0.destroy_pending.borrow_mut().pop_front() { + self.0 + .all_canvases + .borrow_mut() + .retain(|&(item_id, _)| item_id != id); + self.handle_event( + Event::WindowEvent { + window_id: id, + event: crate::event::WindowEvent::Destroyed, + }, + control, + ); + self.0.redraw_pending.borrow_mut().remove(&id); + } + } + // Given the set of new events, run the event loop until the main events and redraw events are // cleared // @@ -215,6 +276,7 @@ impl Shared { for event in events { self.handle_event(event, &mut control); } + self.process_destroy_pending_windows(&mut control); self.handle_event(Event::MainEventsCleared, &mut control); // Collect all of the redraw events to avoid double-locking the RefCell @@ -228,12 +290,17 @@ impl Shared { // If the event loop is closed, it has been closed this iteration and now the closing // event should be emitted if self.is_closed() { - self.handle_event(Event::LoopDestroyed, &mut control); + self.handle_loop_destroyed(&mut control); } } pub fn handle_scale_changed(&self, old_scale: f64, new_scale: f64) { - let start_cause = match (self.0.runner.borrow().as_ref()) + // If there aren't any windows, then there is nothing to do here. + if self.0.all_canvases.borrow().is_empty() { + return; + } + + let start_cause = match (self.0.runner.borrow().maybe_runner()) .unwrap_or_else(|| unreachable!("`scale_changed` should not happen without a runner")) .maybe_start_cause() { @@ -246,8 +313,18 @@ impl Shared { // Handle the start event and all other events in the queue. self.handle_event(Event::NewEvents(start_cause), &mut control); + // It is possible for windows to be dropped before this point. We don't + // want to send `ScaleFactorChanged` for destroyed windows, so we process + // the destroy-pending windows here. + self.process_destroy_pending_windows(&mut control); + // Now handle the `ScaleFactorChanged` events. for &(id, ref canvas) in &*self.0.all_canvases.borrow() { + let canvas = match canvas.upgrade() { + Some(rc) => rc.borrow().raw().clone(), + // This shouldn't happen, but just in case... + None => continue, + }; // First, we send the `ScaleFactorChanged` event: let current_size = crate::dpi::PhysicalSize { width: canvas.width() as u32, @@ -267,7 +344,7 @@ impl Shared { ); // Then we resize the canvas to the new size and send a `Resized` event: - backend::set_canvas_size(canvas, crate::dpi::Size::Physical(new_size)); + backend::set_canvas_size(&canvas, crate::dpi::Size::Physical(new_size)); self.handle_single_event_sync( Event::WindowEvent { window_id: id, @@ -277,6 +354,8 @@ impl Shared { ); } + // Process the destroy-pending windows again. + self.process_destroy_pending_windows(&mut control); self.handle_event(Event::MainEventsCleared, &mut control); // Discard all the pending redraw as we shall just redraw all windows. @@ -290,13 +369,15 @@ impl Shared { // If the event loop is closed, it has been closed this iteration and now the closing // event should be emitted if self.is_closed() { - self.handle_event(Event::LoopDestroyed, &mut control); + self.handle_loop_destroyed(&mut control); } } fn handle_unload(&self) { self.apply_control_flow(root::ControlFlow::Exit); let mut control = self.current_control_flow(); + // We don't call `handle_loop_destroyed` here because we don't need to + // perform cleanup when the web browser is going to destroy the page. self.handle_event(Event::LoopDestroyed, &mut control); } @@ -308,7 +389,7 @@ impl Shared { *control = root::ControlFlow::Exit; } match *self.0.runner.borrow_mut() { - Some(ref mut runner) => { + RunnerEnum::Running(ref mut runner) => { runner.handle_single_event(event, control); } _ => panic!("Cannot handle event synchronously without a runner"), @@ -323,19 +404,21 @@ impl Shared { *control = root::ControlFlow::Exit; } match *self.0.runner.borrow_mut() { - Some(ref mut runner) => { + RunnerEnum::Running(ref mut runner) => { runner.handle_single_event(event, control); } // If an event is being handled without a runner somehow, add it to the event queue so // it will eventually be processed - _ => self.0.events.borrow_mut().push_back(event), + RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event), + // If the Runner has been destroyed, there is nothing to do. + RunnerEnum::Destroyed => return, } let is_closed = *control == root::ControlFlow::Exit; // Don't take events out of the queue if the loop is closed or the runner doesn't exist // If the runner doesn't exist and this method recurses, it will recurse infinitely - if !is_closed && self.0.runner.borrow().is_some() { + if !is_closed && self.0.runner.borrow().maybe_runner().is_some() { // Take an event out of the queue and handle it // Make sure not to let the borrow_mut live during the next handle_event let event = { self.0.events.borrow_mut().pop_front() }; @@ -382,26 +465,58 @@ impl Shared { }; match *self.0.runner.borrow_mut() { - Some(ref mut runner) => { + RunnerEnum::Running(ref mut runner) => { runner.state = new_state; } - None => (), + _ => (), + } + } + + fn handle_loop_destroyed(&self, control: &mut root::ControlFlow) { + self.handle_event(Event::LoopDestroyed, control); + let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut()); + *self.0.scale_change_detector.borrow_mut() = None; + *self.0.unload_event_handle.borrow_mut() = None; + // Dropping the `Runner` drops the event handler closure, which will in + // turn drop all `Window`s moved into the closure. + *self.0.runner.borrow_mut() = RunnerEnum::Destroyed; + for (_, canvas) in all_canvases { + // In case any remaining `Window`s are still not dropped, we will need + // to explicitly remove the event handlers associated with their canvases. + if let Some(canvas) = canvas.upgrade() { + let mut canvas = canvas.borrow_mut(); + canvas.remove_listeners(); + } } + // At this point, the `self.0` `Rc` should only be strongly referenced + // by the following: + // * `self`, i.e. the item which triggered this event loop wakeup, which + // is usually a `wasm-bindgen` `Closure`, which will be dropped after + // returning to the JS glue code. + // * The `EventLoopWindowTarget` leaked inside `EventLoop::run` due to the + // JS exception thrown at the end. + // * For each undropped `Window`: + // * The `register_redraw_request` closure. + // * The `destroy_fn` closure. } // Check if the event loop is currently closed fn is_closed(&self) -> bool { match *self.0.runner.borrow() { - Some(ref runner) => runner.state.is_exit(), - None => false, // If the event loop is None, it has not been intialised yet, so it cannot be closed + RunnerEnum::Running(ref runner) => runner.state.is_exit(), + // The event loop is not closed since it is not initialized. + RunnerEnum::Pending => false, + // The event loop is closed since it has been destroyed. + RunnerEnum::Destroyed => true, } } // Get the current control flow state fn current_control_flow(&self) -> root::ControlFlow { match *self.0.runner.borrow() { - Some(ref runner) => runner.state.control_flow(), - None => root::ControlFlow::Poll, + RunnerEnum::Running(ref runner) => runner.state.control_flow(), + RunnerEnum::Pending => root::ControlFlow::Poll, + RunnerEnum::Destroyed => root::ControlFlow::Exit, } } } diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index f26f984a65..7818ac0d37 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -4,8 +4,10 @@ use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, Win use crate::event_loop::ControlFlow; use crate::monitor::MonitorHandle as RootMH; use crate::window::{Theme, WindowId}; +use std::cell::RefCell; use std::clone::Clone; use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; +use std::rc::Rc; pub struct WindowTarget { pub(crate) runner: runner::Shared, @@ -42,11 +44,12 @@ impl WindowTarget { window::Id(self.runner.generate_id()) } - pub fn register(&self, canvas: &mut backend::Canvas, id: window::Id) { - let runner = self.runner.clone(); + pub fn register(&self, canvas: &Rc>, id: window::Id) { + self.runner.add_canvas(WindowId(id), canvas); + let mut canvas = canvas.borrow_mut(); canvas.set_attribute("data-raw-handle", &id.0.to_string()); - runner.add_canvas(WindowId(id), canvas.raw().clone()); + let runner = self.runner.clone(); canvas.on_blur(move || { runner.send_event(Event::WindowEvent { window_id: WindowId(id), diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index 1fdc7182da..b9e0c9d015 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -37,12 +37,6 @@ pub struct Canvas { wants_fullscreen: Rc>, } -impl Drop for Canvas { - fn drop(&mut self) { - self.raw.remove(); - } -} - impl Canvas { pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result { let canvas = match attr.canvas { @@ -306,4 +300,8 @@ impl Canvas { pub fn is_fullscreen(&self) -> bool { super::is_fullscreen(&self.raw) } + + pub fn remove_listeners(&mut self) { + // TODO: Stub, unimplemented (see web_sys for reference). + } } diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 6d517247dd..23009b28f4 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -28,7 +28,9 @@ pub fn exit_fullscreen() { document().exit_fullscreen(); } -pub fn on_unload(mut handler: impl FnMut() + 'static) { +pub type UnloadEventHandle = (); + +pub fn on_unload(mut handler: impl FnMut() + 'static) -> UnloadEventHandle { window().add_event_listener(move |_: BeforeUnloadEvent| handler()); } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 81e152c1e9..46d9531157 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,4 +1,6 @@ use super::event; +use super::event_handle::EventListenerHandle; +use super::media_query_handle::MediaQueryListHandle; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; @@ -18,14 +20,14 @@ mod pointer_handler; pub struct Canvas { common: Common, - on_focus: Option>, - on_blur: Option>, - on_keyboard_release: Option>, - on_keyboard_press: Option>, - on_received_character: Option>, - on_mouse_wheel: Option>, - on_fullscreen_change: Option>, - on_dark_mode: Option>, + on_focus: Option>, + on_blur: Option>, + on_keyboard_release: Option>, + on_keyboard_press: Option>, + on_received_character: Option>, + on_mouse_wheel: Option>, + on_fullscreen_change: Option>, + on_dark_mode: Option, mouse_state: MouseState, } @@ -35,12 +37,6 @@ struct Common { wants_fullscreen: Rc>, } -impl Drop for Common { - fn drop(&mut self) { - self.raw.remove(); - } -} - impl Canvas { pub fn create(attr: PlatformSpecificWindowBuilderAttributes) -> Result { let canvas = match attr.canvas { @@ -264,22 +260,12 @@ impl Canvas { where F: 'static + FnMut(bool), { - let window = web_sys::window().expect("Failed to obtain window"); - - self.on_dark_mode = window - .match_media("(prefers-color-scheme: dark)") - .ok() - .flatten() - .and_then(|media| { - let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| { - handler(event.matches()) - }) as Box); - - media - .add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref())) - .map(|_| closure) - .ok() - }); + let closure = + Closure::wrap( + Box::new(move |event: MediaQueryListEvent| handler(event.matches())) + as Box, + ); + self.on_dark_mode = MediaQueryListHandle::new("(prefers-color-scheme: dark)", closure); } pub fn request_fullscreen(&self) { @@ -289,10 +275,29 @@ impl Canvas { pub fn is_fullscreen(&self) -> bool { self.common.is_fullscreen() } + + pub fn remove_listeners(&mut self) { + self.on_focus = None; + self.on_blur = None; + self.on_keyboard_release = None; + self.on_keyboard_press = None; + self.on_received_character = None; + self.on_mouse_wheel = None; + self.on_fullscreen_change = None; + self.on_dark_mode = None; + match &mut self.mouse_state { + MouseState::HasPointerEvent(h) => h.remove_listeners(), + MouseState::NoPointerEvent(h) => h.remove_listeners(), + } + } } impl Common { - fn add_event(&self, event_name: &str, mut handler: F) -> Closure + fn add_event( + &self, + event_name: &'static str, + mut handler: F, + ) -> EventListenerHandle where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, F: 'static + FnMut(E), @@ -307,17 +312,19 @@ impl Common { handler(event); }) as Box); - self.raw - .add_event_listener_with_callback(event_name, &closure.as_ref().unchecked_ref()) - .expect("Failed to add event listener with callback"); + let listener = EventListenerHandle::new(&self.raw, event_name, closure); - closure + listener } // The difference between add_event and add_user_event is that the latter has a special meaning // for browser security. A user event is a deliberate action by the user (like a mouse or key // press) and is the only time things like a fullscreen request may be successfully completed.) - fn add_user_event(&self, event_name: &str, mut handler: F) -> Closure + fn add_user_event( + &self, + event_name: &'static str, + mut handler: F, + ) -> EventListenerHandle where E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, F: 'static + FnMut(E), @@ -343,9 +350,9 @@ impl Common { // handling to control event propagation. fn add_window_mouse_event( &self, - event_name: &str, + event_name: &'static str, mut handler: F, - ) -> Closure + ) -> EventListenerHandle where F: 'static + FnMut(MouseEvent), { @@ -364,15 +371,14 @@ impl Common { } }) as Box); - window - .add_event_listener_with_callback_and_add_event_listener_options( - event_name, - &closure.as_ref().unchecked_ref(), - AddEventListenerOptions::new().capture(true), - ) - .expect("Failed to add event listener with callback and options"); + let listener = EventListenerHandle::with_options( + &window, + event_name, + closure, + AddEventListenerOptions::new().capture(true), + ); - closure + listener } pub fn request_fullscreen(&self) { diff --git a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs index 37fdd018e3..fcfb88b991 100644 --- a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs +++ b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs @@ -1,19 +1,19 @@ use super::event; +use super::EventListenerHandle; use crate::dpi::PhysicalPosition; use crate::event::{ModifiersState, MouseButton}; use std::cell::RefCell; use std::rc::Rc; -use wasm_bindgen::closure::Closure; use web_sys::{EventTarget, MouseEvent}; pub(super) struct MouseHandler { - on_mouse_leave: Option>, - on_mouse_enter: Option>, - on_mouse_move: Option>, - on_mouse_press: Option>, - on_mouse_release: Option>, + on_mouse_leave: Option>, + on_mouse_enter: Option>, + on_mouse_move: Option>, + on_mouse_press: Option>, + on_mouse_release: Option>, on_mouse_leave_handler: Rc>>>, mouse_capture_state: Rc>, } @@ -200,4 +200,13 @@ impl MouseHandler { }, )); } + + pub fn remove_listeners(&mut self) { + self.on_mouse_leave = None; + self.on_mouse_enter = None; + self.on_mouse_move = None; + self.on_mouse_press = None; + self.on_mouse_release = None; + *self.on_mouse_leave_handler.borrow_mut() = None; + } } diff --git a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs index f9787dd5d5..4764e03c7d 100644 --- a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs +++ b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs @@ -1,16 +1,16 @@ use super::event; +use super::EventListenerHandle; use crate::dpi::PhysicalPosition; use crate::event::{ModifiersState, MouseButton}; -use wasm_bindgen::closure::Closure; use web_sys::PointerEvent; pub(super) struct PointerHandler { - on_cursor_leave: Option>, - on_cursor_enter: Option>, - on_cursor_move: Option>, - on_pointer_press: Option>, - on_pointer_release: Option>, + on_cursor_leave: Option>, + on_cursor_enter: Option>, + on_cursor_move: Option>, + on_pointer_press: Option>, + on_pointer_release: Option>, } impl PointerHandler { @@ -100,4 +100,12 @@ impl PointerHandler { }, )); } + + pub fn remove_listeners(&mut self) { + self.on_cursor_leave = None; + self.on_cursor_enter = None; + self.on_cursor_move = None; + self.on_pointer_press = None; + self.on_pointer_release = None; + } } diff --git a/src/platform_impl/web/web_sys/event_handle.rs b/src/platform_impl/web/web_sys/event_handle.rs new file mode 100644 index 0000000000..08130d7598 --- /dev/null +++ b/src/platform_impl/web/web_sys/event_handle.rs @@ -0,0 +1,65 @@ +use wasm_bindgen::{prelude::Closure, JsCast}; +use web_sys::{AddEventListenerOptions, EventTarget}; + +pub(super) struct EventListenerHandle { + target: EventTarget, + event_type: &'static str, + listener: Closure, +} + +impl EventListenerHandle { + pub fn new(target: &U, event_type: &'static str, listener: Closure) -> Self + where + U: Clone + Into, + { + let target = target.clone().into(); + target + .add_event_listener_with_callback(event_type, listener.as_ref().unchecked_ref()) + .expect("Failed to add event listener"); + EventListenerHandle { + target, + event_type, + listener, + } + } + + pub fn with_options( + target: &U, + event_type: &'static str, + listener: Closure, + options: &AddEventListenerOptions, + ) -> Self + where + U: Clone + Into, + { + let target = target.clone().into(); + target + .add_event_listener_with_callback_and_add_event_listener_options( + event_type, + listener.as_ref().unchecked_ref(), + options, + ) + .expect("Failed to add event listener"); + EventListenerHandle { + target, + event_type, + listener, + } + } +} + +impl Drop for EventListenerHandle { + fn drop(&mut self) { + self.target + .remove_event_listener_with_callback( + self.event_type, + self.listener.as_ref().unchecked_ref(), + ) + .unwrap_or_else(|e| { + web_sys::console::error_2( + &format!("Error removing event listener {}", self.event_type).into(), + &e, + ) + }); + } +} diff --git a/src/platform_impl/web/web_sys/media_query_handle.rs b/src/platform_impl/web/web_sys/media_query_handle.rs new file mode 100644 index 0000000000..c2ab40da77 --- /dev/null +++ b/src/platform_impl/web/web_sys/media_query_handle.rs @@ -0,0 +1,56 @@ +use wasm_bindgen::{prelude::Closure, JsCast}; +use web_sys::{MediaQueryList, MediaQueryListEvent}; + +pub(super) struct MediaQueryListHandle { + mql: MediaQueryList, + listener: Option>, +} + +impl MediaQueryListHandle { + pub fn new( + media_query: &str, + listener: Closure, + ) -> Option { + let window = web_sys::window().expect("Failed to obtain window"); + let mql = window + .match_media(media_query) + .ok() + .flatten() + .and_then(|mql| { + mql.add_listener_with_opt_callback(Some(&listener.as_ref().unchecked_ref())) + .map(|_| mql) + .ok() + }); + mql.map(|mql| Self { + mql, + listener: Some(listener), + }) + } + + pub fn mql(&self) -> &MediaQueryList { + &self.mql + } + + /// Removes the listener and returns the original listener closure, which + /// can be reused. + pub fn remove(mut self) -> Closure { + let listener = self.listener.take().unwrap_or_else(|| unreachable!()); + remove_listener(&self.mql, &listener); + listener + } +} + +impl Drop for MediaQueryListHandle { + fn drop(&mut self) { + if let Some(listener) = self.listener.take() { + remove_listener(&self.mql, &listener); + } + } +} + +fn remove_listener(mql: &MediaQueryList, listener: &Closure) { + mql.remove_listener_with_opt_callback(Some(listener.as_ref().unchecked_ref())) + .unwrap_or_else(|e| { + web_sys::console::error_2(&"Error removing media query listener".into(), &e) + }); +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 338869acd3..8057f7756a 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -1,5 +1,7 @@ mod canvas; mod event; +mod event_handle; +mod media_query_handle; mod scaling; mod timeout; @@ -10,7 +12,7 @@ pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; use crate::platform::web::WindowExtWebSys; use crate::window::Window; -use wasm_bindgen::{closure::Closure, JsCast}; +use wasm_bindgen::closure::Closure; use web_sys::{window, BeforeUnloadEvent, Element, HtmlCanvasElement}; pub fn throw(msg: &str) { @@ -24,16 +26,21 @@ pub fn exit_fullscreen() { document.exit_fullscreen(); } -pub fn on_unload(mut handler: impl FnMut() + 'static) { +pub struct UnloadEventHandle { + _listener: event_handle::EventListenerHandle, +} + +pub fn on_unload(mut handler: impl FnMut() + 'static) -> UnloadEventHandle { let window = web_sys::window().expect("Failed to obtain window"); let closure = Closure::wrap( Box::new(move |_: BeforeUnloadEvent| handler()) as Box ); - window - .add_event_listener_with_callback("beforeunload", &closure.as_ref().unchecked_ref()) - .expect("Failed to add close listener"); + let listener = event_handle::EventListenerHandle::new(&window, "beforeunload", closure); + UnloadEventHandle { + _listener: listener, + } } impl WindowExtWebSys for Window { diff --git a/src/platform_impl/web/web_sys/scaling.rs b/src/platform_impl/web/web_sys/scaling.rs index 5018257fa5..2129a165d7 100644 --- a/src/platform_impl/web/web_sys/scaling.rs +++ b/src/platform_impl/web/web_sys/scaling.rs @@ -1,8 +1,9 @@ use super::super::ScaleChangeArgs; +use super::media_query_handle::MediaQueryListHandle; use std::{cell::RefCell, rc::Rc}; -use wasm_bindgen::{prelude::Closure, JsCast}; -use web_sys::{MediaQueryList, MediaQueryListEvent}; +use wasm_bindgen::prelude::Closure; +use web_sys::MediaQueryListEvent; pub struct ScaleChangeDetector(Rc>); @@ -19,8 +20,7 @@ impl ScaleChangeDetector { /// changes of the `devicePixelRatio`. struct ScaleChangeDetectorInternal { callback: Box, - closure: Option>, - mql: Option, + mql: Option, last_scale: f64, } @@ -32,27 +32,28 @@ impl ScaleChangeDetectorInternal { let current_scale = super::scale_factor(); let new_self = Rc::new(RefCell::new(Self { callback: Box::new(handler), - closure: None, mql: None, last_scale: current_scale, })); - let cloned_self = new_self.clone(); + let weak_self = Rc::downgrade(&new_self); let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| { - cloned_self.borrow_mut().handler(event) + if let Some(rc_self) = weak_self.upgrade() { + rc_self.borrow_mut().handler(event); + } }) as Box); - let mql = Self::create_mql(&closure); + let mql = Self::create_mql(closure); { let mut borrowed_self = new_self.borrow_mut(); - borrowed_self.closure = Some(closure); borrowed_self.mql = mql; } new_self } - fn create_mql(closure: &Closure) -> Option { - let window = web_sys::window().expect("Failed to obtain window"); + fn create_mql( + closure: Closure, + ) -> Option { let current_scale = super::scale_factor(); // This media query initially matches the current `devicePixelRatio`. // We add 0.0001 to the lower and upper bounds such that it won't fail @@ -62,30 +63,20 @@ impl ScaleChangeDetectorInternal { current_scale - 0.0001, current_scale + 0.0001, ); - window - .match_media(&media_query) - .ok() - .flatten() - .and_then(|mql| { - assert_eq!(mql.matches(), true); - mql.add_listener_with_opt_callback(Some(&closure.as_ref().unchecked_ref())) - .map(|_| mql) - .ok() - }) + let mql = MediaQueryListHandle::new(&media_query, closure); + if let Some(mql) = &mql { + assert_eq!(mql.mql().matches(), true); + } + mql } fn handler(&mut self, event: MediaQueryListEvent) { assert_eq!(event.matches(), false); - let closure = self - .closure - .as_ref() - .expect("DevicePixelRatioChangeDetector::closure should not be None"); let mql = self .mql .take() .expect("DevicePixelRatioChangeDetector::mql should not be None"); - mql.remove_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref())) - .expect("Failed to remove listener from MediaQueryList"); + let closure = mql.remove(); let new_scale = super::scale_factor(); (self.callback)(ScaleChangeArgs { old_scale: self.last_scale, @@ -96,15 +87,3 @@ impl ScaleChangeDetectorInternal { self.last_scale = new_scale; } } - -impl Drop for ScaleChangeDetectorInternal { - fn drop(&mut self) { - match (self.closure.as_ref(), self.mql.as_ref()) { - (Some(closure), Some(mql)) => { - let _ = - mql.remove_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref())); - } - _ => {} - } - } -} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 165661fafa..586da040c1 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -8,15 +8,17 @@ use raw_window_handle::web::WebHandle; use super::{backend, monitor, EventLoopWindowTarget}; -use std::cell::RefCell; +use std::cell::{Ref, RefCell}; use std::collections::vec_deque::IntoIter as VecDequeIter; use std::collections::VecDeque; +use std::rc::Rc; pub struct Window { - canvas: backend::Canvas, + canvas: Rc>, previous_pointer: RefCell<&'static str>, id: Id, register_redraw_request: Box, + destroy_fn: Option>, } impl Window { @@ -29,17 +31,22 @@ impl Window { let id = target.generate_id(); - let mut canvas = backend::Canvas::create(platform_attr)?; + let canvas = backend::Canvas::create(platform_attr)?; + let mut canvas = Rc::new(RefCell::new(canvas)); let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); target.register(&mut canvas, id); + let runner = target.runner.clone(); + let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id))); + let window = Window { canvas, previous_pointer: RefCell::new("auto"), id, register_redraw_request, + destroy_fn: Some(destroy_fn), }; window.set_inner_size(attr.inner_size.unwrap_or(Size::Logical(LogicalSize { @@ -54,12 +61,12 @@ impl Window { Ok(window) } - pub fn canvas(&self) -> &backend::Canvas { - &self.canvas + pub fn canvas<'a>(&'a self) -> Ref<'a, backend::Canvas> { + self.canvas.borrow() } pub fn set_title(&self, title: &str) { - self.canvas.set_attribute("alt", title); + self.canvas.borrow().set_attribute("alt", title); } pub fn set_visible(&self, _visible: bool) { @@ -71,7 +78,11 @@ impl Window { } pub fn outer_position(&self) -> Result, NotSupportedError> { - Ok(self.canvas.position().to_physical(self.scale_factor())) + Ok(self + .canvas + .borrow() + .position() + .to_physical(self.scale_factor())) } pub fn inner_position(&self) -> Result, NotSupportedError> { @@ -82,14 +93,15 @@ impl Window { pub fn set_outer_position(&self, position: Position) { let position = position.to_logical::(self.scale_factor()); - self.canvas.set_attribute("position", "fixed"); - self.canvas.set_attribute("left", &position.x.to_string()); - self.canvas.set_attribute("top", &position.y.to_string()); + let canvas = self.canvas.borrow(); + canvas.set_attribute("position", "fixed"); + canvas.set_attribute("left", &position.x.to_string()); + canvas.set_attribute("top", &position.y.to_string()); } #[inline] pub fn inner_size(&self) -> PhysicalSize { - self.canvas.size() + self.canvas.borrow().size() } #[inline] @@ -100,7 +112,7 @@ impl Window { #[inline] pub fn set_inner_size(&self, size: Size) { - backend::set_canvas_size(self.canvas.raw(), size); + backend::set_canvas_size(self.canvas.borrow().raw(), size); } #[inline] @@ -165,7 +177,7 @@ impl Window { CursorIcon::RowResize => "row-resize", }; *self.previous_pointer.borrow_mut() = text; - backend::set_canvas_style_property(self.canvas.raw(), "cursor", text); + backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", text); } #[inline] @@ -181,9 +193,10 @@ impl Window { #[inline] pub fn set_cursor_visible(&self, visible: bool) { if !visible { - self.canvas.set_attribute("cursor", "none"); + self.canvas.borrow().set_attribute("cursor", "none"); } else { self.canvas + .borrow() .set_attribute("cursor", *self.previous_pointer.borrow()); } } @@ -200,7 +213,7 @@ impl Window { #[inline] pub fn fullscreen(&self) -> Option { - if self.canvas.is_fullscreen() { + if self.canvas.borrow().is_fullscreen() { Some(Fullscreen::Borderless(self.current_monitor_inner())) } else { None @@ -210,8 +223,8 @@ impl Window { #[inline] pub fn set_fullscreen(&self, monitor: Option) { if monitor.is_some() { - self.canvas.request_fullscreen(); - } else if self.canvas.is_fullscreen() { + self.canvas.borrow().request_fullscreen(); + } else if self.canvas.borrow().is_fullscreen() { backend::exit_fullscreen(); } } @@ -277,6 +290,14 @@ impl Window { } } +impl Drop for Window { + fn drop(&mut self) { + if let Some(destroy_fn) = self.destroy_fn.take() { + destroy_fn(); + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id(pub(crate) u32); From 644dc13e0012f7ca69892684d70231c1ebe2fd3a Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Tue, 22 Sep 2020 06:19:00 +0800 Subject: [PATCH 184/239] web: Emit WindowEvent::Resized on Window::set_inner_size (#1717) * web: Allow event to be queued from inside the EventLoop handler The Runner is behind a RefCell, which is mutably borrowed when the event handler is being called. To queue events, `send_events` needs to check `is_closed()` and the `is_busy` flag, but it cannot be done since the RefCell is already locked. This commit changes the conditions to work without needing a successful borrow. * web: Emit WindowEvent::Resized on Window::set_inner_size * Update changelog --- CHANGELOG.md | 1 + src/platform_impl/web/event_loop/runner.rs | 38 ++++++++++------------ src/platform_impl/web/window.rs | 27 ++++++++++++--- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faed9c335e..cc06582867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed. - On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed. - **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped. +- On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index af46e02472..4c51a8a36a 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -9,6 +9,7 @@ use std::{ clone::Clone, collections::{HashSet, VecDeque}, iter, + ops::Deref, rc::{Rc, Weak}, }; @@ -53,7 +54,6 @@ impl RunnerEnum { struct Runner { state: State, - is_busy: bool, event_handler: Box, &mut root::ControlFlow)>, } @@ -61,7 +61,6 @@ impl Runner { pub fn new(event_handler: Box, &mut root::ControlFlow)>) -> Self { Runner { state: State::Init, - is_busy: false, event_handler, } } @@ -87,18 +86,12 @@ impl Runner { fn handle_single_event(&mut self, event: Event<'_, T>, control: &mut root::ControlFlow) { let is_closed = *control == root::ControlFlow::Exit; - // An event is being processed, so the runner should be marked busy - self.is_busy = true; - (self.event_handler)(event, control); // Maintain closed state, even if the callback changes it if is_closed { *control = root::ControlFlow::Exit; } - - // An event is no longer being processed - self.is_busy = false; } } @@ -205,23 +198,25 @@ impl Shared { } // If we can run the event processing right now, or need to queue this and wait for later let mut process_immediately = true; - match &*self.0.runner.borrow() { - RunnerEnum::Running(ref runner) => { + match self.0.runner.try_borrow().as_ref().map(Deref::deref) { + Ok(RunnerEnum::Running(ref runner)) => { // If we're currently polling, queue this and wait for the poll() method to be called if let State::Poll { .. } = runner.state { process_immediately = false; } - // If the runner is busy, queue this and wait for it to process it later - if runner.is_busy { - process_immediately = false; - } } - RunnerEnum::Pending => { + Ok(RunnerEnum::Pending) => { // The runner still hasn't been attached: queue this event and wait for it to be process_immediately = false; } + // Some other code is mutating the runner, which most likely means + // the event loop is running and busy. So we queue this event for + // it to be processed later. + Err(_) => { + process_immediately = false; + } // This is unreachable since `self.is_closed() == true`. - RunnerEnum::Destroyed => unreachable!(), + Ok(RunnerEnum::Destroyed) => unreachable!(), } if !process_immediately { // Queue these events to look at later @@ -502,12 +497,15 @@ impl Shared { // Check if the event loop is currently closed fn is_closed(&self) -> bool { - match *self.0.runner.borrow() { - RunnerEnum::Running(ref runner) => runner.state.is_exit(), + match self.0.runner.try_borrow().as_ref().map(Deref::deref) { + Ok(RunnerEnum::Running(runner)) => runner.state.is_exit(), // The event loop is not closed since it is not initialized. - RunnerEnum::Pending => false, + Ok(RunnerEnum::Pending) => false, // The event loop is closed since it has been destroyed. - RunnerEnum::Destroyed => true, + Ok(RunnerEnum::Destroyed) => true, + // Some other code is mutating the runner, which most likely means + // the event loop is running and busy. + Err(_) => false, } } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 586da040c1..b78048f43d 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -1,5 +1,6 @@ use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; +use crate::event; use crate::icon::Icon; use crate::monitor::MonitorHandle as RootMH; use crate::window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWI}; @@ -18,6 +19,7 @@ pub struct Window { previous_pointer: RefCell<&'static str>, id: Id, register_redraw_request: Box, + resize_notify_fn: Box)>, destroy_fn: Option>, } @@ -38,6 +40,14 @@ impl Window { target.register(&mut canvas, id); + let runner = target.runner.clone(); + let resize_notify_fn = Box::new(move |new_size| { + runner.send_event(event::Event::WindowEvent { + window_id: RootWI(id), + event: event::WindowEvent::Resized(new_size), + }); + }); + let runner = target.runner.clone(); let destroy_fn = Box::new(move || runner.notify_destroy_window(RootWI(id))); @@ -46,13 +56,17 @@ impl Window { previous_pointer: RefCell::new("auto"), id, register_redraw_request, + resize_notify_fn, destroy_fn: Some(destroy_fn), }; - window.set_inner_size(attr.inner_size.unwrap_or(Size::Logical(LogicalSize { - width: 1024.0, - height: 768.0, - }))); + backend::set_canvas_size( + window.canvas.borrow().raw(), + attr.inner_size.unwrap_or(Size::Logical(LogicalSize { + width: 1024.0, + height: 768.0, + })), + ); window.set_title(&attr.title); window.set_maximized(attr.maximized); window.set_visible(attr.visible); @@ -112,7 +126,12 @@ impl Window { #[inline] pub fn set_inner_size(&self, size: Size) { + let old_size = self.inner_size(); backend::set_canvas_size(self.canvas.borrow().raw(), size); + let new_size = self.inner_size(); + if old_size != new_size { + (self.resize_notify_fn)(new_size); + } } #[inline] From 71e3d254226b47d8cbdb1d9432f11e4cdc659a46 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 22 Sep 2020 04:54:47 +0300 Subject: [PATCH 185/239] Rework 'Fullscreen::Borderless' enum variant This changes 'Fullscreen::Borderless' enum variant from 'Fullscreen::Borderless(MonitorHandle)' to 'Fullscreen::Borderless(Option)'. Providing 'None' to it will result in picking the current monitor. --- CHANGELOG.md | 1 + examples/fullscreen.rs | 2 +- examples/multithreaded.rs | 4 +- examples/window_debug.rs | 10 ++++- src/platform_impl/ios/view.rs | 10 ++++- src/platform_impl/ios/window.rs | 12 ++++-- src/platform_impl/linux/wayland/window.rs | 45 +++++++++++++--------- src/platform_impl/linux/x11/window.rs | 9 +++-- src/platform_impl/macos/window.rs | 12 ++++-- src/platform_impl/macos/window_delegate.rs | 2 +- src/platform_impl/web/window.rs | 2 +- src/platform_impl/windows/event_loop.rs | 11 ++++-- src/platform_impl/windows/window.rs | 9 +++-- src/window.rs | 6 ++- 14 files changed, 89 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc06582867..413620671a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ - On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed. - **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped. - On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called. +- **Breaking:** `Fullscreen` enum now uses `Borderless(Option)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor. # 0.22.2 (2020-05-16) diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 3c5ed185b0..4ed157f042 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -19,7 +19,7 @@ fn main() { let fullscreen = Some(match num { 1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))), - 2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)), + 2 => Fullscreen::Borderless(Some(prompt_for_monitor(&event_loop))), _ => panic!("Please enter a valid number"), }); diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 5134fbfb2f..68cdb60b78 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -83,9 +83,7 @@ fn main() { ); } F => window.set_fullscreen(match (state, modifiers.alt()) { - (true, false) => { - Some(Fullscreen::Borderless(window.current_monitor().unwrap())) - } + (true, false) => Some(Fullscreen::Borderless(None)), (true, true) => Some(Fullscreen::Exclusive( video_modes.iter().nth(video_mode_id).unwrap().clone(), )), diff --git a/examples/window_debug.rs b/examples/window_debug.rs index 2771b37072..dd4007f324 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -21,6 +21,7 @@ fn main() { eprintln!("debugging keys:"); eprintln!(" (E) Enter exclusive fullscreen"); eprintln!(" (F) Toggle borderless fullscreen"); + eprintln!(" (P) Toggle borderless fullscreen on system's preffered monitor"); eprintln!(" (M) Toggle minimized"); eprintln!(" (Q) Quit event loop"); eprintln!(" (V) Toggle visibility"); @@ -85,10 +86,17 @@ fn main() { if window.fullscreen().is_some() { window.set_fullscreen(None); } else { - let monitor = window.current_monitor().unwrap(); + let monitor = window.current_monitor(); window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } } + VirtualKeyCode::P => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + window.set_fullscreen(Some(Fullscreen::Borderless(None))); + } + } VirtualKeyCode::M => { minimized = !minimized; window.set_minimized(minimized); diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 5481b8eff5..df5a803a1c 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -519,7 +519,15 @@ pub unsafe fn create_window( msg_send![window, setScreen:video_mode.monitor().ui_screen()] } Some(Fullscreen::Borderless(ref monitor)) => { - msg_send![window, setScreen:monitor.ui_screen()] + let uiscreen: id = match &monitor { + Some(monitor) => monitor.ui_screen() as id, + None => { + let uiscreen: id = msg_send![window, screen]; + uiscreen + } + }; + + msg_send![window, setScreen: uiscreen] } None => (), } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index a7deb61b10..d83b494153 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -196,7 +196,9 @@ impl Inner { let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode]; uiscreen } - Some(Fullscreen::Borderless(monitor)) => monitor.ui_screen() as id, + Some(Fullscreen::Borderless(monitor)) => monitor + .unwrap_or_else(|| self.current_monitor_inner()) + .ui_screen() as id, None => { warn!("`Window::set_fullscreen(None)` ignored on iOS"); return; @@ -235,7 +237,7 @@ impl Inner { && screen_space_bounds.size.width == screen_bounds.size.width && screen_space_bounds.size.height == screen_bounds.size.height { - Some(Fullscreen::Borderless(monitor)) + Some(Fullscreen::Borderless(Some(monitor))) } else { None } @@ -353,8 +355,10 @@ impl Window { Some(Fullscreen::Exclusive(ref video_mode)) => { video_mode.video_mode.monitor.ui_screen() as id } - Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id, - None => monitor::main_uiscreen().ui_screen(), + Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.inner.ui_screen(), + Some(Fullscreen::Borderless(None)) | None => { + monitor::main_uiscreen().ui_screen() as id + } }; let screen_bounds: CGRect = msg_send![screen, bounds]; diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index c61ff19e0c..06f668cc45 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -154,11 +154,16 @@ impl Window { Some(Fullscreen::Exclusive(_)) => { panic!("Wayland doesn't support exclusive fullscreen") } - Some(Fullscreen::Borderless(RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(ref monitor_id), - })) => frame.set_fullscreen(Some(&monitor_id.proxy)), - #[cfg(feature = "x11")] - Some(Fullscreen::Borderless(_)) => unreachable!(), + Some(Fullscreen::Borderless(monitor)) => { + let monitor = + monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { + PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), + #[cfg(feature = "x11")] + PlatformMonitorHandle::X(_) => None, + }); + + frame.set_fullscreen(monitor.as_ref()) + } None => { if attributes.maximized { frame.set_maximized(); @@ -333,11 +338,13 @@ impl Window { pub fn fullscreen(&self) -> Option { if *(self.fullscreen.lock().unwrap()) { - // If the monitor cannot be determined, we cannot report any fullscreen mode. - let current_monitor = self.current_monitor()?; - Some(Fullscreen::Borderless(RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(current_monitor), - })) + let current_monitor = self + .current_monitor() + .map(|current_monitor| RootMonitorHandle { + inner: PlatformMonitorHandle::Wayland(current_monitor), + }); + + Some(Fullscreen::Borderless(current_monitor)) } else { None } @@ -348,16 +355,16 @@ impl Window { Some(Fullscreen::Exclusive(_)) => { panic!("Wayland doesn't support exclusive fullscreen") } - Some(Fullscreen::Borderless(RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(ref monitor_id), - })) => { - self.frame - .lock() - .unwrap() - .set_fullscreen(Some(&monitor_id.proxy)); + Some(Fullscreen::Borderless(monitor)) => { + let monitor = + monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { + PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), + #[cfg(feature = "x11")] + PlatformMonitorHandle::X(_) => None, + }); + + self.frame.lock().unwrap().set_fullscreen(monitor.as_ref()); } - #[cfg(feature = "x11")] - Some(Fullscreen::Borderless(_)) => unreachable!(), None => self.frame.lock().unwrap().unset_fullscreen(), } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index ae63f436a4..058b3aa703 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -649,10 +649,11 @@ impl UnownedWindow { let (video_mode, monitor) = match fullscreen { Fullscreen::Exclusive(RootVideoMode { video_mode: PlatformVideoMode::X(ref video_mode), - }) => (Some(video_mode), video_mode.monitor.as_ref().unwrap()), - Fullscreen::Borderless(RootMonitorHandle { - inner: PlatformMonitorHandle::X(ref monitor), - }) => (None, monitor), + }) => (Some(video_mode), video_mode.monitor.clone().unwrap()), + Fullscreen::Borderless(Some(RootMonitorHandle { + inner: PlatformMonitorHandle::X(monitor), + })) => (None, monitor), + Fullscreen::Borderless(None) => (None, self.current_monitor()), #[cfg(feature = "wayland")] _ => unreachable!(), }; diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index d2b014038a..191ce60a9f 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -142,13 +142,14 @@ fn create_window( unsafe { let pool = NSAutoreleasePool::new(nil); let screen = match attrs.fullscreen { - Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor })) + Some(Fullscreen::Borderless(Some(RootMonitorHandle { inner: ref monitor }))) | Some(Fullscreen::Exclusive(RootVideoMode { video_mode: VideoMode { ref monitor, .. }, })) => { let monitor_screen = monitor.ns_screen(); Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) } + Some(Fullscreen::Borderless(None)) => Some(appkit::NSScreen::mainScreen(nil)), None => None, }; let frame = match screen { @@ -755,10 +756,15 @@ impl UnownedWindow { // does not take a screen parameter, but uses the current screen) if let Some(ref fullscreen) = fullscreen { let new_screen = match fullscreen { - Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }) => monitor, + Fullscreen::Borderless(borderless) => { + let RootMonitorHandle { inner: monitor } = borderless + .clone() + .unwrap_or_else(|| self.current_monitor_inner()); + monitor + } Fullscreen::Exclusive(RootVideoMode { video_mode: VideoMode { ref monitor, .. }, - }) => monitor, + }) => monitor.clone(), } .ns_screen() .unwrap(); diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 9d106e1518..d906756181 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -450,7 +450,7 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { // Otherwise, we must've reached fullscreen by the user clicking // on the green fullscreen button. Update state! None => { - let current_monitor = window.current_monitor_inner(); + let current_monitor = Some(window.current_monitor_inner()); shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor)) } } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index b78048f43d..4719044ef4 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -233,7 +233,7 @@ impl Window { #[inline] pub fn fullscreen(&self) -> Option { if self.canvas.borrow().is_fullscreen() { - Some(Fullscreen::Borderless(self.current_monitor_inner())) + Some(Fullscreen::Borderless(Some(self.current_monitor_inner()))) } else { None } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 9227bceb5f..a6d15fb87f 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -853,8 +853,11 @@ unsafe extern "system" fn public_window_callback( winuser::MonitorFromRect(&new_rect, winuser::MONITOR_DEFAULTTONULL); match fullscreen { Fullscreen::Borderless(ref mut fullscreen_monitor) => { - if new_monitor != fullscreen_monitor.inner.hmonitor() - && new_monitor != ptr::null_mut() + if new_monitor != ptr::null_mut() + && fullscreen_monitor + .as_ref() + .map(|monitor| new_monitor != monitor.inner.hmonitor()) + .unwrap_or(true) { if let Ok(new_monitor_info) = monitor::get_monitor_info(new_monitor) { let new_monitor_rect = new_monitor_info.rcMonitor; @@ -863,9 +866,9 @@ unsafe extern "system" fn public_window_callback( window_pos.cx = new_monitor_rect.right - new_monitor_rect.left; window_pos.cy = new_monitor_rect.bottom - new_monitor_rect.top; } - *fullscreen_monitor = crate::monitor::MonitorHandle { + *fullscreen_monitor = Some(crate::monitor::MonitorHandle { inner: MonitorHandle::new(new_monitor), - }; + }); } } Fullscreen::Exclusive(ref video_mode) => { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 34406310df..6253c94373 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -496,9 +496,12 @@ impl Window { // Update window bounds match &fullscreen { Some(fullscreen) => { - let monitor = match fullscreen { - Fullscreen::Exclusive(ref video_mode) => video_mode.monitor(), - Fullscreen::Borderless(ref monitor) => monitor.clone(), + let monitor = match &fullscreen { + Fullscreen::Exclusive(video_mode) => video_mode.monitor(), + Fullscreen::Borderless(Some(monitor)) => monitor.clone(), + Fullscreen::Borderless(None) => RootMonitorHandle { + inner: monitor::current_monitor(window.0), + }, }; let position: (i32, i32) = monitor.position().into(); diff --git a/src/window.rs b/src/window.rs index 1977d32cb4..d6cd8e6c4e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -628,6 +628,7 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. /// - **Android:** Will always return `None`. + /// - **Wayland:** Can return `Borderless(None)` when there are no monitors. #[inline] pub fn fullscreen(&self) -> Option { self.window.fullscreen() @@ -850,10 +851,13 @@ impl Default for CursorIcon { } } +/// Fullscreen modes. #[derive(Clone, Debug, PartialEq)] pub enum Fullscreen { Exclusive(VideoMode), - Borderless(MonitorHandle), + + /// Providing `None` to `Borderless` will fullscreen on the current monitor. + Borderless(Option), } #[derive(Clone, Debug, PartialEq)] From c9558c5f0e8f5ccb7b250a59c46c0f7a41c2416a Mon Sep 17 00:00:00 2001 From: Michael Hills Date: Wed, 23 Sep 2020 04:21:07 +1000 Subject: [PATCH 186/239] Fix view frame in portrait when starting iOS app in landscape (#1703) Co-authored-by: Francesca Lovebloom --- CHANGELOG.md | 1 + src/platform_impl/ios/ffi.rs | 6 +++--- src/platform_impl/ios/view.rs | 13 +++++++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 413620671a..febfc76317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - On macOS, updated core-* dependencies and cocoa. - Bump `parking_lot` to 0.11 - On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute. +- On iOS, fixed starting the app in landscape where the view still had portrait dimensions. - Deprecate the stdweb backend, to be removed in a future release - **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`. - Added `Asterisk` and `Plus` virtual key codes. diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 071b586795..726723f30b 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -29,14 +29,14 @@ pub struct NSOperatingSystemVersion { } #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct CGPoint { pub x: CGFloat, pub y: CGFloat, } #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct CGSize { pub width: CGFloat, pub height: CGFloat, @@ -52,7 +52,7 @@ impl CGSize { } #[repr(C)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct CGRect { pub origin: CGPoint, pub size: CGSize, diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index df5a803a1c..ccdec6bfbf 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -123,17 +123,26 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { let window: id = msg_send![object, window]; assert!(!window.is_null()); - let bounds: CGRect = msg_send![window, bounds]; + let window_bounds: CGRect = msg_send![window, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; let screen_frame: CGRect = - msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; + msg_send![object, convertRect:window_bounds toCoordinateSpace:screen_space]; let scale_factor: CGFloat = msg_send![screen, scale]; let size = crate::dpi::LogicalSize { width: screen_frame.size.width as f64, height: screen_frame.size.height as f64, } .to_physical(scale_factor.into()); + + // If the app is started in landscape, the view frame and window bounds can be mismatched. + // The view frame will be in portrait and the window bounds in landscape. So apply the + // window bounds to the view frame to make it consistent. + let view_frame: CGRect = msg_send![object, frame]; + if view_frame != window_bounds { + let () = msg_send![object, setFrame: window_bounds]; + } + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::Resized(size), From 3cd6a180481714f93d9d8e6a7f3dfc079be52530 Mon Sep 17 00:00:00 2001 From: Wang Kai Date: Wed, 23 Sep 2020 18:54:53 +0800 Subject: [PATCH 187/239] Fix WindowEvent::Moved ignoring DPI on macOS --- CHANGELOG.md | 1 + src/platform_impl/macos/window_delegate.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index febfc76317..da2e8e34c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ - **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped. - On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called. - **Breaking:** `Fullscreen` enum now uses `Borderless(Option)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor. +- On MacOS, fix `WindowEvent::Moved` ignoring the scale factor. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index d906756181..c226a66d7a 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -15,7 +15,7 @@ use objc::{ }; use crate::{ - dpi::LogicalSize, + dpi::{LogicalPosition, LogicalSize}, event::{Event, ModifiersState, WindowEvent}, platform_impl::platform::{ app_state::AppState, @@ -112,7 +112,9 @@ impl WindowDelegateState { let moved = self.previous_position != Some((x, y)); if moved { self.previous_position = Some((x, y)); - self.emit_event(WindowEvent::Moved((x, y).into())); + let scale_factor = self.get_scale_factor(); + let physical_pos = LogicalPosition::::from((x, y)).to_physical(scale_factor); + self.emit_event(WindowEvent::Moved(physical_pos)); } } From 9d6b9797c077977ae3bcea66cc7d3d301001be28 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Thu, 24 Sep 2020 22:30:26 +0800 Subject: [PATCH 188/239] Clarify ControlFlow::Poll doc for web (#1725) Make it clear that ControlFlow::Poll causing events to be sent on `requestAnimationFrame` is an implementation detail which should not be relied on. --- src/event_loop.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/event_loop.rs b/src/event_loop.rs index efc67efd0b..edc5670ac9 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -74,9 +74,11 @@ pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of /// whether or not new events are available to process. /// - /// For web, events are queued and usually sent when `requestAnimationFrame` fires but sometimes - /// the events in the queue may be sent before the next `requestAnimationFrame` callback, for - /// example when the scaling of the page has changed. + /// ## Platform-specific + /// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes + /// the events in the queue may be sent before the next `requestAnimationFrame` callback, for + /// example when the scaling of the page has changed. This should be treated as an implementation + /// detail which should not be relied on. Poll, /// When the current loop iteration finishes, suspend the thread until another event arrives. Wait, From be2e17d605d1780bba15cd7fa4a98ef4917cedf4 Mon Sep 17 00:00:00 2001 From: alvinhochun Date: Fri, 25 Sep 2020 01:52:11 +0800 Subject: [PATCH 189/239] Update readme info regarding WebAssembly and web target (#1726) --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fc2ed91943..86a31b9b5e 100644 --- a/README.md +++ b/README.md @@ -71,13 +71,24 @@ Winit provides the following features, which can be enabled in your `Cargo.toml` #### WebAssembly -Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to: - -- Put a `` element somewhere. A canvas corresponds to a winit "window". -- Write a Javascript code that creates a global variable named `Module`. Set `Module.canvas` to - the element of the `` element (in the example you would retrieve it via `document.getElementById("my_id")`). - More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html). -- Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created. +Winit supports compiling to the `wasm32-unknown-unknown` target with either a +`stdweb` or a `web-sys` backend for use on web browsers. However, please note +that **the `stdweb` backend is being deprecated and may be removed in a future +release of Winit**. The `web-sys` backend is also more feature complete. + +On the web platform, a Winit window is backed by a `` element. You can +either [provide Winit with a `` element][web with_canvas], or [let Winit +create a `` element which you can then retrieve][web canvas getter] and +insert it into the DOM yourself. + +For example code using Winit with WebAssembly, check out the [web example]. For +information on using Rust on WebAssembly, check out the [Rust and WebAssembly +book]. + +[web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas +[web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas +[web example]: ./examples/web.rs +[Rust and WebAssembly book]: https://rustwasm.github.io/book/ #### Android From 471b1e003afecb89ee370d288061774ed35c83b7 Mon Sep 17 00:00:00 2001 From: Logan Magee Date: Sun, 27 Sep 2020 06:47:47 -0800 Subject: [PATCH 190/239] Bump console_log from 0.1 to 0.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f5c6205235..6f9056b4ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,4 +129,4 @@ optional = true features = ["experimental_features_which_may_break_on_minor_version_bumps"] [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -console_log = "0.1" +console_log = "0.2" From 3d85af04bebeedbd5f974a0ec14884988fd58fda Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 29 Sep 2020 00:11:43 +0300 Subject: [PATCH 191/239] Update SCTK to 0.11.0 * Update SCTK to 0.11.0 Updates smithay-client-toolkit to 0.11.0. The major highlight of that updated, is update of wayland-rs to 0.27.0. Switching to wayland-cursor, instead of using libwayland-cursor. It also fixes the following bugs: - Disabled repeat rate not being handled. - Decoration buttons not working after tty switch. - Scaling not being applied on output reenable. - Crash when `XCURSOR_SIZE` is `0`. - Pointer getting created in some cases without pointer capability. - On kwin, fix space between window and decorations on startup. - Incorrect size event when entering fullscreen when using client side decorations. - Client side decorations not being hided properly in fullscreen. - Size tracking between fullscreen/tiled state changes. - Repeat rate triggering multiple times from slow callback handler. - Resizable attribute not being applied properly on startup. - Not working IME Besides those fixes it also adds a bunch of missing virtual key codes, implements proper cursor grabbing, adds right click on decorations to open application menu, disabled maximize button for non-resizeable window, and fall back for cursor icon to similar ones, if the requested is missing. It also adds new methods to a `Theme` trait, such as: - `title_font(&self) -> Option<(String, f32)>` - The font for a title. - `title_color(&self, window_active: bool) -> [u8; 4]` - The color of the text in the title. Fixes #1680. Fixes #1678. Fixes #1676. Fixes #1646. Fixes #1614. Fixes #1601. Fixes #1533. Fixes #1509. Fixes #952. Fixes #947. --- CHANGELOG.md | 17 + Cargo.toml | 6 +- src/platform/desktop.rs | 6 +- src/platform/unix.rs | 143 +-- src/platform_impl/linux/mod.rs | 21 +- src/platform_impl/linux/wayland/env.rs | 149 +++ src/platform_impl/linux/wayland/event_loop.rs | 1134 ----------------- .../linux/wayland/event_loop/mod.rs | 539 ++++++++ .../linux/wayland/event_loop/proxy.rs | 32 + .../linux/wayland/event_loop/sink.rs | 36 + .../linux/wayland/event_loop/state.rs | 25 + src/platform_impl/linux/wayland/keyboard.rs | 396 ------ src/platform_impl/linux/wayland/mod.rs | 28 +- src/platform_impl/linux/wayland/output.rs | 240 ++++ src/platform_impl/linux/wayland/pointer.rs | 290 ----- .../linux/wayland/seat/keyboard/handlers.rs | 151 +++ .../linux/wayland/seat/keyboard/keymap.rs | 188 +++ .../linux/wayland/seat/keyboard/mod.rs | 105 ++ src/platform_impl/linux/wayland/seat/mod.rs | 208 +++ .../linux/wayland/seat/pointer/data.rs | 74 ++ .../linux/wayland/seat/pointer/handlers.rs | 297 +++++ .../linux/wayland/seat/pointer/mod.rs | 242 ++++ .../linux/wayland/seat/text_input/handlers.rs | 78 ++ .../linux/wayland/seat/text_input/mod.rs | 66 + .../linux/wayland/seat/touch/handlers.rs | 122 ++ .../linux/wayland/seat/touch/mod.rs | 78 ++ src/platform_impl/linux/wayland/touch.rs | 132 -- src/platform_impl/linux/wayland/window.rs | 569 --------- src/platform_impl/linux/wayland/window/mod.rs | 656 ++++++++++ .../linux/wayland/window/shim.rs | 388 ++++++ src/window.rs | 9 +- 31 files changed, 3793 insertions(+), 2632 deletions(-) create mode 100644 src/platform_impl/linux/wayland/env.rs delete mode 100644 src/platform_impl/linux/wayland/event_loop.rs create mode 100644 src/platform_impl/linux/wayland/event_loop/mod.rs create mode 100644 src/platform_impl/linux/wayland/event_loop/proxy.rs create mode 100644 src/platform_impl/linux/wayland/event_loop/sink.rs create mode 100644 src/platform_impl/linux/wayland/event_loop/state.rs delete mode 100644 src/platform_impl/linux/wayland/keyboard.rs create mode 100644 src/platform_impl/linux/wayland/output.rs delete mode 100644 src/platform_impl/linux/wayland/pointer.rs create mode 100644 src/platform_impl/linux/wayland/seat/keyboard/handlers.rs create mode 100644 src/platform_impl/linux/wayland/seat/keyboard/keymap.rs create mode 100644 src/platform_impl/linux/wayland/seat/keyboard/mod.rs create mode 100644 src/platform_impl/linux/wayland/seat/mod.rs create mode 100644 src/platform_impl/linux/wayland/seat/pointer/data.rs create mode 100644 src/platform_impl/linux/wayland/seat/pointer/handlers.rs create mode 100644 src/platform_impl/linux/wayland/seat/pointer/mod.rs create mode 100644 src/platform_impl/linux/wayland/seat/text_input/handlers.rs create mode 100644 src/platform_impl/linux/wayland/seat/text_input/mod.rs create mode 100644 src/platform_impl/linux/wayland/seat/touch/handlers.rs create mode 100644 src/platform_impl/linux/wayland/seat/touch/mod.rs delete mode 100644 src/platform_impl/linux/wayland/touch.rs delete mode 100644 src/platform_impl/linux/wayland/window.rs create mode 100644 src/platform_impl/linux/wayland/window/mod.rs create mode 100644 src/platform_impl/linux/wayland/window/shim.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index da2e8e34c0..5dc3fd1836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,23 @@ - On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called. - **Breaking:** `Fullscreen` enum now uses `Borderless(Option)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor. - On MacOS, fix `WindowEvent::Moved` ignoring the scale factor. +- On Wayland, add missing virtual keycodes. +- On Wayland, implement proper `set_cursor_grab`. +- On Wayland, the cursor will use similar icons if the requested one isn't available. +- On Wayland, right clicking on client side decorations will request application menu. +- On Wayland, fix tracking of window size after state changes. +- On Wayland, fix client side decorations not being hidden properly in fullscreen. +- On Wayland, fix incorrect size event when entering fullscreen with client side decorations. +- On Wayland, fix `resizable` attribute not being applied properly on startup. +- On Wayland, fix disabled repeat rate not being handled. +- On Wayland, fix decoration buttons not working after tty switch. +- On Wayland, fix scaling not being applied on output re-enable. +- On Wayland, fix crash when `XCURSOR_SIZE` is `0`. +- On Wayland, fix pointer getting created in some cases without pointer capability. +- On Wayland, on kwin, fix space between window and decorations on startup. +- **Breaking:** On Wayland, `Theme` trait was reworked. +- On Wayland, disable maximize button for non-resizable window. +- On Wayland, added support for `set_ime_position`. # 0.22.2 (2020-05-16) diff --git a/Cargo.toml b/Cargo.toml index 6f9056b4ca..b10c4b8a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ default = ["x11", "wayland"] web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"] stdweb = ["std_web", "instant/stdweb"] x11 = ["x11-dl"] -wayland = ["wayland-client", "smithay-client-toolkit"] +wayland = ["wayland-client", "sctk"] [dependencies] instant = "0.1" @@ -81,10 +81,10 @@ features = [ ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] -wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] , optional = true } +wayland-client = { version = "0.27", features = [ "dlopen"] , optional = true } +sctk = { package = "smithay-client-toolkit", version = "0.11", optional = true } mio = "0.6" mio-extras = "2.0" -smithay-client-toolkit = { version = "^0.6.6", optional = true } x11-dl = { version = "2.18.5", optional = true } percent-encoding = "2.0" diff --git a/src/platform/desktop.rs b/src/platform/desktop.rs index abf58b29ad..a49d71c377 100644 --- a/src/platform/desktop.rs +++ b/src/platform/desktop.rs @@ -2,7 +2,11 @@ target_os = "windows", target_os = "macos", target_os = "android", - target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd" + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" ))] use crate::{ diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 13346a86cd..02472dd8c5 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -1,12 +1,15 @@ -#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] +#![cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] use std::os::raw; #[cfg(feature = "x11")] use std::{ptr, sync::Arc}; -#[cfg(feature = "wayland")] -use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme}; - use crate::{ event_loop::{EventLoop, EventLoopWindowTarget}, monitor::MonitorHandle, @@ -328,7 +331,7 @@ impl WindowExtUnix for Window { #[cfg(feature = "wayland")] fn wayland_display(&self) -> Option<*mut raw::c_void> { match self.window { - LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _), + LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _), #[cfg(feature = "x11")] _ => None, } @@ -338,7 +341,7 @@ impl WindowExtUnix for Window { #[cfg(feature = "wayland")] fn set_wayland_theme(&self, theme: T) { match self.window { - LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)), + LinuxWindow::Wayland(ref w) => w.set_theme(theme), #[cfg(feature = "x11")] _ => {} } @@ -466,83 +469,50 @@ impl MonitorHandleExtUnix for MonitorHandle { } } -/// Wrapper for implementing SCTK's theme trait. +/// A theme for a Wayland's client side decorations. #[cfg(feature = "wayland")] -struct WaylandTheme(T); - pub trait Theme: Send + 'static { - /// Primary color of the scheme. - fn primary_color(&self, window_active: bool) -> [u8; 4]; - - /// Secondary color of the scheme. - fn secondary_color(&self, window_active: bool) -> [u8; 4]; - - /// Color for the close button. - fn close_button_color(&self, status: ButtonState) -> [u8; 4]; - - /// Icon color for the close button, defaults to the secondary color. - #[allow(unused_variables)] - fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] { - self.secondary_color(true) - } - - /// Background color for the maximize button. - fn maximize_button_color(&self, status: ButtonState) -> [u8; 4]; - - /// Icon color for the maximize button, defaults to the secondary color. - #[allow(unused_variables)] - fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] { - self.secondary_color(true) - } - - /// Background color for the minimize button. - fn minimize_button_color(&self, status: ButtonState) -> [u8; 4]; - - /// Icon color for the minimize button, defaults to the secondary color. - #[allow(unused_variables)] - fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] { - self.secondary_color(true) + /// Title bar color. + fn element_color(&self, element: Element, window_active: bool) -> ARGBColor; + + /// Color for a given button part. + fn button_color( + &self, + button: Button, + state: ButtonState, + foreground: bool, + window_active: bool, + ) -> ARGBColor; + + /// Font name and the size for the title bar. + /// + /// By default the font is `sans-serif` at the size of 11. + /// + /// Returning `None` means that title won't be drawn. + fn font(&self) -> Option<(String, f32)> { + // Not having any title isn't something desirable for the users, so setting it to + // something generic. + Some((String::from("sans-serif"), 11.)) } } +/// A button on Wayland's client side decorations. #[cfg(feature = "wayland")] -impl SCTKTheme for WaylandTheme { - fn get_primary_color(&self, active: bool) -> [u8; 4] { - self.0.primary_color(active) - } - - fn get_secondary_color(&self, active: bool) -> [u8; 4] { - self.0.secondary_color(active) - } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Button { + /// Button that maximizes the window. + Maximize, - fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0.close_button_color(ButtonState::from_sctk(status)) - } + /// Button that minimizes the window. + Minimize, - fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0 - .close_button_icon_color(ButtonState::from_sctk(status)) - } - - fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0.maximize_button_color(ButtonState::from_sctk(status)) - } - - fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0 - .maximize_button_icon_color(ButtonState::from_sctk(status)) - } - - fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0.minimize_button_color(ButtonState::from_sctk(status)) - } - - fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] { - self.0 - .minimize_button_icon_color(ButtonState::from_sctk(status)) - } + /// Button that closes the window. + Close, } +/// A button state of the button on Wayland's client side decorations. +#[cfg(feature = "wayland")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ButtonState { /// Button is being hovered over by pointer. Hovered, @@ -553,12 +523,23 @@ pub enum ButtonState { } #[cfg(feature = "wayland")] -impl ButtonState { - fn from_sctk(button_state: SCTKButtonState) -> Self { - match button_state { - SCTKButtonState::Hovered => Self::Hovered, - SCTKButtonState::Idle => Self::Idle, - SCTKButtonState::Disabled => Self::Disabled, - } - } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Element { + /// Bar itself. + Bar, + + /// Separator between window and title bar. + Separator, + + /// Title bar text. + Text, +} + +#[cfg(feature = "wayland")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ARGBColor { + pub a: u8, + pub r: u8, + pub g: u8, + pub b: u8, } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 43fd84518a..7bc91bdec7 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -9,6 +9,8 @@ #[cfg(all(not(feature = "x11"), not(feature = "wayland")))] compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); +#[cfg(feature = "wayland")] +use std::error::Error; use std::{collections::VecDeque, env, fmt}; #[cfg(feature = "x11")] use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; @@ -16,8 +18,6 @@ use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; #[cfg(feature = "x11")] use parking_lot::Mutex; use raw_window_handle::RawWindowHandle; -#[cfg(feature = "wayland")] -use smithay_client_toolkit::reexports::client::ConnectError; #[cfg(feature = "x11")] pub use self::x11::XNotSupported; @@ -108,6 +108,8 @@ pub enum OsError { XError(XError), #[cfg(feature = "x11")] XMisc(&'static str), + #[cfg(feature = "wayland")] + WaylandMisc(&'static str), } impl fmt::Display for OsError { @@ -117,6 +119,8 @@ impl fmt::Display for OsError { OsError::XError(ref e) => _f.pad(&e.description), #[cfg(feature = "x11")] OsError::XMisc(ref e) => _f.pad(e), + #[cfg(feature = "wayland")] + OsError::WaylandMisc(ref e) => _f.pad(e), } } } @@ -410,13 +414,8 @@ impl Window { } #[inline] - pub fn set_ime_position(&self, _position: Position) { - match self { - #[cfg(feature = "x11")] - &Window::X(ref w) => w.set_ime_position(_position), - #[cfg(feature = "wayland")] - _ => (), - } + pub fn set_ime_position(&self, position: Position) { + x11_or_wayland!(match self; Window(w) => w.set_ime_position(position)) } #[inline] @@ -597,14 +596,14 @@ impl EventLoop { } #[cfg(feature = "wayland")] - pub fn new_wayland() -> Result, ConnectError> { + pub fn new_wayland() -> Result, Box> { assert_is_main_thread("new_wayland_any_thread"); EventLoop::new_wayland_any_thread() } #[cfg(feature = "wayland")] - pub fn new_wayland_any_thread() -> Result, ConnectError> { + pub fn new_wayland_any_thread() -> Result, Box> { wayland::EventLoop::new().map(EventLoop::Wayland) } diff --git a/src/platform_impl/linux/wayland/env.rs b/src/platform_impl/linux/wayland/env.rs new file mode 100644 index 0000000000..1cb2745b59 --- /dev/null +++ b/src/platform_impl/linux/wayland/env.rs @@ -0,0 +1,149 @@ +//! SCTK environment setup. + +use sctk::reexports::client::protocol::wl_compositor::WlCompositor; +use sctk::reexports::client::protocol::wl_output::WlOutput; +use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6; +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1; +use sctk::reexports::client::protocol::wl_shell::WlShell; +use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor; +use sctk::reexports::client::{Attached, DispatchData}; +use sctk::reexports::client::protocol::wl_shm::WlShm; +use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase; +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; + +use sctk::environment::{Environment, SimpleGlobal}; +use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener}; +use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener}; +use sctk::shell::{Shell, ShellHandler, ShellHandling}; +use sctk::shm::ShmHandler; + +/// Set of extra features that are supported by the compositor. +#[derive(Debug, Clone, Copy)] +pub struct WindowingFeatures { + cursor_grab: bool, +} + +impl WindowingFeatures { + /// Create `WindowingFeatures` based on the presented interfaces. + pub fn new(env: &Environment) -> Self { + let cursor_grab = env.get_global::().is_some(); + Self { cursor_grab } + } + + pub fn cursor_grab(&self) -> bool { + self.cursor_grab + } +} + +sctk::environment!(WinitEnv, + singles = [ + WlShm => shm, + WlCompositor => compositor, + WlSubcompositor => subcompositor, + WlShell => shell, + XdgWmBase => shell, + ZxdgShellV6 => shell, + ZxdgDecorationManagerV1 => decoration_manager, + ZwpRelativePointerManagerV1 => relative_pointer_manager, + ZwpPointerConstraintsV1 => pointer_constraints, + ZwpTextInputManagerV3 => text_input_manager, + ], + multis = [ + WlSeat => seats, + WlOutput => outputs, + ] +); + +/// The environment that we utilize. +pub struct WinitEnv { + seats: SeatHandler, + + outputs: OutputHandler, + + shm: ShmHandler, + + compositor: SimpleGlobal, + + subcompositor: SimpleGlobal, + + shell: ShellHandler, + + relative_pointer_manager: SimpleGlobal, + + pointer_constraints: SimpleGlobal, + + text_input_manager: SimpleGlobal, + + decoration_manager: SimpleGlobal, +} + +impl WinitEnv { + pub fn new() -> Self { + // Output tracking for available_monitors, etc. + let outputs = OutputHandler::new(); + + // Keyboard/Pointer/Touch input. + let seats = SeatHandler::new(); + + // Essential globals. + let shm = ShmHandler::new(); + let compositor = SimpleGlobal::new(); + let subcompositor = SimpleGlobal::new(); + + // Gracefully handle shell picking, since SCTK automatically supports multiple + // backends. + let shell = ShellHandler::new(); + + // Server side decorations. + let decoration_manager = SimpleGlobal::new(); + + // Device events for pointer. + let relative_pointer_manager = SimpleGlobal::new(); + + // Pointer grab functionality. + let pointer_constraints = SimpleGlobal::new(); + + // IME handling. + let text_input_manager = SimpleGlobal::new(); + + Self { + seats, + outputs, + shm, + compositor, + subcompositor, + shell, + decoration_manager, + relative_pointer_manager, + pointer_constraints, + text_input_manager, + } + } +} + +impl ShellHandling for WinitEnv { + fn get_shell(&self) -> Option { + self.shell.get_shell() + } +} + +impl SeatHandling for WinitEnv { + fn listen, &SeatData, DispatchData<'_>) + 'static>( + &mut self, + f: F, + ) -> SeatListener { + self.seats.listen(f) + } +} + +impl OutputHandling for WinitEnv { + fn listen) + 'static>( + &mut self, + f: F, + ) -> OutputStatusListener { + self.outputs.listen(f) + } +} diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs deleted file mode 100644 index 4dd66f1223..0000000000 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ /dev/null @@ -1,1134 +0,0 @@ -use std::{ - cell::RefCell, - collections::VecDeque, - fmt, - io::ErrorKind, - rc::Rc, - sync::{Arc, Mutex}, - time::{Duration, Instant}, -}; - -use mio::{Events, Poll, PollOpt, Ready, Token}; - -use mio_extras::channel::{channel, Receiver, SendError, Sender}; - -use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{ - zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::ZwpPointerConstraintsV1, -}; -use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{ - zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, - zwp_relative_pointer_v1::ZwpRelativePointerV1, -}; - -use smithay_client_toolkit::pointer::{AutoPointer, AutoThemer}; -use smithay_client_toolkit::reexports::client::protocol::{ - wl_compositor::WlCompositor, wl_shm::WlShm, wl_surface::WlSurface, -}; - -use crate::{ - dpi::{LogicalSize, PhysicalPosition, PhysicalSize}, - event::{ - DeviceEvent, DeviceId as RootDeviceId, Event, ModifiersState, StartCause, WindowEvent, - }, - event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform_impl::platform::{ - sticky_exit_callback, DeviceId as PlatformDeviceId, MonitorHandle as PlatformMonitorHandle, - VideoMode as PlatformVideoMode, WindowId as PlatformWindowId, - }, - window::{CursorIcon, WindowId as RootWindowId}, -}; - -use super::{ - window::{DecorationsAction, WindowStore}, - DeviceId, WindowId, -}; - -use smithay_client_toolkit::{ - output::OutputMgr, - reexports::client::{ - protocol::{wl_keyboard, wl_output, wl_pointer, wl_registry, wl_seat, wl_touch}, - ConnectError, Display, EventQueue, GlobalEvent, - }, - Environment, -}; - -const KBD_TOKEN: Token = Token(0); -const USER_TOKEN: Token = Token(1); -const EVQ_TOKEN: Token = Token(2); - -#[derive(Clone)] -pub struct EventsSink { - sender: Sender>, -} - -impl EventsSink { - pub fn new(sender: Sender>) -> EventsSink { - EventsSink { sender } - } - - pub fn send_event(&self, event: Event<'static, ()>) { - self.sender.send(event).unwrap() - } - - pub fn send_device_event(&self, event: DeviceEvent, device_id: DeviceId) { - self.send_event(Event::DeviceEvent { - event, - device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)), - }); - } - - pub fn send_window_event(&self, event: WindowEvent<'static>, window_id: WindowId) { - self.send_event(Event::WindowEvent { - event, - window_id: RootWindowId(PlatformWindowId::Wayland(window_id)), - }); - } -} - -pub struct CursorManager { - pointer_constraints_proxy: Arc>>, - auto_themer: Option, - pointers: Vec, - locked_pointers: Vec, - cursor_visible: bool, - current_cursor: CursorIcon, - scale_factor: u32, -} - -impl CursorManager { - fn new(constraints: Arc>>) -> CursorManager { - CursorManager { - pointer_constraints_proxy: constraints, - auto_themer: None, - pointers: Vec::new(), - locked_pointers: Vec::new(), - cursor_visible: true, - current_cursor: CursorIcon::default(), - scale_factor: 1, - } - } - - fn register_pointer(&mut self, pointer: wl_pointer::WlPointer) { - let auto_themer = self - .auto_themer - .as_ref() - .expect("AutoThemer not initialized. Server did not advertise shm or compositor?"); - self.pointers.push(auto_themer.theme_pointer(pointer)); - } - - fn set_auto_themer(&mut self, auto_themer: AutoThemer) { - self.auto_themer = Some(auto_themer); - } - - pub fn set_cursor_visible(&mut self, visible: bool) { - if !visible { - for pointer in self.pointers.iter() { - (**pointer).set_cursor(0, None, 0, 0); - } - } else { - self.set_cursor_icon_impl(self.current_cursor); - } - self.cursor_visible = visible; - } - - /// A helper function to restore cursor styles on PtrEvent::Enter. - pub fn reload_cursor_style(&mut self) { - if !self.cursor_visible { - self.set_cursor_visible(false); - } else { - self.set_cursor_icon_impl(self.current_cursor); - } - } - - pub fn set_cursor_icon(&mut self, cursor: CursorIcon) { - if cursor != self.current_cursor { - self.current_cursor = cursor; - if self.cursor_visible { - self.set_cursor_icon_impl(cursor); - } - } - } - - pub fn update_scale_factor(&mut self, scale: u32) { - self.scale_factor = scale; - self.reload_cursor_style(); - } - - fn set_cursor_icon_impl(&mut self, cursor: CursorIcon) { - let cursor = match cursor { - CursorIcon::Alias => "link", - CursorIcon::Arrow => "arrow", - CursorIcon::Cell => "plus", - CursorIcon::Copy => "copy", - CursorIcon::Crosshair => "crosshair", - CursorIcon::Default => "left_ptr", - CursorIcon::Hand => "hand", - CursorIcon::Help => "question_arrow", - CursorIcon::Move => "move", - CursorIcon::Grab => "grab", - CursorIcon::Grabbing => "grabbing", - CursorIcon::Progress => "progress", - CursorIcon::AllScroll => "all-scroll", - CursorIcon::ContextMenu => "context-menu", - - CursorIcon::NoDrop => "no-drop", - CursorIcon::NotAllowed => "crossed_circle", - - // Resize cursors - CursorIcon::EResize => "right_side", - CursorIcon::NResize => "top_side", - CursorIcon::NeResize => "top_right_corner", - CursorIcon::NwResize => "top_left_corner", - CursorIcon::SResize => "bottom_side", - CursorIcon::SeResize => "bottom_right_corner", - CursorIcon::SwResize => "bottom_left_corner", - CursorIcon::WResize => "left_side", - CursorIcon::EwResize => "h_double_arrow", - CursorIcon::NsResize => "v_double_arrow", - CursorIcon::NwseResize => "bd_double_arrow", - CursorIcon::NeswResize => "fd_double_arrow", - CursorIcon::ColResize => "h_double_arrow", - CursorIcon::RowResize => "v_double_arrow", - - CursorIcon::Text => "text", - CursorIcon::VerticalText => "vertical-text", - - CursorIcon::Wait => "watch", - - CursorIcon::ZoomIn => "zoom-in", - CursorIcon::ZoomOut => "zoom-out", - }; - - for pointer in self.pointers.iter() { - // Ignore erros, since we don't want to fail hard in case we can't find a proper cursor - // in a given theme. - let _ = pointer.set_cursor_with_scale(cursor, self.scale_factor, None); - } - } - - // This function can only be called from a thread on which `pointer_constraints_proxy` event - // queue is located, so calling it directly from a Window doesn't work well, in case - // you've sent your window to another thread, so we need to pass cursor grab updates to - // the event loop and call this function from there. - fn grab_pointer(&mut self, surface: Option<&WlSurface>) { - for locked_pointer in self.locked_pointers.drain(..) { - locked_pointer.destroy(); - } - - if let Some(surface) = surface { - for pointer in self.pointers.iter() { - let locked_pointer = self - .pointer_constraints_proxy - .try_lock() - .unwrap() - .as_ref() - .and_then(|pointer_constraints| { - super::pointer::implement_locked_pointer( - surface, - &**pointer, - pointer_constraints, - ) - .ok() - }); - - if let Some(locked_pointer) = locked_pointer { - self.locked_pointers.push(locked_pointer); - } - } - } - } -} - -pub struct EventLoop { - // Poll instance - poll: Poll, - // The wayland display - pub display: Arc, - // The cursor manager - cursor_manager: Arc>, - kbd_channel: Receiver>, - user_channel: Receiver, - user_sender: Sender, - window_target: RootELW, -} - -// A handle that can be sent across threads and used to wake up the `EventLoop`. -// -// We should only try and wake up the `EventLoop` if it still exists, so we hold Weak ptrs. -pub struct EventLoopProxy { - user_sender: Sender, -} - -pub struct EventLoopWindowTarget { - // the event queue - pub evq: RefCell, - // The window store - pub store: Arc>, - // The cursor manager - pub cursor_manager: Arc>, - // The env - pub env: Environment, - // A cleanup switch to prune dead windows - pub cleanup_needed: Arc>, - // The wayland display - pub display: Arc, - // The list of seats - pub seats: Arc>>, - // The output manager - pub outputs: OutputMgr, - _marker: ::std::marker::PhantomData, -} - -impl Clone for EventLoopProxy { - fn clone(&self) -> Self { - EventLoopProxy { - user_sender: self.user_sender.clone(), - } - } -} - -impl EventLoopProxy { - pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { - self.user_sender.send(event).map_err(|e| { - EventLoopClosed(if let SendError::Disconnected(x) = e { - x - } else { - unreachable!() - }) - }) - } -} - -impl EventLoop { - pub fn new() -> Result, ConnectError> { - let (display, mut event_queue) = Display::connect_to_env()?; - - let display = Arc::new(display); - let store = Arc::new(Mutex::new(WindowStore::new())); - let seats = Arc::new(Mutex::new(Vec::new())); - - let poll = Poll::new().unwrap(); - - let (kbd_sender, kbd_channel) = channel(); - - let sink = EventsSink::new(kbd_sender); - - poll.register(&kbd_channel, KBD_TOKEN, Ready::readable(), PollOpt::level()) - .unwrap(); - - let pointer_constraints_proxy = Arc::new(Mutex::new(None)); - - let mut seat_manager = SeatManager { - sink, - store: store.clone(), - seats: seats.clone(), - relative_pointer_manager_proxy: Rc::new(RefCell::new(None)), - pointer_constraints_proxy: pointer_constraints_proxy.clone(), - cursor_manager: Arc::new(Mutex::new(CursorManager::new(pointer_constraints_proxy))), - }; - - let cursor_manager = seat_manager.cursor_manager.clone(); - let cursor_manager_clone = cursor_manager.clone(); - - let shm_cell = Rc::new(RefCell::new(None)); - let compositor_cell = Rc::new(RefCell::new(None)); - - let env = Environment::from_display_with_cb( - &display, - &mut event_queue, - move |event, registry| match event { - GlobalEvent::New { - id, - ref interface, - version, - } => { - if interface == "zwp_relative_pointer_manager_v1" { - let relative_pointer_manager_proxy = registry - .bind(version, id, move |pointer_manager| { - pointer_manager.implement_closure(|_, _| (), ()) - }) - .unwrap(); - - *seat_manager - .relative_pointer_manager_proxy - .try_borrow_mut() - .unwrap() = Some(relative_pointer_manager_proxy); - } - if interface == "zwp_pointer_constraints_v1" { - let pointer_constraints_proxy = registry - .bind(version, id, move |pointer_constraints| { - pointer_constraints.implement_closure(|_, _| (), ()) - }) - .unwrap(); - - *seat_manager.pointer_constraints_proxy.lock().unwrap() = - Some(pointer_constraints_proxy); - } - if interface == "wl_shm" { - let shm: WlShm = registry - .bind(version, id, move |shm| shm.implement_closure(|_, _| (), ())) - .unwrap(); - - (*shm_cell.borrow_mut()) = Some(shm); - } - if interface == "wl_compositor" { - let compositor: WlCompositor = registry - .bind(version, id, move |compositor| { - compositor.implement_closure(|_, _| (), ()) - }) - .unwrap(); - (*compositor_cell.borrow_mut()) = Some(compositor); - } - - if compositor_cell.borrow().is_some() && shm_cell.borrow().is_some() { - let compositor = compositor_cell.borrow_mut().take().unwrap(); - let shm = shm_cell.borrow_mut().take().unwrap(); - let auto_themer = AutoThemer::init(None, compositor, &shm); - cursor_manager_clone - .lock() - .unwrap() - .set_auto_themer(auto_themer); - } - - if interface == "wl_seat" { - seat_manager.add_seat(id, version, registry) - } - } - GlobalEvent::Removed { id, ref interface } => { - if interface == "wl_seat" { - seat_manager.remove_seat(id) - } - } - }, - ) - .unwrap(); - - poll.register(&event_queue, EVQ_TOKEN, Ready::readable(), PollOpt::level()) - .unwrap(); - - let (user_sender, user_channel) = channel(); - - poll.register( - &user_channel, - USER_TOKEN, - Ready::readable(), - PollOpt::level(), - ) - .unwrap(); - - let cursor_manager_clone = cursor_manager.clone(); - let outputs = env.outputs.clone(); - Ok(EventLoop { - poll, - display: display.clone(), - user_sender, - user_channel, - kbd_channel, - cursor_manager, - window_target: RootELW { - p: crate::platform_impl::EventLoopWindowTarget::Wayland(EventLoopWindowTarget { - evq: RefCell::new(event_queue), - store, - env, - cursor_manager: cursor_manager_clone, - cleanup_needed: Arc::new(Mutex::new(false)), - seats, - display, - outputs, - _marker: ::std::marker::PhantomData, - }), - _marker: ::std::marker::PhantomData, - }, - }) - } - - pub fn create_proxy(&self) -> EventLoopProxy { - EventLoopProxy { - user_sender: self.user_sender.clone(), - } - } - - pub fn run(mut self, callback: F) -> ! - where - F: 'static + FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - self.run_return(callback); - std::process::exit(0); - } - - pub fn run_return(&mut self, mut callback: F) - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - // send pending events to the server - self.display.flush().expect("Wayland connection lost."); - - let mut control_flow = ControlFlow::default(); - let mut events = Events::with_capacity(8); - - callback( - Event::NewEvents(StartCause::Init), - &self.window_target, - &mut control_flow, - ); - - loop { - // Read events from the event queue - { - let mut evq = get_target(&self.window_target).evq.borrow_mut(); - - evq.dispatch_pending() - .expect("failed to dispatch wayland events"); - - if let Some(read) = evq.prepare_read() { - if let Err(e) = read.read_events() { - if e.kind() != ErrorKind::WouldBlock { - panic!("failed to read wayland events: {}", e); - } - } - - evq.dispatch_pending() - .expect("failed to dispatch wayland events"); - } - } - - self.post_dispatch_triggers(&mut callback, &mut control_flow); - - while let Ok(event) = self.kbd_channel.try_recv() { - let event = event.map_nonuser_event().unwrap(); - sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); - } - - while let Ok(event) = self.user_channel.try_recv() { - sticky_exit_callback( - Event::UserEvent(event), - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - - // send Events cleared - { - sticky_exit_callback( - Event::MainEventsCleared, - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - - // handle request-redraw - { - self.redraw_triggers(|wid, window_target| { - sticky_exit_callback( - Event::RedrawRequested(crate::window::WindowId( - crate::platform_impl::WindowId::Wayland(wid), - )), - window_target, - &mut control_flow, - &mut callback, - ); - }); - } - - // send RedrawEventsCleared - { - sticky_exit_callback( - Event::RedrawEventsCleared, - &self.window_target, - &mut control_flow, - &mut callback, - ); - } - - // send pending events to the server - self.display.flush().expect("Wayland connection lost."); - - // During the run of the user callback, some other code monitoring and reading the - // wayland socket may have been run (mesa for example does this with vsync), if that - // is the case, some events may have been enqueued in our event queue. - // - // If some messages are there, the event loop needs to behave as if it was instantly - // woken up by messages arriving from the wayland socket, to avoid getting stuck. - let instant_wakeup = { - let window_target = match self.window_target.p { - crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, - #[cfg(feature = "x11")] - _ => unreachable!(), - }; - let dispatched = window_target - .evq - .borrow_mut() - .dispatch_pending() - .expect("Wayland connection lost."); - dispatched > 0 - }; - - match control_flow { - ControlFlow::Exit => break, - ControlFlow::Poll => { - // non-blocking dispatch - self.poll - .poll(&mut events, Some(Duration::from_millis(0))) - .unwrap(); - events.clear(); - - callback( - Event::NewEvents(StartCause::Poll), - &self.window_target, - &mut control_flow, - ); - } - ControlFlow::Wait => { - if !instant_wakeup { - self.poll.poll(&mut events, None).unwrap(); - events.clear(); - } - - callback( - Event::NewEvents(StartCause::WaitCancelled { - start: Instant::now(), - requested_resume: None, - }), - &self.window_target, - &mut control_flow, - ); - } - ControlFlow::WaitUntil(deadline) => { - let start = Instant::now(); - // compute the blocking duration - let duration = if deadline > start && !instant_wakeup { - deadline - start - } else { - Duration::from_millis(0) - }; - self.poll.poll(&mut events, Some(duration)).unwrap(); - events.clear(); - - let now = Instant::now(); - if now < deadline { - callback( - Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: Some(deadline), - }), - &self.window_target, - &mut control_flow, - ); - } else { - callback( - Event::NewEvents(StartCause::ResumeTimeReached { - start, - requested_resume: deadline, - }), - &self.window_target, - &mut control_flow, - ); - } - } - } - } - - callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); - } - - pub fn window_target(&self) -> &RootELW { - &self.window_target - } -} - -impl EventLoopWindowTarget { - pub fn display(&self) -> &Display { - &*self.display - } - - pub fn available_monitors(&self) -> VecDeque { - available_monitors(&self.outputs) - } - - pub fn primary_monitor(&self) -> Option { - // Wayland doesn't have a notion of primary monitor. - None - } -} - -/* - * Private EventLoop Internals - */ - -impl EventLoop { - fn redraw_triggers(&mut self, mut callback: F) - where - F: FnMut(WindowId, &RootELW), - { - let window_target = match self.window_target.p { - crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, - #[cfg(feature = "x11")] - _ => unreachable!(), - }; - window_target.store.lock().unwrap().for_each_redraw_trigger( - |refresh, frame_refresh, wid, frame| { - if let Some(frame) = frame { - let mut frame = frame.lock().unwrap(); - - if frame_refresh { - frame.refresh(); - if !refresh { - frame.surface().commit() - } - } - } - if refresh { - callback(wid, &self.window_target); - } - }, - ) - } - - fn post_dispatch_triggers(&mut self, mut callback: F, control_flow: &mut ControlFlow) - where - F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), - { - let window_target = match self.window_target.p { - crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, - #[cfg(feature = "x11")] - _ => unreachable!(), - }; - - let mut callback = |event: Event<'_, T>| { - sticky_exit_callback(event, &self.window_target, control_flow, &mut callback); - }; - - // prune possible dead windows - { - let mut cleanup_needed = window_target.cleanup_needed.lock().unwrap(); - if *cleanup_needed { - let pruned = window_target.store.lock().unwrap().cleanup(); - *cleanup_needed = false; - for wid in pruned { - callback(Event::WindowEvent { - window_id: crate::window::WindowId( - crate::platform_impl::WindowId::Wayland(wid), - ), - event: WindowEvent::Destroyed, - }); - } - } - } - // process pending resize/refresh - window_target.store.lock().unwrap().for_each(|window| { - let window_id = - crate::window::WindowId(crate::platform_impl::WindowId::Wayland(window.wid)); - - // Update window logical .size field (for callbacks using .inner_size) - let (old_logical_size, mut logical_size) = { - let mut window_size = window.size.lock().unwrap(); - let old_logical_size = *window_size; - *window_size = window.new_size.unwrap_or(old_logical_size); - (old_logical_size, *window_size) - }; - - if let Some(scale_factor) = window.new_scale_factor { - // Update cursor scale factor - self.cursor_manager - .lock() - .unwrap() - .update_scale_factor(scale_factor as u32); - let new_logical_size = { - let scale_factor = scale_factor as f64; - let mut physical_size = - LogicalSize::::from(logical_size).to_physical(scale_factor); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor, - new_inner_size: &mut physical_size, - }, - }); - physical_size.to_logical::(scale_factor).into() - }; - // Update size if changed by callback - if new_logical_size != logical_size { - logical_size = new_logical_size; - *window.size.lock().unwrap() = logical_size.into(); - } - } - - if window.new_size.is_some() || window.new_scale_factor.is_some() { - if let Some(frame) = window.frame { - let mut frame = frame.lock().unwrap(); - // Update decorations state - match window.decorations_action { - Some(DecorationsAction::Hide) => frame.set_decorate(false), - Some(DecorationsAction::Show) => frame.set_decorate(true), - None => (), - } - - // mutter (GNOME Wayland) relies on `set_geometry` to reposition window in case - // it overlaps mutter's `bounding box`, so we can't avoid this resize call, - // which calls `set_geometry` under the hood, for now. - let (w, h) = logical_size; - frame.resize(w, h); - frame.refresh(); - } - // Don't send resize event downstream if the new logical size and scale is identical to the - // current one - if logical_size != old_logical_size || window.new_scale_factor.is_some() { - let physical_size = LogicalSize::::from(logical_size).to_physical( - window.new_scale_factor.unwrap_or(window.prev_scale_factor) as f64, - ); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Resized(physical_size), - }); - } - } - - if window.closed { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::CloseRequested, - }); - } - - if let Some(grab_cursor) = window.grab_cursor { - let surface = if grab_cursor { - Some(window.surface) - } else { - None - }; - self.cursor_manager.lock().unwrap().grab_pointer(surface); - } - }) - } -} - -fn get_target(target: &RootELW) -> &EventLoopWindowTarget { - match target.p { - crate::platform_impl::EventLoopWindowTarget::Wayland(ref wt) => wt, - #[cfg(feature = "x11")] - _ => unreachable!(), - } -} - -/* - * Wayland protocol implementations - */ - -struct SeatManager { - sink: EventsSink, - store: Arc>, - seats: Arc>>, - relative_pointer_manager_proxy: Rc>>, - pointer_constraints_proxy: Arc>>, - cursor_manager: Arc>, -} - -impl SeatManager { - fn add_seat(&mut self, id: u32, version: u32, registry: wl_registry::WlRegistry) { - use std::cmp::min; - - let mut seat_data = SeatData { - sink: self.sink.clone(), - store: self.store.clone(), - pointer: None, - relative_pointer: None, - relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.clone(), - keyboard: None, - touch: None, - modifiers_tracker: Arc::new(Mutex::new(ModifiersState::default())), - cursor_manager: self.cursor_manager.clone(), - }; - let seat = registry - .bind(min(version, 5), id, move |seat| { - seat.implement_closure(move |event, seat| seat_data.receive(event, seat), ()) - }) - .unwrap(); - self.store.lock().unwrap().new_seat(&seat); - self.seats.lock().unwrap().push((id, seat)); - } - - fn remove_seat(&mut self, id: u32) { - let mut seats = self.seats.lock().unwrap(); - if let Some(idx) = seats.iter().position(|&(i, _)| i == id) { - let (_, seat) = seats.swap_remove(idx); - if seat.as_ref().version() >= 5 { - seat.release(); - } - } - } -} - -struct SeatData { - sink: EventsSink, - store: Arc>, - pointer: Option, - relative_pointer: Option, - relative_pointer_manager_proxy: Rc>>, - keyboard: Option, - touch: Option, - modifiers_tracker: Arc>, - cursor_manager: Arc>, -} - -impl SeatData { - fn receive(&mut self, evt: wl_seat::Event, seat: wl_seat::WlSeat) { - match evt { - wl_seat::Event::Name { .. } => (), - wl_seat::Event::Capabilities { capabilities } => { - // create pointer if applicable - if capabilities.contains(wl_seat::Capability::Pointer) && self.pointer.is_none() { - self.pointer = Some(super::pointer::implement_pointer( - &seat, - self.sink.clone(), - self.store.clone(), - self.modifiers_tracker.clone(), - self.cursor_manager.clone(), - )); - - self.cursor_manager - .lock() - .unwrap() - .register_pointer(self.pointer.as_ref().unwrap().clone()); - - self.relative_pointer = self - .relative_pointer_manager_proxy - .try_borrow() - .unwrap() - .as_ref() - .and_then(|manager| { - super::pointer::implement_relative_pointer( - self.sink.clone(), - self.pointer.as_ref().unwrap(), - manager, - ) - .ok() - }) - } - // destroy pointer if applicable - if !capabilities.contains(wl_seat::Capability::Pointer) { - if let Some(pointer) = self.pointer.take() { - if pointer.as_ref().version() >= 3 { - pointer.release(); - } - } - } - // create keyboard if applicable - if capabilities.contains(wl_seat::Capability::Keyboard) && self.keyboard.is_none() { - self.keyboard = Some(super::keyboard::init_keyboard( - &seat, - self.sink.clone(), - self.modifiers_tracker.clone(), - )) - } - // destroy keyboard if applicable - if !capabilities.contains(wl_seat::Capability::Keyboard) { - if let Some(kbd) = self.keyboard.take() { - if kbd.as_ref().version() >= 3 { - kbd.release(); - } - } - } - // create touch if applicable - if capabilities.contains(wl_seat::Capability::Touch) && self.touch.is_none() { - self.touch = Some(super::touch::implement_touch( - &seat, - self.sink.clone(), - self.store.clone(), - )) - } - // destroy touch if applicable - if !capabilities.contains(wl_seat::Capability::Touch) { - if let Some(touch) = self.touch.take() { - if touch.as_ref().version() >= 3 { - touch.release(); - } - } - } - } - _ => unreachable!(), - } - } -} - -impl Drop for SeatData { - fn drop(&mut self) { - if let Some(pointer) = self.pointer.take() { - if pointer.as_ref().version() >= 3 { - pointer.release(); - } - } - if let Some(kbd) = self.keyboard.take() { - if kbd.as_ref().version() >= 3 { - kbd.release(); - } - } - if let Some(touch) = self.touch.take() { - if touch.as_ref().version() >= 3 { - touch.release(); - } - } - } -} - -/* - * Monitor stuff - */ - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct VideoMode { - pub(crate) size: (u32, u32), - pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, - pub(crate) monitor: MonitorHandle, -} - -impl VideoMode { - #[inline] - pub fn size(&self) -> PhysicalSize { - self.size.into() - } - - #[inline] - pub fn bit_depth(&self) -> u16 { - self.bit_depth - } - - #[inline] - pub fn refresh_rate(&self) -> u16 { - self.refresh_rate - } - - #[inline] - pub fn monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(self.monitor.clone()), - } - } -} - -#[derive(Clone)] -pub struct MonitorHandle { - pub(crate) proxy: wl_output::WlOutput, - pub(crate) mgr: OutputMgr, -} - -impl PartialEq for MonitorHandle { - fn eq(&self, other: &Self) -> bool { - self.native_identifier() == other.native_identifier() - } -} - -impl Eq for MonitorHandle {} - -impl PartialOrd for MonitorHandle { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(&other)) - } -} - -impl Ord for MonitorHandle { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.native_identifier().cmp(&other.native_identifier()) - } -} - -impl std::hash::Hash for MonitorHandle { - fn hash(&self, state: &mut H) { - self.native_identifier().hash(state); - } -} - -impl fmt::Debug for MonitorHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[derive(Debug)] - struct MonitorHandle { - name: Option, - native_identifier: u32, - size: PhysicalSize, - position: PhysicalPosition, - scale_factor: i32, - } - - let monitor_id_proxy = MonitorHandle { - name: self.name(), - native_identifier: self.native_identifier(), - size: self.size(), - position: self.position(), - scale_factor: self.scale_factor(), - }; - - monitor_id_proxy.fmt(f) - } -} - -impl MonitorHandle { - pub fn name(&self) -> Option { - self.mgr.with_info(&self.proxy, |_, info| { - format!("{} ({})", info.model, info.make) - }) - } - - #[inline] - pub fn native_identifier(&self) -> u32 { - self.mgr.with_info(&self.proxy, |id, _| id).unwrap_or(0) - } - - pub fn size(&self) -> PhysicalSize { - match self.mgr.with_info(&self.proxy, |_, info| { - info.modes - .iter() - .find(|m| m.is_current) - .map(|m| m.dimensions) - }) { - Some(Some((w, h))) => (w as u32, h as u32), - _ => (0, 0), - } - .into() - } - - pub fn position(&self) -> PhysicalPosition { - self.mgr - .with_info(&self.proxy, |_, info| info.location) - .unwrap_or((0, 0)) - .into() - } - - #[inline] - pub fn scale_factor(&self) -> i32 { - self.mgr - .with_info(&self.proxy, |_, info| info.scale_factor) - .unwrap_or(1) - } - - #[inline] - pub fn video_modes(&self) -> impl Iterator { - let monitor = self.clone(); - - self.mgr - .with_info(&self.proxy, |_, info| info.modes.clone()) - .unwrap_or(vec![]) - .into_iter() - .map(move |x| RootVideoMode { - video_mode: PlatformVideoMode::Wayland(VideoMode { - size: (x.dimensions.0 as u32, x.dimensions.1 as u32), - refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16, - bit_depth: 32, - monitor: monitor.clone(), - }), - }) - } -} - -pub fn available_monitors(outputs: &OutputMgr) -> VecDeque { - outputs.with_all(|list| { - list.iter() - .map(|&(_, ref proxy, _)| MonitorHandle { - proxy: proxy.clone(), - mgr: outputs.clone(), - }) - .collect() - }) -} diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs new file mode 100644 index 0000000000..9e7cdde304 --- /dev/null +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -0,0 +1,539 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::error::Error; +use std::process; +use std::rc::Rc; +use std::time::{Duration, Instant}; + +use sctk::reexports::client::protocol::wl_compositor::WlCompositor; +use sctk::reexports::client::protocol::wl_shm::WlShm; +use sctk::reexports::client::Display; + +use sctk::reexports::calloop; + +use sctk::environment::Environment; +use sctk::seat::pointer::{ThemeManager, ThemeSpec}; +use sctk::WaylandSource; + +use crate::event::{Event, StartCause, WindowEvent}; +use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget}; +use crate::platform_impl::platform::sticky_exit_callback; + +use super::env::{WindowingFeatures, WinitEnv}; +use super::output::OutputManager; +use super::seat::SeatManager; +use super::window::shim::{self, WindowUpdate}; +use super::{DeviceId, WindowId}; + +mod proxy; +mod sink; +mod state; + +pub use proxy::EventLoopProxy; +pub use state::WinitState; + +use sink::EventSink; + +pub struct EventLoopWindowTarget { + /// Wayland display. + pub display: Display, + + /// Environment to handle object creation, etc. + pub env: Environment, + + /// Event loop handle. + pub event_loop_handle: calloop::LoopHandle, + + /// Output manager. + pub output_manager: OutputManager, + + /// State that we share across callbacks. + pub state: RefCell, + + /// Wayland source. + pub wayland_source: Rc>, + + /// A proxy to wake up event loop. + pub event_loop_awakener: calloop::ping::Ping, + + /// The available windowing features. + pub windowing_features: WindowingFeatures, + + /// Theme manager to manage cursors. + /// + /// It's being shared amoung all windows to avoid loading + /// multiple similar themes. + pub theme_manager: ThemeManager, + + _marker: std::marker::PhantomData, +} + +pub struct EventLoop { + /// Event loop. + event_loop: calloop::EventLoop, + + /// Wayland display. + display: Display, + + /// Pending user events. + pending_user_events: Rc>>, + + /// Sender of user events. + user_events_sender: calloop::channel::Sender, + + /// Wayland source of events. + wayland_source: Rc>, + + /// Window target. + window_target: RootEventLoopWindowTarget, + + /// Output manager. + _seat_manager: SeatManager, +} + +impl EventLoop { + pub fn new() -> Result, Box> { + // Connect to wayland server and setup event queue. + let display = Display::connect_to_env()?; + let mut event_queue = display.create_event_queue(); + let display_proxy = display.attach(event_queue.token()); + + // Setup environment. + let env = Environment::init(&display_proxy, WinitEnv::new()); + + // Issue 2 sync roundtrips to initialize environment. + event_queue.sync_roundtrip(&mut (), |_, _, _| unreachable!())?; + event_queue.sync_roundtrip(&mut (), |_, _, _| unreachable!())?; + + // Create event loop. + let event_loop = calloop::EventLoop::::new()?; + // Build windowing features. + let windowing_features = WindowingFeatures::new(&env); + + // Create a theme manager. + let compositor = env.require_global::(); + let shm = env.require_global::(); + let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm); + + // Setup theme seat and output managers. + let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone()); + let output_manager = OutputManager::new(&env); + + // A source of events that we plug into our event loop. + let wayland_source = WaylandSource::new(event_queue).quick_insert(event_loop.handle())?; + let wayland_source = Rc::new(wayland_source); + + // A source of user events. + let pending_user_events = Rc::new(RefCell::new(Vec::new())); + let pending_user_events_clone = pending_user_events.clone(); + let (user_events_sender, user_events_channel) = calloop::channel::channel(); + + // User events channel. + event_loop + .handle() + .insert_source(user_events_channel, move |event, _, _| { + if let calloop::channel::Event::Msg(msg) = event { + pending_user_events_clone.borrow_mut().push(msg); + } + })?; + + // An event's loop awakener to wake up for window events from winit's windows. + let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?; + + // Handler of window requests. + event_loop.handle().insert_source( + event_loop_awakener_source, + move |_, _, winit_state| { + shim::handle_window_requests(winit_state); + }, + )?; + + let event_loop_handle = event_loop.handle(); + let window_map = HashMap::new(); + let event_sink = EventSink::new(); + let window_updates = HashMap::new(); + + // Create event loop window target. + let event_loop_window_target = EventLoopWindowTarget { + display: display.clone(), + env, + state: RefCell::new(WinitState { + window_map, + event_sink, + window_updates, + }), + event_loop_handle, + output_manager, + event_loop_awakener, + wayland_source: wayland_source.clone(), + windowing_features, + theme_manager, + _marker: std::marker::PhantomData, + }; + + // Create event loop itself. + let event_loop = Self { + event_loop, + display, + pending_user_events, + wayland_source, + _seat_manager: seat_manager, + user_events_sender, + window_target: RootEventLoopWindowTarget { + p: crate::platform_impl::EventLoopWindowTarget::Wayland(event_loop_window_target), + _marker: std::marker::PhantomData, + }, + }; + + Ok(event_loop) + } + + pub fn run(mut self, callback: F) -> ! + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow) + 'static, + { + self.run_return(callback); + process::exit(0) + } + + pub fn run_return(&mut self, mut callback: F) + where + F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget, &mut ControlFlow), + { + // Send pending events to the server. + let _ = self.display.flush(); + + let mut control_flow = ControlFlow::default(); + + let pending_user_events = self.pending_user_events.clone(); + + callback( + Event::NewEvents(StartCause::Init), + &self.window_target, + &mut control_flow, + ); + + let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new(); + let mut event_sink_back_buffer = Vec::new(); + + // NOTE We break on errors from dispatches, since if we've got protocol error + // libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not + // really an option. Instead we inform that the event loop got destroyed. We may + // communicate an error that something was terminated, but winit doesn't provide us + // with an API to do that via some event. + loop { + // Handle pending user events. We don't need back buffer, since we can't dispatch + // user events indirectly via callback to the user. + for user_event in pending_user_events.borrow_mut().drain(..) { + sticky_exit_callback( + Event::UserEvent(user_event), + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + + // Process 'new' pending updates. + self.with_state(|state| { + window_updates.clear(); + window_updates.extend( + state + .window_updates + .iter_mut() + .map(|(wid, window_update)| (*wid, window_update.take())), + ); + }); + + for (window_id, window_update) in window_updates.iter_mut() { + if let Some(scale_factor) = window_update.scale_factor.map(|f| f as f64) { + let mut physical_size = self.with_state(|state| { + let window_handle = state.window_map.get(&window_id).unwrap(); + let mut size = window_handle.size.lock().unwrap(); + + // Update the new logical size if it was changed. + let window_size = window_update.size.unwrap_or(*size); + *size = window_size; + + window_size.to_physical(scale_factor) + }); + + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(*window_id), + ), + event: WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size: &mut physical_size, + }, + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); + + // We don't update size on a window handle since we'll do that later + // when handling size update. + let new_logical_size = physical_size.to_logical(scale_factor); + window_update.size = Some(new_logical_size); + } + + if let Some(size) = window_update.size.take() { + let physical_size = self.with_state(|state| { + let window_handle = state.window_map.get_mut(&window_id).unwrap(); + let mut window_size = window_handle.size.lock().unwrap(); + + // Always issue resize event on scale factor change. + let physical_size = + if window_update.scale_factor.is_none() && *window_size == size { + // The size hasn't changed, don't inform downstream about that. + None + } else { + *window_size = size; + let scale_factor = + sctk::get_surface_scale_factor(&window_handle.window.surface()); + let physical_size = size.to_physical(scale_factor as f64); + Some(physical_size) + }; + + // We still perform all of those resize related logic even if the size + // hasn't changed, since GNOME relies on `set_geometry` calls after + // configures. + window_handle.window.resize(size.width, size.height); + window_handle.window.refresh(); + + // Mark that refresh isn't required, since we've done it right now. + window_update.refresh_frame = false; + + physical_size + }); + + if let Some(physical_size) = physical_size { + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(*window_id), + ), + event: WindowEvent::Resized(physical_size), + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + } + + if window_update.close_window { + sticky_exit_callback( + Event::WindowEvent { + window_id: crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(*window_id), + ), + event: WindowEvent::CloseRequested, + }, + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + } + + // The purpose of the back buffer and that swap is to not hold borrow_mut when + // we're doing callback to the user, since we can double borrow if the user decides + // to create a window in one of those callbacks. + self.with_state(|state| { + std::mem::swap( + &mut event_sink_back_buffer, + &mut state.event_sink.window_events, + ) + }); + + // Handle pending window events. + for event in event_sink_back_buffer.drain(..) { + let event = event.map_nonuser_event().unwrap(); + sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback); + } + + // Send events cleared. + sticky_exit_callback( + Event::MainEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); + + // Handle RedrawRequested events. + for (window_id, window_update) in window_updates.iter() { + // Handle refresh of the frame. + if window_update.refresh_frame { + self.with_state(|state| { + let window_handle = state.window_map.get_mut(&window_id).unwrap(); + window_handle.window.refresh(); + if !window_update.redraw_requested { + window_handle.window.surface().commit(); + } + }); + } + + // Handle redraw request. + if window_update.redraw_requested { + sticky_exit_callback( + Event::RedrawRequested(crate::window::WindowId( + crate::platform_impl::WindowId::Wayland(*window_id), + )), + &self.window_target, + &mut control_flow, + &mut callback, + ); + } + } + + // Send RedrawEventCleared. + sticky_exit_callback( + Event::RedrawEventsCleared, + &self.window_target, + &mut control_flow, + &mut callback, + ); + + // Send pending events to the server. + let _ = self.display.flush(); + + // During the run of the user callback, some other code monitoring and reading the + // Wayland socket may have been run (mesa for example does this with vsync), if that + // is the case, some events may have been enqueued in our event queue. + // + // If some messages are there, the event loop needs to behave as if it was instantly + // woken up by messages arriving from the Wayland socket, to avoid delaying the + // dispatch of these events until we're woken up again. + let instant_wakeup = { + let handle = self.event_loop.handle(); + let source = self.wayland_source.clone(); + let dispatched = handle.with_source(&source, |wayland_source| { + let queue = wayland_source.queue(); + self.with_state(|state| { + queue.dispatch_pending(state, |_, _, _| unimplemented!()) + }) + }); + + if let Ok(dispatched) = dispatched { + dispatched > 0 + } else { + break; + } + }; + + match control_flow { + ControlFlow::Exit => break, + ControlFlow::Poll => { + // Non-blocking dispatch. + let timeout = Duration::from_millis(0); + if self.loop_dispatch(Some(timeout)).is_err() { + break; + } + + callback( + Event::NewEvents(StartCause::Poll), + &self.window_target, + &mut control_flow, + ); + } + ControlFlow::Wait => { + let timeout = if instant_wakeup { + Some(Duration::from_millis(0)) + } else { + None + }; + + if self.loop_dispatch(timeout).is_err() { + break; + } + + callback( + Event::NewEvents(StartCause::WaitCancelled { + start: Instant::now(), + requested_resume: None, + }), + &self.window_target, + &mut control_flow, + ); + } + ControlFlow::WaitUntil(deadline) => { + let start = Instant::now(); + + // Compute the amount of time we'll block for. + let duration = if deadline > start && !instant_wakeup { + deadline - start + } else { + Duration::from_millis(0) + }; + + if self.loop_dispatch(Some(duration)).is_err() { + break; + } + + let now = Instant::now(); + + if now < deadline { + callback( + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: Some(deadline), + }), + &self.window_target, + &mut control_flow, + ) + } else { + callback( + Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume: deadline, + }), + &self.window_target, + &mut control_flow, + ) + } + } + } + } + + callback(Event::LoopDestroyed, &self.window_target, &mut control_flow); + } + + #[inline] + pub fn create_proxy(&self) -> EventLoopProxy { + EventLoopProxy::new(self.user_events_sender.clone()) + } + + #[inline] + pub fn window_target(&self) -> &RootEventLoopWindowTarget { + &self.window_target + } + + fn with_state U>(&mut self, f: F) -> U { + let state = match &mut self.window_target.p { + crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => { + window_target.state.get_mut() + } + #[cfg(feature = "x11")] + _ => unreachable!(), + }; + + f(state) + } + + fn loop_dispatch>>( + &mut self, + timeout: D, + ) -> std::io::Result<()> { + let mut state = match &mut self.window_target.p { + crate::platform_impl::EventLoopWindowTarget::Wayland(ref mut window_target) => { + window_target.state.get_mut() + } + #[cfg(feature = "x11")] + _ => unreachable!(), + }; + + self.event_loop.dispatch(timeout, &mut state) + } +} diff --git a/src/platform_impl/linux/wayland/event_loop/proxy.rs b/src/platform_impl/linux/wayland/event_loop/proxy.rs new file mode 100644 index 0000000000..dad64ef2c9 --- /dev/null +++ b/src/platform_impl/linux/wayland/event_loop/proxy.rs @@ -0,0 +1,32 @@ +//! An event loop proxy. + +use std::sync::mpsc::SendError; + +use sctk::reexports::calloop::channel::Sender; + +use crate::event_loop::EventLoopClosed; + +/// A handle that can be sent across the threads and used to wake up the `EventLoop`. +pub struct EventLoopProxy { + user_events_sender: Sender, +} + +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + EventLoopProxy { + user_events_sender: self.user_events_sender.clone(), + } + } +} + +impl EventLoopProxy { + pub fn new(user_events_sender: Sender) -> Self { + Self { user_events_sender } + } + + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.user_events_sender + .send(event) + .map_err(|SendError(error)| EventLoopClosed(error)) + } +} diff --git a/src/platform_impl/linux/wayland/event_loop/sink.rs b/src/platform_impl/linux/wayland/event_loop/sink.rs new file mode 100644 index 0000000000..303ab826e4 --- /dev/null +++ b/src/platform_impl/linux/wayland/event_loop/sink.rs @@ -0,0 +1,36 @@ +//! An event loop's sink to deliver events from the Wayland event callbacks. + +use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent}; +use crate::platform_impl::platform::{DeviceId as PlatformDeviceId, WindowId as PlatformWindowId}; +use crate::window::WindowId as RootWindowId; + +use super::{DeviceId, WindowId}; + +/// An event loop's sink to deliver events from the Wayland event callbacks +/// to the winit's user. +#[derive(Default)] +pub struct EventSink { + pub window_events: Vec>, +} + +impl EventSink { + pub fn new() -> Self { + Default::default() + } + + /// Add new device event to a queue. + pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) { + self.window_events.push(Event::DeviceEvent { + event, + device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)), + }); + } + + /// Add new window event to a queue. + pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) { + self.window_events.push(Event::WindowEvent { + event, + window_id: RootWindowId(PlatformWindowId::Wayland(window_id)), + }); + } +} diff --git a/src/platform_impl/linux/wayland/event_loop/state.rs b/src/platform_impl/linux/wayland/event_loop/state.rs new file mode 100644 index 0000000000..7aad9eccd2 --- /dev/null +++ b/src/platform_impl/linux/wayland/event_loop/state.rs @@ -0,0 +1,25 @@ +//! A state that we pass around in a dispatch. + +use std::collections::HashMap; + +use super::EventSink; +use crate::platform_impl::wayland::window::shim::{WindowHandle, WindowUpdate}; +use crate::platform_impl::wayland::WindowId; + +/// Wrapper to carry winit's state. +pub struct WinitState { + /// A sink for window and device events that is being filled during dispatching + /// event loop and forwarded downstream afterwards. + pub event_sink: EventSink, + + /// Window updates, which are coming from SCTK or the compositor, which require + /// calling back to the winit's downstream. They are handled right in the event loop, + /// unlike the ones coming from buffers on the `WindowHandle`'s. + pub window_updates: HashMap, + + /// Window map containing all SCTK windows. Since those windows aren't allowed + /// to be sent to other threads, they live on the event loop's thread + /// and requests from winit's windows are being forwarded to them either via + /// `WindowUpdate` or buffer on the associated with it `WindowHandle`. + pub window_map: HashMap, +} diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs deleted file mode 100644 index 7c58e7c42e..0000000000 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ /dev/null @@ -1,396 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use super::{event_loop::EventsSink, make_wid, DeviceId}; -use smithay_client_toolkit::{ - keyboard::{ - self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind, - }, - reexports::client::protocol::{wl_keyboard, wl_seat}, -}; - -use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}; - -pub fn init_keyboard( - seat: &wl_seat::WlSeat, - sink: EventsSink, - modifiers_tracker: Arc>, -) -> wl_keyboard::WlKeyboard { - // { variables to be captured by the closures - let target = Arc::new(Mutex::new(None)); - let my_sink = sink.clone(); - let repeat_sink = sink.clone(); - let repeat_target = target.clone(); - let my_modifiers = modifiers_tracker.clone(); - // } - let ret = map_keyboard_auto_with_repeat( - seat, - KeyRepeatKind::System, - move |evt: KbEvent<'_>, _| { - match evt { - KbEvent::Enter { surface, .. } => { - let wid = make_wid(&surface); - my_sink.send_window_event(WindowEvent::Focused(true), wid); - *target.lock().unwrap() = Some(wid); - - let modifiers = *modifiers_tracker.lock().unwrap(); - - if !modifiers.is_empty() { - my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid); - } - } - KbEvent::Leave { surface, .. } => { - let wid = make_wid(&surface); - let modifiers = *modifiers_tracker.lock().unwrap(); - - if !modifiers.is_empty() { - my_sink.send_window_event( - WindowEvent::ModifiersChanged(ModifiersState::empty()), - wid, - ); - } - - my_sink.send_window_event(WindowEvent::Focused(false), wid); - *target.lock().unwrap() = None; - } - KbEvent::Key { - rawkey, - keysym, - state, - utf8, - .. - } => { - if let Some(wid) = *target.lock().unwrap() { - let state = match state { - wl_keyboard::KeyState::Pressed => ElementState::Pressed, - wl_keyboard::KeyState::Released => ElementState::Released, - _ => unreachable!(), - }; - let vkcode = key_to_vkey(rawkey, keysym); - my_sink.send_window_event( - #[allow(deprecated)] - WindowEvent::KeyboardInput { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - input: KeyboardInput { - state, - scancode: rawkey, - virtual_keycode: vkcode, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - is_synthetic: false, - }, - wid, - ); - // send char event only on key press, not release - if let ElementState::Released = state { - return; - } - if let Some(txt) = utf8 { - for chr in txt.chars() { - my_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid); - } - } - } - } - KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ } - KbEvent::Modifiers { - modifiers: event_modifiers, - } => { - let modifiers = ModifiersState::from_wayland(event_modifiers); - - *modifiers_tracker.lock().unwrap() = modifiers; - - if let Some(wid) = *target.lock().unwrap() { - my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid); - } - } - } - }, - move |repeat_event: KeyRepeatEvent, _| { - if let Some(wid) = *repeat_target.lock().unwrap() { - let state = ElementState::Pressed; - let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym); - repeat_sink.send_window_event( - #[allow(deprecated)] - WindowEvent::KeyboardInput { - device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( - DeviceId, - )), - input: KeyboardInput { - state, - scancode: repeat_event.rawkey, - virtual_keycode: vkcode, - modifiers: my_modifiers.lock().unwrap().clone(), - }, - is_synthetic: false, - }, - wid, - ); - if let Some(txt) = repeat_event.utf8 { - for chr in txt.chars() { - repeat_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid); - } - } - } - }, - ); - - match ret { - Ok(keyboard) => keyboard, - Err(_) => { - // This is a fallback impl if libxkbcommon was not available - // This case should probably never happen, as most wayland - // compositors _need_ libxkbcommon anyway... - // - // In this case, we don't have the keymap information (it is - // supposed to be serialized by the compositor using libxkbcommon) - - seat.get_keyboard(|keyboard| { - // { variables to be captured by the closure - let mut target = None; - let my_sink = sink; - // } - - keyboard.implement_closure( - move |evt, _| match evt { - wl_keyboard::Event::Enter { surface, .. } => { - let wid = make_wid(&surface); - my_sink.send_window_event(WindowEvent::Focused(true), wid); - target = Some(wid); - } - wl_keyboard::Event::Leave { surface, .. } => { - let wid = make_wid(&surface); - my_sink.send_window_event(WindowEvent::Focused(false), wid); - target = None; - } - wl_keyboard::Event::Key { key, state, .. } => { - if let Some(wid) = target { - let state = match state { - wl_keyboard::KeyState::Pressed => ElementState::Pressed, - wl_keyboard::KeyState::Released => ElementState::Released, - _ => unreachable!(), - }; - my_sink.send_window_event( - #[allow(deprecated)] - WindowEvent::KeyboardInput { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - input: KeyboardInput { - state, - scancode: key, - virtual_keycode: None, - modifiers: ModifiersState::default(), - }, - is_synthetic: false, - }, - wid, - ); - } - } - _ => (), - }, - (), - ) - }) - .unwrap() - } - } -} - -fn key_to_vkey(rawkey: u32, keysym: u32) -> Option { - match rawkey { - 1 => Some(VirtualKeyCode::Escape), - 2 => Some(VirtualKeyCode::Key1), - 3 => Some(VirtualKeyCode::Key2), - 4 => Some(VirtualKeyCode::Key3), - 5 => Some(VirtualKeyCode::Key4), - 6 => Some(VirtualKeyCode::Key5), - 7 => Some(VirtualKeyCode::Key6), - 8 => Some(VirtualKeyCode::Key7), - 9 => Some(VirtualKeyCode::Key8), - 10 => Some(VirtualKeyCode::Key9), - 11 => Some(VirtualKeyCode::Key0), - _ => keysym_to_vkey(keysym), - } -} - -fn keysym_to_vkey(keysym: u32) -> Option { - use smithay_client_toolkit::keyboard::keysyms; - match keysym { - // letters - keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A), - keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B), - keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C), - keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D), - keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E), - keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F), - keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G), - keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H), - keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I), - keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J), - keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K), - keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L), - keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M), - keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N), - keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O), - keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P), - keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q), - keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R), - keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S), - keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T), - keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U), - keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V), - keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W), - keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X), - keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y), - keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z), - // F-- - keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1), - keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2), - keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3), - keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4), - keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5), - keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6), - keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7), - keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8), - keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9), - keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10), - keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11), - keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12), - keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13), - keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14), - keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15), - keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16), - keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17), - keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18), - keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19), - keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20), - keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21), - keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22), - keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23), - keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24), - // flow control - keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot), - keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll), - keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause), - keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert), - keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete), - keysyms::XKB_KEY_End => Some(VirtualKeyCode::End), - keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp), - // arrows - keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left), - keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up), - keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right), - keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down), - // - keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back), - keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return), - keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space), - // keypad - keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock), - keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0), - keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1), - keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2), - keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3), - keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4), - keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5), - keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6), - keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7), - keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8), - keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9), - // misc - // => Some(VirtualKeyCode::AbntC1), - // => Some(VirtualKeyCode::AbntC2), - keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus), - keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe), - // => Some(VirtualKeyCode::Apps), - // => Some(VirtualKeyCode::At), - // => Some(VirtualKeyCode::Ax), - keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash), - // => Some(VirtualKeyCode::Calculator), - // => Some(VirtualKeyCode::Capital), - keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon), - keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma), - // => Some(VirtualKeyCode::Convert), - keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals), - // => Some(VirtualKeyCode::Grave), - // => Some(VirtualKeyCode::Kana), - // => Some(VirtualKeyCode::Kanji), - keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt), - // => Some(VirtualKeyCode::LBracket), - keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl), - keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift), - // => Some(VirtualKeyCode::LWin), - // => Some(VirtualKeyCode::Mail), - // => Some(VirtualKeyCode::MediaSelect), - // => Some(VirtualKeyCode::MediaStop), - keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus), - keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk), - // => Some(VirtualKeyCode::Mute), - // => Some(VirtualKeyCode::MyComputer), - // => Some(VirtualKeyCode::NextTrack), - // => Some(VirtualKeyCode::NoConvert), - keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), - keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), - keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), - keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd), - keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), - keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply), - keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal), - keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide), - keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), - keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End), - // => Some(VirtualKeyCode::OEM102), - // => Some(VirtualKeyCode::Period), - // => Some(VirtualKeyCode::Playpause), - // => Some(VirtualKeyCode::Power), - // => Some(VirtualKeyCode::Prevtrack), - keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt), - // => Some(VirtualKeyCode::RBracket), - keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl), - keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift), - // => Some(VirtualKeyCode::RWin), - keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon), - keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash), - // => Some(VirtualKeyCode::Sleep), - // => Some(VirtualKeyCode::Stop), - // => Some(VirtualKeyCode::Sysrq), - keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab), - keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), - // => Some(VirtualKeyCode::Underline), - // => Some(VirtualKeyCode::Unlabeled), - keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown), - keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp), - // => Some(VirtualKeyCode::Wake), - // => Some(VirtualKeyCode::Webback), - // => Some(VirtualKeyCode::WebFavorites), - // => Some(VirtualKeyCode::WebForward), - // => Some(VirtualKeyCode::WebHome), - // => Some(VirtualKeyCode::WebRefresh), - // => Some(VirtualKeyCode::WebSearch), - // => Some(VirtualKeyCode::WebStop), - // => Some(VirtualKeyCode::Yen), - keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy), - keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste), - keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut), - // fallback - _ => None, - } -} - -impl ModifiersState { - pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState { - let mut m = ModifiersState::empty(); - m.set(ModifiersState::SHIFT, mods.shift); - m.set(ModifiersState::CTRL, mods.ctrl); - m.set(ModifiersState::ALT, mods.alt); - m.set(ModifiersState::LOGO, mods.logo); - m - } -} diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index 7fdb3fd1ed..a63a6f1ef0 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -1,17 +1,21 @@ -#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", - target_os = "netbsd", target_os = "openbsd"))] +#![cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] -pub use self::{ - event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode}, - window::Window, -}; +use sctk::reexports::client::protocol::wl_surface::WlSurface; -use smithay_client_toolkit::reexports::client::protocol::wl_surface; +pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; +pub use output::{MonitorHandle, VideoMode}; +pub use window::Window; +mod env; mod event_loop; -mod keyboard; -mod pointer; -mod touch; +mod output; +mod seat; mod window; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -33,6 +37,6 @@ impl WindowId { } #[inline] -fn make_wid(s: &wl_surface::WlSurface) -> WindowId { - WindowId(s.as_ref().c_ptr() as usize) +fn make_wid(surface: &WlSurface) -> WindowId { + WindowId(surface.as_ref().c_ptr() as usize) } diff --git a/src/platform_impl/linux/wayland/output.rs b/src/platform_impl/linux/wayland/output.rs new file mode 100644 index 0000000000..fc4d08737b --- /dev/null +++ b/src/platform_impl/linux/wayland/output.rs @@ -0,0 +1,240 @@ +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; + +use sctk::reexports::client::protocol::wl_output::WlOutput; +use sctk::reexports::client::Display; + +use sctk::environment::Environment; +use sctk::output::OutputStatusListener; + +use crate::dpi::{PhysicalPosition, PhysicalSize}; +use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}; +use crate::platform_impl::platform::{ + MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode, +}; + +use super::env::WinitEnv; +use super::event_loop::EventLoopWindowTarget; + +/// Output manager. +pub struct OutputManager { + /// A handle that actually performs all operations on outputs. + handle: OutputManagerHandle, + + _output_listener: OutputStatusListener, +} + +impl OutputManager { + pub fn new(env: &Environment) -> Self { + let handle = OutputManagerHandle::new(); + + // Handle existing outputs. + for output in env.get_all_outputs() { + match sctk::output::with_output_info(&output, |info| info.obsolete) { + Some(false) => (), + // The output is obsolete or we've failed to access its data, skipping. + _ => continue, + } + + // The output is present and unusable, add it to the output manager manager. + handle.add_output(output); + } + + let handle_for_listener = handle.clone(); + + let output_listener = env.listen_for_outputs(move |output, info, _| { + if info.obsolete { + handle_for_listener.remove_output(output) + } else { + handle_for_listener.add_output(output) + } + }); + + Self { + handle, + _output_listener: output_listener, + } + } + + pub fn handle(&self) -> OutputManagerHandle { + self.handle.clone() + } +} + +/// A handle to output manager. +#[derive(Debug, Clone)] +pub struct OutputManagerHandle { + outputs: Arc>>, +} + +impl OutputManagerHandle { + fn new() -> Self { + let outputs = Arc::new(Mutex::new(VecDeque::new())); + Self { outputs } + } + + /// Handle addition of the output. + fn add_output(&self, output: WlOutput) { + let mut outputs = self.outputs.lock().unwrap(); + let position = outputs.iter().position(|handle| handle.proxy == output); + if position.is_none() { + outputs.push_back(MonitorHandle::new(output)); + } + } + + /// Handle removal of the output. + fn remove_output(&self, output: WlOutput) { + let mut outputs = self.outputs.lock().unwrap(); + let position = outputs.iter().position(|handle| handle.proxy == output); + if let Some(position) = position { + outputs.remove(position); + } + } + + /// Get all observed outputs. + pub fn available_outputs(&self) -> VecDeque { + self.outputs.lock().unwrap().clone() + } +} + +#[derive(Clone, Debug)] +pub struct MonitorHandle { + pub(crate) proxy: WlOutput, +} + +impl PartialEq for MonitorHandle { + fn eq(&self, other: &Self) -> bool { + self.native_identifier() == other.native_identifier() + } +} + +impl Eq for MonitorHandle {} + +impl PartialOrd for MonitorHandle { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(&other)) + } +} + +impl Ord for MonitorHandle { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.native_identifier().cmp(&other.native_identifier()) + } +} + +impl std::hash::Hash for MonitorHandle { + fn hash(&self, state: &mut H) { + self.native_identifier().hash(state); + } +} + +impl MonitorHandle { + #[inline] + pub(crate) fn new(proxy: WlOutput) -> Self { + Self { proxy } + } + + #[inline] + pub fn name(&self) -> Option { + sctk::output::with_output_info(&self.proxy, |info| { + format!("{} ({})", info.model, info.make) + }) + } + + #[inline] + pub fn native_identifier(&self) -> u32 { + sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0) + } + + #[inline] + pub fn size(&self) -> PhysicalSize { + match sctk::output::with_output_info(&self.proxy, |info| { + info.modes + .iter() + .find(|mode| mode.is_current) + .map(|mode| mode.dimensions) + }) { + Some(Some((w, h))) => (w as u32, h as u32), + _ => (0, 0), + } + .into() + } + + #[inline] + pub fn position(&self) -> PhysicalPosition { + sctk::output::with_output_info(&self.proxy, |info| info.location) + .unwrap_or((0, 0)) + .into() + } + + #[inline] + pub fn scale_factor(&self) -> i32 { + sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1) + } + + #[inline] + pub fn video_modes(&self) -> impl Iterator { + let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone()) + .unwrap_or_else(Vec::new); + + let monitor = self.clone(); + + modes.into_iter().map(move |mode| RootVideoMode { + video_mode: PlatformVideoMode::Wayland(VideoMode { + size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(), + refresh_rate: (mode.refresh_rate as f32 / 1000.0).round() as u16, + bit_depth: 32, + monitor: monitor.clone(), + }), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct VideoMode { + pub(crate) size: PhysicalSize, + pub(crate) bit_depth: u16, + pub(crate) refresh_rate: u16, + pub(crate) monitor: MonitorHandle, +} + +impl VideoMode { + #[inline] + pub fn size(&self) -> PhysicalSize { + self.size + } + + #[inline] + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + #[inline] + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> RootMonitorHandle { + RootMonitorHandle { + inner: PlatformMonitorHandle::Wayland(self.monitor.clone()), + } + } +} + +impl EventLoopWindowTarget { + #[inline] + pub fn display(&self) -> &Display { + &self.display + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + self.output_manager.handle.available_outputs() + } + + #[inline] + pub fn primary_monitor(&self) -> Option { + // There's no primary monitor on Wayland. + None + } +} diff --git a/src/platform_impl/linux/wayland/pointer.rs b/src/platform_impl/linux/wayland/pointer.rs deleted file mode 100644 index 4f2f4c2eaa..0000000000 --- a/src/platform_impl/linux/wayland/pointer.rs +++ /dev/null @@ -1,290 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use crate::dpi::LogicalPosition; -use crate::event::{ - DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, - WindowEvent, -}; - -use super::{ - event_loop::{CursorManager, EventsSink}, - make_wid, - window::WindowStore, - DeviceId, -}; - -use smithay_client_toolkit::surface; - -use smithay_client_toolkit::reexports::client::protocol::{ - wl_pointer::{self, Event as PtrEvent, WlPointer}, - wl_seat, -}; - -use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{ - zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, zwp_relative_pointer_v1::Event, - zwp_relative_pointer_v1::ZwpRelativePointerV1, -}; - -use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{ - zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::Lifetime, - zwp_pointer_constraints_v1::ZwpPointerConstraintsV1, -}; - -use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface; - -pub fn implement_pointer( - seat: &wl_seat::WlSeat, - sink: EventsSink, - store: Arc>, - modifiers_tracker: Arc>, - cursor_manager: Arc>, -) -> WlPointer { - seat.get_pointer(|pointer| { - // Currently focused winit surface - let mut mouse_focus = None; - let mut axis_buffer = None; - let mut axis_discrete_buffer = None; - let mut axis_state = TouchPhase::Ended; - - pointer.implement_closure( - move |evt, pointer| { - let store = store.lock().unwrap(); - let mut cursor_manager = cursor_manager.lock().unwrap(); - match evt { - PtrEvent::Enter { - surface, - surface_x, - surface_y, - .. - } => { - let wid = store.find_wid(&surface); - - if let Some(wid) = wid { - let scale_factor = surface::get_dpi_factor(&surface) as f64; - mouse_focus = Some(surface); - - // Reload cursor style only when we enter winit's surface. Calling - // this function every time on `PtrEvent::Enter` could interfere with - // SCTK CSD handling, since it changes cursor icons when you hover - // cursor over the window borders. - cursor_manager.reload_cursor_style(); - - sink.send_window_event( - WindowEvent::CursorEntered { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - }, - wid, - ); - - let position = LogicalPosition::new(surface_x, surface_y) - .to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::CursorMoved { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - position, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } - } - PtrEvent::Leave { surface, .. } => { - mouse_focus = None; - let wid = store.find_wid(&surface); - if let Some(wid) = wid { - sink.send_window_event( - WindowEvent::CursorLeft { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - }, - wid, - ); - } - } - PtrEvent::Motion { - surface_x, - surface_y, - .. - } => { - if let Some(surface) = mouse_focus.as_ref() { - let wid = make_wid(surface); - - let scale_factor = surface::get_dpi_factor(&surface) as f64; - let position = LogicalPosition::new(surface_x, surface_y) - .to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::CursorMoved { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - position, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } - } - PtrEvent::Button { button, state, .. } => { - if let Some(surface) = mouse_focus.as_ref() { - let state = match state { - wl_pointer::ButtonState::Pressed => ElementState::Pressed, - wl_pointer::ButtonState::Released => ElementState::Released, - _ => unreachable!(), - }; - let button = match button { - 0x110 => MouseButton::Left, - 0x111 => MouseButton::Right, - 0x112 => MouseButton::Middle, - // TODO figure out the translation ? - _ => return, - }; - sink.send_window_event( - WindowEvent::MouseInput { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - state, - button, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - make_wid(surface), - ); - } - } - PtrEvent::Axis { axis, value, .. } => { - if let Some(surface) = mouse_focus.as_ref() { - let wid = make_wid(surface); - if pointer.as_ref().version() < 5 { - let (mut x, mut y) = (0.0, 0.0); - // old seat compatibility - match axis { - // wayland vertical sign convention is the inverse of winit - wl_pointer::Axis::VerticalScroll => y -= value as f32, - wl_pointer::Axis::HorizontalScroll => x += value as f32, - _ => unreachable!(), - } - let scale_factor = surface::get_dpi_factor(&surface) as f64; - let delta = LogicalPosition::new(x as f64, y as f64) - .to_physical(scale_factor); - sink.send_window_event( - WindowEvent::MouseWheel { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - delta: MouseScrollDelta::PixelDelta(delta), - phase: TouchPhase::Moved, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } else { - let (mut x, mut y) = axis_buffer.unwrap_or((0.0, 0.0)); - match axis { - // wayland vertical sign convention is the inverse of winit - wl_pointer::Axis::VerticalScroll => y -= value as f32, - wl_pointer::Axis::HorizontalScroll => x += value as f32, - _ => unreachable!(), - } - axis_buffer = Some((x, y)); - axis_state = match axis_state { - TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, - _ => TouchPhase::Started, - } - } - } - } - PtrEvent::Frame => { - let axis_buffer = axis_buffer.take(); - let axis_discrete_buffer = axis_discrete_buffer.take(); - if let Some(surface) = mouse_focus.as_ref() { - let wid = make_wid(surface); - if let Some((x, y)) = axis_discrete_buffer { - sink.send_window_event( - WindowEvent::MouseWheel { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - delta: MouseScrollDelta::LineDelta(x, y), - phase: axis_state, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } else if let Some((x, y)) = axis_buffer { - let scale_factor = surface::get_dpi_factor(&surface) as f64; - let delta = LogicalPosition::new(x, y).to_physical(scale_factor); - sink.send_window_event( - WindowEvent::MouseWheel { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - delta: MouseScrollDelta::PixelDelta(delta), - phase: axis_state, - modifiers: modifiers_tracker.lock().unwrap().clone(), - }, - wid, - ); - } - } - } - PtrEvent::AxisSource { .. } => (), - PtrEvent::AxisStop { .. } => { - axis_state = TouchPhase::Ended; - } - PtrEvent::AxisDiscrete { axis, discrete } => { - let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0.0, 0.0)); - match axis { - // wayland vertical sign convention is the inverse of winit - wl_pointer::Axis::VerticalScroll => y -= discrete as f32, - wl_pointer::Axis::HorizontalScroll => x += discrete as f32, - _ => unreachable!(), - } - axis_discrete_buffer = Some((x, y)); - axis_state = match axis_state { - TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, - _ => TouchPhase::Started, - } - } - _ => unreachable!(), - } - }, - (), - ) - }) - .unwrap() -} - -pub fn implement_relative_pointer( - sink: EventsSink, - pointer: &WlPointer, - manager: &ZwpRelativePointerManagerV1, -) -> Result { - manager.get_relative_pointer(pointer, |rel_pointer| { - rel_pointer.implement_closure( - move |evt, _rel_pointer| match evt { - Event::RelativeMotion { dx, dy, .. } => { - sink.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId) - } - _ => unreachable!(), - }, - (), - ) - }) -} - -pub fn implement_locked_pointer( - surface: &WlSurface, - pointer: &WlPointer, - constraints: &ZwpPointerConstraintsV1, -) -> Result { - constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent.to_raw(), |c| { - c.implement_closure(|_, _| (), ()) - }) -} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs new file mode 100644 index 0000000000..7c320973b8 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs @@ -0,0 +1,151 @@ +//! Handling of various keyboard events. + +use sctk::reexports::client::protocol::wl_keyboard::KeyState; + +use sctk::seat::keyboard::Event as KeyboardEvent; + +use crate::event::{ElementState, KeyboardInput, ModifiersState, WindowEvent}; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::{self, DeviceId}; + +use super::keymap; +use super::KeyboardInner; + +#[inline] +pub(super) fn handle_keyboard( + event: KeyboardEvent<'_>, + inner: &mut KeyboardInner, + winit_state: &mut WinitState, +) { + let event_sink = &mut winit_state.event_sink; + match event { + KeyboardEvent::Enter { surface, .. } => { + let window_id = wayland::make_wid(&surface); + + // Window gained focus. + event_sink.push_window_event(WindowEvent::Focused(true), window_id); + + // Dispatch modifers changes that we've received before getting `Enter` event. + if let Some(modifiers) = inner.pending_modifers_state.take() { + *inner.modifiers_state.borrow_mut() = modifiers; + event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id); + } + + inner.target_window_id = Some(window_id); + } + KeyboardEvent::Leave { surface, .. } => { + let window_id = wayland::make_wid(&surface); + + // Notify that no modifiers are being pressed. + if !inner.modifiers_state.borrow().is_empty() { + event_sink.push_window_event( + WindowEvent::ModifiersChanged(ModifiersState::empty()), + window_id, + ); + } + + // Window lost focus. + event_sink.push_window_event(WindowEvent::Focused(false), window_id); + + // Reset the id. + inner.target_window_id = None; + } + KeyboardEvent::Key { + rawkey, + keysym, + state, + utf8, + .. + } => { + let window_id = match inner.target_window_id { + Some(window_id) => window_id, + None => return, + }; + + let state = match state { + KeyState::Pressed => ElementState::Pressed, + KeyState::Released => ElementState::Released, + _ => unreachable!(), + }; + + let virtual_keycode = keymap::keysym_to_vkey(keysym); + + event_sink.push_window_event( + #[allow(deprecated)] + WindowEvent::KeyboardInput { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + input: KeyboardInput { + state, + scancode: rawkey, + virtual_keycode, + modifiers: *inner.modifiers_state.borrow(), + }, + is_synthetic: false, + }, + window_id, + ); + + // Send ReceivedCharacter event only on ElementState::Pressed. + if ElementState::Released == state { + return; + } + + if let Some(txt) = utf8 { + for ch in txt.chars() { + event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); + } + } + } + KeyboardEvent::Repeat { + rawkey, + keysym, + utf8, + .. + } => { + let window_id = match inner.target_window_id { + Some(window_id) => window_id, + None => return, + }; + + let virtual_keycode = keymap::keysym_to_vkey(keysym); + + event_sink.push_window_event( + #[allow(deprecated)] + WindowEvent::KeyboardInput { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + input: KeyboardInput { + state: ElementState::Pressed, + scancode: rawkey, + virtual_keycode, + modifiers: *inner.modifiers_state.borrow(), + }, + is_synthetic: false, + }, + window_id, + ); + + if let Some(txt) = utf8 { + for ch in txt.chars() { + event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); + } + } + } + KeyboardEvent::Modifiers { modifiers } => { + let modifiers = ModifiersState::from(modifiers); + if let Some(window_id) = inner.target_window_id { + *inner.modifiers_state.borrow_mut() = modifiers; + + event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id); + } else { + // Compositor must send modifiers after wl_keyboard::enter, however certain + // compositors are still sending it before, so stash such events and send + // them on wl_keyboard::enter. + inner.pending_modifers_state = Some(modifiers); + } + } + } +} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs new file mode 100644 index 0000000000..717a68dfd9 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs @@ -0,0 +1,188 @@ +//! Convert Wayland keys to winit keys. + +use crate::event::VirtualKeyCode; + +pub fn keysym_to_vkey(keysym: u32) -> Option { + use sctk::seat::keyboard::keysyms; + match keysym { + // Numbers. + keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1), + keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2), + keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3), + keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4), + keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5), + keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6), + keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7), + keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8), + keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9), + keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0), + // Letters. + keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A), + keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B), + keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C), + keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D), + keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E), + keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F), + keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G), + keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H), + keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I), + keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J), + keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K), + keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L), + keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M), + keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N), + keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O), + keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P), + keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q), + keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R), + keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S), + keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T), + keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U), + keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V), + keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W), + keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X), + keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y), + keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z), + // Escape. + keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape), + // Function keys. + keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1), + keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2), + keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3), + keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4), + keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5), + keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6), + keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7), + keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8), + keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9), + keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10), + keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11), + keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12), + keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13), + keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14), + keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15), + keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16), + keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17), + keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18), + keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19), + keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20), + keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21), + keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22), + keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23), + keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24), + // Flow control. + keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot), + keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll), + keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause), + keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert), + keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home), + keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete), + keysyms::XKB_KEY_End => Some(VirtualKeyCode::End), + keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown), + keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp), + // Arrows. + keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left), + keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up), + keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right), + keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down), + + keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back), + keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return), + keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space), + + keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose), + keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret), + + // Keypad. + keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock), + keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0), + keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1), + keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2), + keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3), + keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4), + keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5), + keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6), + keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7), + keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8), + keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9), + // Misc. + // => Some(VirtualKeyCode::AbntC1), + // => Some(VirtualKeyCode::AbntC2), + keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus), + keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe), + // => Some(VirtualKeyCode::Apps), + keysyms::XKB_KEY_at => Some(VirtualKeyCode::At), + // => Some(VirtualKeyCode::Ax), + keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash), + keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator), + // => Some(VirtualKeyCode::Capital), + keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon), + keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma), + // => Some(VirtualKeyCode::Convert), + keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals), + keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave), + // => Some(VirtualKeyCode::Kana), + keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji), + keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt), + keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket), + keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl), + keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift), + keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin), + keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail), + // => Some(VirtualKeyCode::MediaSelect), + // => Some(VirtualKeyCode::MediaStop), + keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus), + keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk), + keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute), + // => Some(VirtualKeyCode::MyComputer), + keysyms::XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack), + // => Some(VirtualKeyCode::NoConvert), + keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), + keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), + keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), + keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd), + keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), + keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply), + keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide), + keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal), + keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), + keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), + keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), + keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End), + // => Some(VirtualKeyCode::OEM102), + keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period), + // => Some(VirtualKeyCode::Playpause), + keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power), + keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack), + keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt), + keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket), + keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl), + keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift), + keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin), + keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon), + keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash), + keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep), + // => Some(VirtualKeyCode::Stop), + // => Some(VirtualKeyCode::Sysrq), + keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab), + keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), + keysyms::XKB_KEY_underscore => Some(VirtualKeyCode::Underline), + // => Some(VirtualKeyCode::Unlabeled), + keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown), + keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp), + // => Some(VirtualKeyCode::Wake), + // => Some(VirtualKeyCode::Webback), + // => Some(VirtualKeyCode::WebFavorites), + // => Some(VirtualKeyCode::WebForward), + // => Some(VirtualKeyCode::WebHome), + // => Some(VirtualKeyCode::WebRefresh), + // => Some(VirtualKeyCode::WebSearch), + // => Some(VirtualKeyCode::WebStop), + keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen), + keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy), + keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste), + keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut), + // Fallback. + _ => None, + } +} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs new file mode 100644 index 0000000000..1362dcf797 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -0,0 +1,105 @@ +//! Wayland keyboard handling. + +use std::cell::RefCell; +use std::rc::Rc; + +use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::Attached; + +use sctk::reexports::calloop::{LoopHandle, Source}; + +use sctk::seat::keyboard::{self, RepeatSource}; + +use crate::event::ModifiersState; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::WindowId; + +mod handlers; +mod keymap; + +pub(crate) struct Keyboard { + pub keyboard: WlKeyboard, + + /// The source for repeat keys. + pub repeat_source: Option>, + + /// LoopHandle to drop `RepeatSource`, when dropping the keyboard. + pub loop_handle: LoopHandle, +} + +impl Keyboard { + pub fn new( + seat: &Attached, + loop_handle: LoopHandle, + modifiers_state: Rc>, + ) -> Option { + let mut inner = KeyboardInner::new(modifiers_state); + let keyboard_data = keyboard::map_keyboard_repeat( + loop_handle.clone(), + &seat, + None, + keyboard::RepeatKind::System, + move |event, _, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_keyboard(event, &mut inner, winit_state); + }, + ); + + let (keyboard, repeat_source) = keyboard_data.ok()?; + + Some(Self { + keyboard, + loop_handle, + repeat_source: Some(repeat_source), + }) + } +} + +impl Drop for Keyboard { + fn drop(&mut self) { + if self.keyboard.as_ref().version() >= 3 { + self.keyboard.release(); + } + + if let Some(repeat_source) = self.repeat_source.take() { + self.loop_handle.remove(repeat_source); + } + } +} + +struct KeyboardInner { + /// Currently focused surface. + target_window_id: Option, + + /// A pending state of modifiers. + /// + /// This state is getting set if we've got a modifiers update + /// before `Enter` event, which shouldn't happen in general, however + /// some compositors are still doing so. + pending_modifers_state: Option, + + /// Current state of modifiers keys. + modifiers_state: Rc>, +} + +impl KeyboardInner { + fn new(modifiers_state: Rc>) -> Self { + Self { + target_window_id: None, + pending_modifers_state: None, + modifiers_state, + } + } +} + +impl From for ModifiersState { + fn from(mods: keyboard::ModifiersState) -> ModifiersState { + let mut wl_mods = ModifiersState::empty(); + wl_mods.set(ModifiersState::SHIFT, mods.shift); + wl_mods.set(ModifiersState::CTRL, mods.ctrl); + wl_mods.set(ModifiersState::ALT, mods.alt); + wl_mods.set(ModifiersState::LOGO, mods.logo); + wl_mods + } +} diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs new file mode 100644 index 0000000000..23098d08c8 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -0,0 +1,208 @@ +//! Seat handling and managing. + +use std::cell::RefCell; +use std::rc::Rc; + +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; + +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::Attached; + +use sctk::environment::Environment; +use sctk::reexports::calloop::LoopHandle; +use sctk::seat::pointer::ThemeManager; +use sctk::seat::{SeatData, SeatListener}; + +use super::env::WinitEnv; +use super::event_loop::WinitState; +use crate::event::ModifiersState; + +mod keyboard; +pub mod pointer; +pub mod text_input; +mod touch; + +use keyboard::Keyboard; +use pointer::Pointers; +use text_input::TextInput; +use touch::Touch; + +pub struct SeatManager { + /// Listener for seats. + _seat_listener: SeatListener, +} + +impl SeatManager { + pub fn new( + env: &Environment, + loop_handle: LoopHandle, + theme_manager: ThemeManager, + ) -> Self { + let relative_pointer_manager = env.get_global::(); + let pointer_constraints = env.get_global::(); + let text_input_manager = env.get_global::(); + + let mut inner = SeatManagerInner::new( + theme_manager, + relative_pointer_manager, + pointer_constraints, + text_input_manager, + loop_handle, + ); + + // Handle existing seats. + for seat in env.get_all_seats() { + let seat_data = match sctk::seat::clone_seat_data(&seat) { + Some(seat_data) => seat_data, + None => continue, + }; + + inner.process_seat_update(&seat, &seat_data); + } + + let seat_listener = env.listen_for_seats(move |seat, seat_data, _| { + inner.process_seat_update(&seat, &seat_data); + }); + + Self { + _seat_listener: seat_listener, + } + } +} + +/// Inner state of the seat manager. +struct SeatManagerInner { + /// Currently observed seats. + seats: Vec, + + /// Loop handle. + loop_handle: LoopHandle, + + /// Relative pointer manager. + relative_pointer_manager: Option>, + + /// Pointer constraints. + pointer_constraints: Option>, + + /// Text input manager. + text_input_manager: Option>, + + /// A theme manager. + theme_manager: ThemeManager, +} + +impl SeatManagerInner { + fn new( + theme_manager: ThemeManager, + relative_pointer_manager: Option>, + pointer_constraints: Option>, + text_input_manager: Option>, + loop_handle: LoopHandle, + ) -> Self { + Self { + seats: Vec::new(), + loop_handle, + relative_pointer_manager, + pointer_constraints, + text_input_manager, + theme_manager, + } + } + + /// Handle seats update from the `SeatListener`. + pub fn process_seat_update(&mut self, seat: &Attached, seat_data: &SeatData) { + let detached_seat = seat.detach(); + + let position = self.seats.iter().position(|si| si.seat == detached_seat); + let index = position.unwrap_or_else(|| { + self.seats.push(SeatInfo::new(detached_seat)); + self.seats.len() - 1 + }); + + let seat_info = &mut self.seats[index]; + + // Pointer handling. + if seat_data.has_pointer && !seat_data.defunct { + if seat_info.pointer.is_none() { + seat_info.pointer = Some(Pointers::new( + &seat, + &self.theme_manager, + &self.relative_pointer_manager, + &self.pointer_constraints, + seat_info.modifiers_state.clone(), + )); + } + } else { + seat_info.pointer = None; + } + + // Handle keyboard. + if seat_data.has_keyboard && !seat_data.defunct { + if seat_info.keyboard.is_none() { + seat_info.keyboard = Keyboard::new( + &seat, + self.loop_handle.clone(), + seat_info.modifiers_state.clone(), + ); + } + } else { + seat_info.keyboard = None; + } + + // Handle touch. + if seat_data.has_touch && !seat_data.defunct { + if seat_info.touch.is_none() { + seat_info.touch = Some(Touch::new(&seat)); + } + } else { + seat_info.touch = None; + } + + // Handle text input. + if let Some(text_input_manager) = self.text_input_manager.as_ref() { + if seat_data.defunct { + seat_info.text_input = None; + } else if seat_info.text_input.is_none() { + seat_info.text_input = Some(TextInput::new(&seat, &text_input_manager)); + } + } + } +} + +/// Resources associtated with a given seat. +struct SeatInfo { + /// Seat to which this `SeatInfo` belongs. + seat: WlSeat, + + /// A keyboard handle with its repeat rate handling. + keyboard: Option, + + /// All pointers we're using on a seat. + pointer: Option, + + /// Touch handling. + touch: Option, + + /// Text input handling aka IME. + text_input: Option, + + /// The current state of modifiers observed in keyboard handler. + /// + /// We keep modifiers state on a seat, since it's being used by pointer events as well. + modifiers_state: Rc>, +} + +impl SeatInfo { + pub fn new(seat: WlSeat) -> Self { + Self { + seat, + keyboard: None, + pointer: None, + touch: None, + text_input: None, + modifiers_state: Rc::new(RefCell::new(ModifiersState::default())), + } + } +} diff --git a/src/platform_impl/linux/wayland/seat/pointer/data.rs b/src/platform_impl/linux/wayland/seat/pointer/data.rs new file mode 100644 index 0000000000..1da60d3526 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/pointer/data.rs @@ -0,0 +1,74 @@ +//! Data which is used in pointer callbacks. + +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Attached; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1}; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; + +use crate::event::{ModifiersState, TouchPhase}; + +/// A data being used by pointer handlers. +pub(super) struct PointerData { + /// Winit's surface the pointer is currently over. + pub surface: Option, + + /// Current modifiers state. + /// + /// This refers a state of modifiers from `WlKeyboard` on + /// the given seat. + pub modifiers_state: Rc>, + + /// Pointer constraints. + pub pointer_constraints: Option>, + + pub confined_pointer: Rc>>, + + /// A latest event serial. + pub latest_serial: Rc>, + + /// The currently accumulated axis data on a pointer. + pub axis_data: AxisData, +} + +impl PointerData { + pub fn new( + confined_pointer: Rc>>, + pointer_constraints: Option>, + modifiers_state: Rc>, + ) -> Self { + Self { + surface: None, + latest_serial: Rc::new(Cell::new(0)), + confined_pointer, + modifiers_state, + pointer_constraints, + axis_data: AxisData::new(), + } + } +} + +/// Axis data. +#[derive(Clone, Copy)] +pub(super) struct AxisData { + /// Current state of the axis. + pub axis_state: TouchPhase, + + /// A buffer for `PixelDelta` event. + pub axis_buffer: Option<(f32, f32)>, + + /// A buffer for `LineDelta` event. + pub axis_discrete_buffer: Option<(f32, f32)>, +} + +impl AxisData { + pub fn new() -> Self { + Self { + axis_state: TouchPhase::Ended, + axis_buffer: None, + axis_discrete_buffer: None, + } + } +} diff --git a/src/platform_impl/linux/wayland/seat/pointer/handlers.rs b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs new file mode 100644 index 0000000000..06165a49f9 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs @@ -0,0 +1,297 @@ +//! Handlers for the pointers we're using. + +use std::cell::RefCell; +use std::rc::Rc; + +use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent}; +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent; + +use sctk::seat::pointer::ThemedPointer; + +use crate::dpi::LogicalPosition; +use crate::event::{ + DeviceEvent, ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent, +}; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::{self, DeviceId}; + +use super::{PointerData, WinitPointer}; + +#[inline] +pub(super) fn handle_pointer( + pointer: ThemedPointer, + event: PointerEvent, + pointer_data: &Rc>, + winit_state: &mut WinitState, +) { + let event_sink = &mut winit_state.event_sink; + let mut pointer_data = pointer_data.borrow_mut(); + match event { + PointerEvent::Enter { + surface, + surface_x, + surface_y, + serial, + .. + } => { + pointer_data.latest_serial.replace(serial); + + let window_id = wayland::make_wid(&surface); + if !winit_state.window_map.contains_key(&window_id) { + return; + } + let window_handle = match winit_state.window_map.get_mut(&window_id) { + Some(window_handle) => window_handle, + None => return, + }; + + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + pointer_data.surface = Some(surface); + + // Notify window that pointer entered the surface. + let winit_pointer = WinitPointer { + pointer, + confined_pointer: Rc::downgrade(&pointer_data.confined_pointer), + pointer_constraints: pointer_data.pointer_constraints.clone(), + latest_serial: pointer_data.latest_serial.clone(), + }; + window_handle.pointer_entered(winit_pointer); + + event_sink.push_window_event( + WindowEvent::CursorEntered { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + }, + window_id, + ); + + let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor); + + event_sink.push_window_event( + WindowEvent::CursorMoved { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + position, + modifiers: *pointer_data.modifiers_state.borrow(), + }, + window_id, + ); + } + PointerEvent::Leave { surface, serial } => { + pointer_data.surface = None; + pointer_data.latest_serial.replace(serial); + + let window_id = wayland::make_wid(&surface); + + let window_handle = match winit_state.window_map.get_mut(&window_id) { + Some(window_handle) => window_handle, + None => return, + }; + + // Notify a window that pointer is no longer observing it. + let winit_pointer = WinitPointer { + pointer, + confined_pointer: Rc::downgrade(&pointer_data.confined_pointer), + pointer_constraints: pointer_data.pointer_constraints.clone(), + latest_serial: pointer_data.latest_serial.clone(), + }; + window_handle.pointer_left(winit_pointer); + + event_sink.push_window_event( + WindowEvent::CursorLeft { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + }, + window_id, + ); + } + PointerEvent::Motion { + surface_x, + surface_y, + .. + } => { + let surface = match pointer_data.surface.as_ref() { + Some(surface) => surface, + None => return, + }; + + let window_id = wayland::make_wid(surface); + + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor); + + event_sink.push_window_event( + WindowEvent::CursorMoved { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + position, + modifiers: *pointer_data.modifiers_state.borrow(), + }, + window_id, + ); + } + PointerEvent::Button { + button, + state, + serial, + .. + } => { + pointer_data.latest_serial.replace(serial); + let window_id = match pointer_data.surface.as_ref().map(wayland::make_wid) { + Some(window_id) => window_id, + None => return, + }; + + let state = match state { + wl_pointer::ButtonState::Pressed => ElementState::Pressed, + wl_pointer::ButtonState::Released => ElementState::Released, + _ => unreachable!(), + }; + + let button = match button { + 0x110 => MouseButton::Left, + 0x111 => MouseButton::Right, + 0x112 => MouseButton::Middle, + // TODO - figure out the translation. + _ => return, + }; + + event_sink.push_window_event( + WindowEvent::MouseInput { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + state, + button, + modifiers: *pointer_data.modifiers_state.borrow(), + }, + window_id, + ); + } + PointerEvent::Axis { axis, value, .. } => { + let surface = match pointer_data.surface.as_ref() { + Some(surface) => surface, + None => return, + }; + + let window_id = wayland::make_wid(&surface); + + if pointer.as_ref().version() < 5 { + let (mut x, mut y) = (0.0, 0.0); + + // Old seat compatibility. + match axis { + // Wayland vertical sign convention is the inverse of winit. + wl_pointer::Axis::VerticalScroll => y -= value as f32, + wl_pointer::Axis::HorizontalScroll => x += value as f32, + _ => unreachable!(), + } + + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor); + + event_sink.push_window_event( + WindowEvent::MouseWheel { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + delta: MouseScrollDelta::PixelDelta(delta), + phase: TouchPhase::Moved, + modifiers: *pointer_data.modifiers_state.borrow(), + }, + window_id, + ); + } else { + let (mut x, mut y) = pointer_data.axis_data.axis_buffer.unwrap_or((0.0, 0.0)); + match axis { + // Wayland vertical sign convention is the inverse of winit. + wl_pointer::Axis::VerticalScroll => y -= value as f32, + wl_pointer::Axis::HorizontalScroll => x += value as f32, + _ => unreachable!(), + } + + pointer_data.axis_data.axis_buffer = Some((x, y)); + + pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state { + TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, + _ => TouchPhase::Started, + } + } + } + PointerEvent::AxisDiscrete { axis, discrete } => { + let (mut x, mut y) = pointer_data + .axis_data + .axis_discrete_buffer + .unwrap_or((0., 0.)); + + match axis { + // Wayland vertical sign convention is the inverse of winit. + wl_pointer::Axis::VerticalScroll => y -= discrete as f32, + wl_pointer::Axis::HorizontalScroll => x += discrete as f32, + _ => unreachable!(), + } + + pointer_data.axis_data.axis_discrete_buffer = Some((x, y)); + + pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state { + TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved, + _ => TouchPhase::Started, + } + } + PointerEvent::AxisSource { .. } => (), + PointerEvent::AxisStop { .. } => { + pointer_data.axis_data.axis_state = TouchPhase::Ended; + } + PointerEvent::Frame => { + let axis_buffer = pointer_data.axis_data.axis_buffer.take(); + let axis_discrete_buffer = pointer_data.axis_data.axis_discrete_buffer.take(); + + let surface = match pointer_data.surface.as_ref() { + Some(surface) => surface, + None => return, + }; + let window_id = wayland::make_wid(&surface); + + let window_event = if let Some((x, y)) = axis_discrete_buffer { + WindowEvent::MouseWheel { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + delta: MouseScrollDelta::LineDelta(x, y), + phase: pointer_data.axis_data.axis_state, + modifiers: *pointer_data.modifiers_state.borrow(), + } + } else if let Some((x, y)) = axis_buffer { + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let delta = LogicalPosition::new(x, y).to_physical(scale_factor); + + WindowEvent::MouseWheel { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + delta: MouseScrollDelta::PixelDelta(delta), + phase: pointer_data.axis_data.axis_state, + modifiers: *pointer_data.modifiers_state.borrow(), + } + } else { + return; + }; + + event_sink.push_window_event(window_event, window_id); + } + _ => (), + } +} + +#[inline] +pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) { + if let RelativePointerEvent::RelativeMotion { dx, dy, .. } = event { + winit_state + .event_sink + .push_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId) + } +} diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs new file mode 100644 index 0000000000..5debc8cbcf --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -0,0 +1,242 @@ +//! All pointer related handling. + +use std::cell::{Cell, RefCell}; +use std::rc::{Rc, Weak}; + +use sctk::reexports::client::protocol::wl_pointer::WlPointer; +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Attached; +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1; +use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime}; +use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; + +use sctk::seat::pointer::{ThemeManager, ThemedPointer}; + +use crate::event::ModifiersState; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::window::CursorIcon; + +mod data; +mod handlers; + +use data::PointerData; + +/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`. +pub struct WinitPointer { + pointer: ThemedPointer, + + /// Create confined pointers. + pointer_constraints: Option>, + + /// Cursor to handle confine requests. + confined_pointer: Weak>>, + + /// Latest observed serial in pointer events. + latest_serial: Rc>, +} + +impl PartialEq for WinitPointer { + fn eq(&self, other: &Self) -> bool { + *self.pointer == *other.pointer + } +} + +impl Eq for WinitPointer {} + +impl WinitPointer { + /// Set the cursor icon. + /// + /// Providing `None` will hide the cursor. + pub fn set_cursor(&self, cursor_icon: Option) { + let cursor_icon = match cursor_icon { + Some(cursor_icon) => cursor_icon, + None => { + // Hide the cursor. + (*self.pointer).set_cursor(self.latest_serial.get(), None, 0, 0); + return; + } + }; + + let cursors: &[&str] = match cursor_icon { + CursorIcon::Alias => &["link"], + CursorIcon::Arrow => &["arrow"], + CursorIcon::Cell => &["plus"], + CursorIcon::Copy => &["copy"], + CursorIcon::Crosshair => &["crosshair"], + CursorIcon::Default => &["left_ptr"], + CursorIcon::Hand => &["hand"], + CursorIcon::Help => &["question_arrow"], + CursorIcon::Move => &["move"], + CursorIcon::Grab => &["openhand", "grab"], + CursorIcon::Grabbing => &["closedhand", "grabbing"], + CursorIcon::Progress => &["progress"], + CursorIcon::AllScroll => &["all-scroll"], + CursorIcon::ContextMenu => &["context-menu"], + + CursorIcon::NoDrop => &["no-drop", "circle"], + CursorIcon::NotAllowed => &["crossed_circle"], + + // Resize cursors + CursorIcon::EResize => &["right_side"], + CursorIcon::NResize => &["top_side"], + CursorIcon::NeResize => &["top_right_corner"], + CursorIcon::NwResize => &["top_left_corner"], + CursorIcon::SResize => &["bottom_side"], + CursorIcon::SeResize => &["bottom_right_corner"], + CursorIcon::SwResize => &["bottom_left_corner"], + CursorIcon::WResize => &["left_side"], + CursorIcon::EwResize => &["h_double_arrow"], + CursorIcon::NsResize => &["v_double_arrow"], + CursorIcon::NwseResize => &["bd_double_arrow", "size_bdiag"], + CursorIcon::NeswResize => &["fd_double_arrow", "size_fdiag"], + CursorIcon::ColResize => &["split_h", "h_double_arrow"], + CursorIcon::RowResize => &["split_v", "v_double_arrow"], + CursorIcon::Text => &["text", "xterm"], + CursorIcon::VerticalText => &["vertical-text"], + + CursorIcon::Wait => &["watch"], + + CursorIcon::ZoomIn => &["zoom-in"], + CursorIcon::ZoomOut => &["zoom-out"], + }; + + let serial = Some(self.latest_serial.get()); + for cursor in cursors { + if self.pointer.set_cursor(cursor, serial).is_ok() { + break; + } + } + } + + /// Confine the pointer to a surface. + pub fn confine(&self, surface: &WlSurface) { + let pointer_constraints = match &self.pointer_constraints { + Some(pointer_constraints) => pointer_constraints, + None => return, + }; + + let confined_pointer = match self.confined_pointer.upgrade() { + Some(confined_pointer) => confined_pointer, + // A pointer is gone. + None => return, + }; + + *confined_pointer.borrow_mut() = Some(init_confined_pointer( + &pointer_constraints, + &surface, + &*self.pointer, + )); + } + + /// Tries to unconfine the pointer if the current pointer is confined. + pub fn unconfine(&self) { + let confined_pointer = match self.confined_pointer.upgrade() { + Some(confined_pointer) => confined_pointer, + // A pointer is gone. + None => return, + }; + + let mut confined_pointer = confined_pointer.borrow_mut(); + + if let Some(confined_pointer) = confined_pointer.take() { + confined_pointer.destroy(); + } + } +} + +/// A pointer wrapper for easy releasing and managing pointers. +pub(super) struct Pointers { + /// A pointer itself. + pointer: ThemedPointer, + + /// A relative pointer handler. + relative_pointer: Option, + + /// Confined pointer. + confined_pointer: Rc>>, +} + +impl Pointers { + pub(super) fn new( + seat: &Attached, + theme_manager: &ThemeManager, + relative_pointer_manager: &Option>, + pointer_constraints: &Option>, + modifiers_state: Rc>, + ) -> Self { + let confined_pointer = Rc::new(RefCell::new(None)); + let pointer_data = Rc::new(RefCell::new(PointerData::new( + confined_pointer.clone(), + pointer_constraints.clone(), + modifiers_state, + ))); + let pointer = theme_manager.theme_pointer_with_impl( + seat, + move |event, pointer, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_pointer(pointer, event, &pointer_data, winit_state); + }, + ); + + // Setup relative_pointer if it's available. + let relative_pointer = match relative_pointer_manager.as_ref() { + Some(relative_pointer_manager) => { + Some(init_relative_pointer(&relative_pointer_manager, &*pointer)) + } + None => None, + }; + + Self { + pointer, + relative_pointer, + confined_pointer, + } + } +} + +impl Drop for Pointers { + fn drop(&mut self) { + // Drop relative pointer. + if let Some(relative_pointer) = self.relative_pointer.take() { + relative_pointer.destroy(); + } + + // Drop confined pointer. + if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() { + confined_pointer.destroy(); + } + + // Drop the pointer itself in case it's possible. + if self.pointer.as_ref().version() >= 3 { + self.pointer.release(); + } + } +} + +pub(super) fn init_relative_pointer( + relative_pointer_manager: &ZwpRelativePointerManagerV1, + pointer: &WlPointer, +) -> ZwpRelativePointerV1 { + let relative_pointer = relative_pointer_manager.get_relative_pointer(&*pointer); + relative_pointer.quick_assign(move |_, event, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_relative_pointer(event, winit_state); + }); + + relative_pointer.detach() +} + +pub(super) fn init_confined_pointer( + pointer_constraints: &Attached, + surface: &WlSurface, + pointer: &WlPointer, +) -> ZwpConfinedPointerV1 { + let confined_pointer = + pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent.to_raw()); + + confined_pointer.quick_assign(move |_, _, _| {}); + + confined_pointer.detach() +} diff --git a/src/platform_impl/linux/wayland/seat/text_input/handlers.rs b/src/platform_impl/linux/wayland/seat/text_input/handlers.rs new file mode 100644 index 0000000000..4ba13d6715 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/text_input/handlers.rs @@ -0,0 +1,78 @@ +//! Handling of IME events. + +use sctk::reexports::client::Main; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::{ + Event as TextInputEvent, ZwpTextInputV3, +}; + +use crate::event::WindowEvent; +use crate::platform_impl::wayland; +use crate::platform_impl::wayland::event_loop::WinitState; + +use super::{TextInputHandler, TextInputInner}; + +#[inline] +pub(super) fn handle_text_input( + text_input: Main, + inner: &mut TextInputInner, + event: TextInputEvent, + winit_state: &mut WinitState, +) { + let event_sink = &mut winit_state.event_sink; + match event { + TextInputEvent::Enter { surface } => { + let window_id = wayland::make_wid(&surface); + + let window_handle = match winit_state.window_map.get_mut(&window_id) { + Some(window_handle) => window_handle, + None => return, + }; + inner.target_window_id = Some(window_id); + + // Enable text input on that surface. + text_input.enable(); + text_input.commit(); + + // Notify a window we're currently over about text input handler. + let text_input_handler = TextInputHandler { + text_input: text_input.detach(), + }; + window_handle.text_input_entered(text_input_handler); + } + TextInputEvent::Leave { surface } => { + // Always issue a disable. + text_input.disable(); + text_input.commit(); + + let window_id = wayland::make_wid(&surface); + + let window_handle = match winit_state.window_map.get_mut(&window_id) { + Some(window_handle) => window_handle, + None => return, + }; + + inner.target_window_id = None; + + // Remove text input handler from the window we're leaving. + let text_input_handler = TextInputHandler { + text_input: text_input.detach(), + }; + window_handle.text_input_left(text_input_handler); + } + TextInputEvent::CommitString { text } => { + // Update currenly commited string. + inner.commit_string = text; + } + TextInputEvent::Done { .. } => { + let (window_id, text) = match (inner.target_window_id, inner.commit_string.take()) { + (Some(window_id), Some(text)) => (window_id, text), + _ => return, + }; + + for ch in text.chars() { + event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); + } + } + _ => (), + } +} diff --git a/src/platform_impl/linux/wayland/seat/text_input/mod.rs b/src/platform_impl/linux/wayland/seat/text_input/mod.rs new file mode 100644 index 0000000000..77f4ff0827 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/text_input/mod.rs @@ -0,0 +1,66 @@ +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::Attached; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3; +use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::ZwpTextInputV3; + +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::WindowId; + +mod handlers; + +/// A handler for text input that we're advertising for `WindowHandle`. +#[derive(Eq, PartialEq)] +pub struct TextInputHandler { + text_input: ZwpTextInputV3, +} + +impl TextInputHandler { + #[inline] + pub fn set_ime_position(&self, x: i32, y: i32) { + self.text_input.set_cursor_rectangle(x, y, 0, 0); + self.text_input.commit(); + } +} + +/// A wrapper around text input to automatically destroy the object on `Drop`. +pub struct TextInput { + text_input: Attached, +} + +impl TextInput { + pub fn new(seat: &Attached, text_input_manager: &ZwpTextInputManagerV3) -> Self { + let text_input = text_input_manager.get_text_input(seat); + let mut text_input_inner = TextInputInner::new(); + text_input.quick_assign(move |text_input, event, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_text_input(text_input, &mut text_input_inner, event, winit_state); + }); + + let text_input: Attached = text_input.into(); + + Self { text_input } + } +} + +impl Drop for TextInput { + fn drop(&mut self) { + self.text_input.destroy(); + } +} + +struct TextInputInner { + /// Currently focused surface. + target_window_id: Option, + + /// Pending string to commit. + commit_string: Option, +} + +impl TextInputInner { + fn new() -> Self { + Self { + target_window_id: None, + commit_string: None, + } + } +} diff --git a/src/platform_impl/linux/wayland/seat/touch/handlers.rs b/src/platform_impl/linux/wayland/seat/touch/handlers.rs new file mode 100644 index 0000000000..8a17b39372 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/touch/handlers.rs @@ -0,0 +1,122 @@ +//! Various handlers for touch events. + +use sctk::reexports::client::protocol::wl_touch::Event as TouchEvent; + +use crate::dpi::LogicalPosition; +use crate::event::{TouchPhase, WindowEvent}; + +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::{self, DeviceId}; + +use super::{TouchInner, TouchPoint}; + +/// Handle WlTouch events. +#[inline] +pub(super) fn handle_touch( + event: TouchEvent, + inner: &mut TouchInner, + winit_state: &mut WinitState, +) { + let event_sink = &mut winit_state.event_sink; + + match event { + TouchEvent::Down { + surface, id, x, y, .. + } => { + let window_id = wayland::make_wid(&surface); + if !winit_state.window_map.contains_key(&window_id) { + return; + } + + let scale_factor = sctk::get_surface_scale_factor(&surface) as f64; + let position = LogicalPosition::new(x, y); + + event_sink.push_window_event( + WindowEvent::Touch(crate::event::Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Started, + location: position.to_physical(scale_factor), + force: None, // TODO + id: id as u64, + }), + window_id, + ); + + inner + .touch_points + .push(TouchPoint::new(surface, position, id)); + } + TouchEvent::Up { id, .. } => { + let touch_point = match inner.touch_points.iter().find(|p| p.id == id) { + Some(touch_point) => touch_point, + None => return, + }; + + let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64; + let location = touch_point.position.to_physical(scale_factor); + let window_id = wayland::make_wid(&touch_point.surface); + + event_sink.push_window_event( + WindowEvent::Touch(crate::event::Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Ended, + location, + force: None, // TODO + id: id as u64, + }), + window_id, + ); + } + TouchEvent::Motion { id, x, y, .. } => { + let touch_point = match inner.touch_points.iter_mut().find(|p| p.id == id) { + Some(touch_point) => touch_point, + None => return, + }; + + touch_point.position = LogicalPosition::new(x, y); + + let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64; + let location = touch_point.position.to_physical(scale_factor); + let window_id = wayland::make_wid(&touch_point.surface); + + event_sink.push_window_event( + WindowEvent::Touch(crate::event::Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Moved, + location, + force: None, // TODO + id: id as u64, + }), + window_id, + ); + } + TouchEvent::Frame => (), + TouchEvent::Cancel => { + for touch_point in inner.touch_points.drain(..) { + let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64; + let location = touch_point.position.to_physical(scale_factor); + let window_id = wayland::make_wid(&touch_point.surface); + + event_sink.push_window_event( + WindowEvent::Touch(crate::event::Touch { + device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( + DeviceId, + )), + phase: TouchPhase::Cancelled, + location, + force: None, // TODO + id: touch_point.id as u64, + }), + window_id, + ); + } + } + _ => (), + } +} diff --git a/src/platform_impl/linux/wayland/seat/touch/mod.rs b/src/platform_impl/linux/wayland/seat/touch/mod.rs new file mode 100644 index 0000000000..197e9ac9c5 --- /dev/null +++ b/src/platform_impl/linux/wayland/seat/touch/mod.rs @@ -0,0 +1,78 @@ +//! Touch handling. + +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::protocol::wl_touch::WlTouch; +use sctk::reexports::client::Attached; + +use crate::dpi::LogicalPosition; + +use crate::platform_impl::wayland::event_loop::WinitState; + +mod handlers; + +/// Wrapper around touch to handle release. +pub struct Touch { + /// Proxy to touch. + touch: WlTouch, +} + +impl Touch { + pub fn new(seat: &Attached) -> Self { + let touch = seat.get_touch(); + let mut inner = TouchInner::new(); + + touch.quick_assign(move |_, event, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + handlers::handle_touch(event, &mut inner, winit_state); + }); + + Self { + touch: touch.detach(), + } + } +} + +impl Drop for Touch { + fn drop(&mut self) { + if self.touch.as_ref().version() >= 3 { + self.touch.release(); + } + } +} + +/// The data used by touch handlers. +pub(super) struct TouchInner { + /// Current touch points. + touch_points: Vec, +} + +impl TouchInner { + fn new() -> Self { + Self { + touch_points: Vec::new(), + } + } +} + +/// Location of touch press. +pub(super) struct TouchPoint { + /// A surface where the touch point is located. + surface: WlSurface, + + /// Location of the touch point. + position: LogicalPosition, + + /// Id. + id: i32, +} + +impl TouchPoint { + pub fn new(surface: WlSurface, position: LogicalPosition, id: i32) -> Self { + Self { + surface, + position, + id, + } + } +} diff --git a/src/platform_impl/linux/wayland/touch.rs b/src/platform_impl/linux/wayland/touch.rs deleted file mode 100644 index ce4e32c284..0000000000 --- a/src/platform_impl/linux/wayland/touch.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use crate::dpi::LogicalPosition; -use crate::event::{TouchPhase, WindowEvent}; - -use super::{event_loop::EventsSink, make_wid, window::WindowStore, DeviceId}; - -use smithay_client_toolkit::surface; - -use smithay_client_toolkit::reexports::client::protocol::{ - wl_seat, - wl_surface::WlSurface, - wl_touch::{Event as TouchEvent, WlTouch}, -}; - -// location is in logical coordinates. -struct TouchPoint { - surface: WlSurface, - position: LogicalPosition, - id: i32, -} - -pub(crate) fn implement_touch( - seat: &wl_seat::WlSeat, - sink: EventsSink, - store: Arc>, -) -> WlTouch { - let mut pending_ids = Vec::new(); - seat.get_touch(|touch| { - touch.implement_closure( - move |evt, _| { - let store = store.lock().unwrap(); - match evt { - TouchEvent::Down { - surface, id, x, y, .. - } => { - let wid = store.find_wid(&surface); - if let Some(wid) = wid { - let scale_factor = surface::get_dpi_factor(&surface) as f64; - let position = LogicalPosition::new(x, y); - - sink.send_window_event( - WindowEvent::Touch(crate::event::Touch { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - phase: TouchPhase::Started, - location: position.to_physical(scale_factor), - force: None, // TODO - id: id as u64, - }), - wid, - ); - pending_ids.push(TouchPoint { - surface, - position, - id, - }); - } - } - TouchEvent::Up { id, .. } => { - let idx = pending_ids.iter().position(|p| p.id == id); - if let Some(idx) = idx { - let pt = pending_ids.remove(idx); - - let scale_factor = surface::get_dpi_factor(&pt.surface) as f64; - let location = pt.position.to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::Touch(crate::event::Touch { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - phase: TouchPhase::Ended, - location, - force: None, // TODO - id: id as u64, - }), - make_wid(&pt.surface), - ); - } - } - TouchEvent::Motion { id, x, y, .. } => { - let pt = pending_ids.iter_mut().find(|p| p.id == id); - if let Some(pt) = pt { - pt.position = LogicalPosition::new(x, y); - - let scale_factor = surface::get_dpi_factor(&pt.surface) as f64; - let location = pt.position.to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::Touch(crate::event::Touch { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - phase: TouchPhase::Moved, - location, - force: None, // TODO - id: id as u64, - }), - make_wid(&pt.surface), - ); - } - } - TouchEvent::Frame => (), - TouchEvent::Cancel => { - for pt in pending_ids.drain(..) { - let scale_factor = surface::get_dpi_factor(&pt.surface) as f64; - let location = pt.position.to_physical(scale_factor); - - sink.send_window_event( - WindowEvent::Touch(crate::event::Touch { - device_id: crate::event::DeviceId( - crate::platform_impl::DeviceId::Wayland(DeviceId), - ), - phase: TouchPhase::Cancelled, - location, - force: None, // TODO - id: pt.id as u64, - }), - make_wid(&pt.surface), - ); - } - } - _ => unreachable!(), - } - }, - (), - ) - }) - .unwrap() -} diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs deleted file mode 100644 index 06f668cc45..0000000000 --- a/src/platform_impl/linux/wayland/window.rs +++ /dev/null @@ -1,569 +0,0 @@ -use raw_window_handle::unix::WaylandHandle; -use std::{ - collections::VecDeque, - mem::replace, - sync::{Arc, Mutex, Weak}, -}; - -use crate::{ - dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, - error::{ExternalError, NotSupportedError, OsError as RootOsError}, - monitor::MonitorHandle as RootMonitorHandle, - platform_impl::{ - platform::wayland::event_loop::available_monitors, MonitorHandle as PlatformMonitorHandle, - PlatformSpecificWindowBuilderAttributes as PlAttributes, - }, - window::{CursorIcon, Fullscreen, WindowAttributes}, -}; - -use smithay_client_toolkit::{ - output::OutputMgr, - reexports::client::{ - protocol::{wl_seat, wl_surface}, - Display, - }, - surface::{get_dpi_factor, get_outputs}, - window::{ConceptFrame, Event as WEvent, State as WState, Theme, Window as SWindow}, -}; - -use super::{event_loop::CursorManager, make_wid, EventLoopWindowTarget, MonitorHandle, WindowId}; - -pub struct Window { - surface: wl_surface::WlSurface, - frame: Arc>>, - cursor_manager: Arc>, - outputs: OutputMgr, // Access to info for all monitors - size: Arc>, - kill_switch: (Arc>, Arc>), - display: Arc, - need_frame_refresh: Arc>, - need_refresh: Arc>, - fullscreen: Arc>, - cursor_grab_changed: Arc>>, // Update grab state - decorated: Arc>, -} - -#[derive(Clone, Copy, Debug)] -pub enum DecorationsAction { - Hide, - Show, -} - -impl Window { - pub fn new( - evlp: &EventLoopWindowTarget, - attributes: WindowAttributes, - pl_attribs: PlAttributes, - ) -> Result { - // Create the surface first to get initial DPI - let window_store = evlp.store.clone(); - let cursor_manager = evlp.cursor_manager.clone(); - let surface = evlp.env.create_surface(move |scale_factor, surface| { - window_store - .lock() - .unwrap() - .scale_factor_change(&surface, scale_factor); - surface.set_buffer_scale(scale_factor); - }); - - // Always 1. - let scale_factor = get_dpi_factor(&surface); - - let (width, height) = attributes - .inner_size - .map(|size| size.to_logical::(scale_factor as f64).into()) - .unwrap_or((800, 600)); - - // Create the window - let size = Arc::new(Mutex::new((width, height))); - let fullscreen = Arc::new(Mutex::new(false)); - - let window_store = evlp.store.clone(); - - let decorated = Arc::new(Mutex::new(attributes.decorations)); - let pending_decorations_action = Arc::new(Mutex::new(None)); - - let my_surface = surface.clone(); - let mut frame = SWindow::::init_from_env( - &evlp.env, - surface.clone(), - (width, height), - move |event| match event { - WEvent::Configure { new_size, states } => { - let mut store = window_store.lock().unwrap(); - let is_fullscreen = states.contains(&WState::Fullscreen); - - for window in &mut store.windows { - if window.surface.as_ref().equals(&my_surface.as_ref()) { - window.new_size = new_size; - *(window.need_refresh.lock().unwrap()) = true; - { - // Get whether we're in fullscreen - let mut fullscreen = window.fullscreen.lock().unwrap(); - // Fullscreen state was changed, so update decorations - if *fullscreen != is_fullscreen { - let decorated = { *window.decorated.lock().unwrap() }; - if decorated { - *window.pending_decorations_action.lock().unwrap() = - if is_fullscreen { - Some(DecorationsAction::Hide) - } else { - Some(DecorationsAction::Show) - }; - } - } - *fullscreen = is_fullscreen; - } - *(window.need_frame_refresh.lock().unwrap()) = true; - return; - } - } - } - WEvent::Refresh => { - let store = window_store.lock().unwrap(); - for window in &store.windows { - if window.surface.as_ref().equals(&my_surface.as_ref()) { - *(window.need_frame_refresh.lock().unwrap()) = true; - return; - } - } - } - WEvent::Close => { - let mut store = window_store.lock().unwrap(); - for window in &mut store.windows { - if window.surface.as_ref().equals(&my_surface.as_ref()) { - window.closed = true; - return; - } - } - } - }, - ) - .unwrap(); - - if let Some(app_id) = pl_attribs.app_id { - frame.set_app_id(app_id); - } - - for &(_, ref seat) in evlp.seats.lock().unwrap().iter() { - frame.new_seat(seat); - } - - // Check for fullscreen requirements - match attributes.fullscreen { - Some(Fullscreen::Exclusive(_)) => { - panic!("Wayland doesn't support exclusive fullscreen") - } - Some(Fullscreen::Borderless(monitor)) => { - let monitor = - monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { - PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), - #[cfg(feature = "x11")] - PlatformMonitorHandle::X(_) => None, - }); - - frame.set_fullscreen(monitor.as_ref()) - } - None => { - if attributes.maximized { - frame.set_maximized(); - } - } - } - - frame.set_resizable(attributes.resizable); - - // set decorations - frame.set_decorate(attributes.decorations); - - // set title - frame.set_title(attributes.title); - - // min-max dimensions - frame.set_min_size( - attributes - .min_inner_size - .map(|size| size.to_logical::(scale_factor as f64).into()), - ); - frame.set_max_size( - attributes - .max_inner_size - .map(|size| size.to_logical::(scale_factor as f64).into()), - ); - - let kill_switch = Arc::new(Mutex::new(false)); - let need_frame_refresh = Arc::new(Mutex::new(true)); - let frame = Arc::new(Mutex::new(frame)); - let need_refresh = Arc::new(Mutex::new(true)); - let cursor_grab_changed = Arc::new(Mutex::new(None)); - - evlp.store.lock().unwrap().windows.push(InternalWindow { - closed: false, - new_size: None, - size: size.clone(), - need_refresh: need_refresh.clone(), - fullscreen: fullscreen.clone(), - cursor_grab_changed: cursor_grab_changed.clone(), - need_frame_refresh: need_frame_refresh.clone(), - surface: surface.clone(), - kill_switch: kill_switch.clone(), - frame: Arc::downgrade(&frame), - current_scale_factor: scale_factor, - new_scale_factor: None, - decorated: decorated.clone(), - pending_decorations_action: pending_decorations_action.clone(), - }); - evlp.evq.borrow_mut().sync_roundtrip().unwrap(); - - Ok(Window { - display: evlp.display.clone(), - surface, - frame, - outputs: evlp.env.outputs.clone(), - size, - kill_switch: (kill_switch, evlp.cleanup_needed.clone()), - need_frame_refresh, - need_refresh, - cursor_manager, - fullscreen, - cursor_grab_changed, - decorated, - }) - } - - #[inline] - pub fn id(&self) -> WindowId { - make_wid(&self.surface) - } - - pub fn set_title(&self, title: &str) { - self.frame.lock().unwrap().set_title(title.into()); - } - - pub fn set_visible(&self, _visible: bool) { - // TODO - } - - #[inline] - pub fn outer_position(&self) -> Result, NotSupportedError> { - Err(NotSupportedError::new()) - } - - #[inline] - pub fn inner_position(&self) -> Result, NotSupportedError> { - Err(NotSupportedError::new()) - } - - #[inline] - pub fn set_outer_position(&self, _pos: Position) { - // Not possible with wayland - } - - pub fn inner_size(&self) -> PhysicalSize { - let scale_factor = self.scale_factor() as f64; - let size = LogicalSize::::from(*self.size.lock().unwrap()); - size.to_physical(scale_factor) - } - - pub fn request_redraw(&self) { - *self.need_refresh.lock().unwrap() = true; - } - - #[inline] - pub fn outer_size(&self) -> PhysicalSize { - let scale_factor = self.scale_factor() as f64; - let (w, h) = self.size.lock().unwrap().clone(); - // let (w, h) = super::wayland_window::add_borders(w as i32, h as i32); - let size = LogicalSize::::from((w, h)); - size.to_physical(scale_factor) - } - - #[inline] - // NOTE: This will only resize the borders, the contents must be updated by the user - pub fn set_inner_size(&self, size: Size) { - let scale_factor = self.scale_factor() as f64; - let (w, h) = size.to_logical::(scale_factor).into(); - self.frame.lock().unwrap().resize(w, h); - *(self.size.lock().unwrap()) = (w, h); - } - - #[inline] - pub fn set_min_inner_size(&self, dimensions: Option) { - let scale_factor = self.scale_factor() as f64; - self.frame - .lock() - .unwrap() - .set_min_size(dimensions.map(|dim| dim.to_logical::(scale_factor).into())); - } - - #[inline] - pub fn set_max_inner_size(&self, dimensions: Option) { - let scale_factor = self.scale_factor() as f64; - self.frame - .lock() - .unwrap() - .set_max_size(dimensions.map(|dim| dim.to_logical::(scale_factor).into())); - } - - #[inline] - pub fn set_resizable(&self, resizable: bool) { - self.frame.lock().unwrap().set_resizable(resizable); - } - - #[inline] - pub fn scale_factor(&self) -> i32 { - get_dpi_factor(&self.surface) - } - - pub fn set_decorations(&self, decorate: bool) { - *(self.decorated.lock().unwrap()) = decorate; - self.frame.lock().unwrap().set_decorate(decorate); - *(self.need_frame_refresh.lock().unwrap()) = true; - } - - pub fn set_minimized(&self, minimized: bool) { - // An app cannot un-minimize itself on Wayland - if minimized { - self.frame.lock().unwrap().set_minimized(); - } - } - - pub fn set_maximized(&self, maximized: bool) { - if maximized { - self.frame.lock().unwrap().set_maximized(); - } else { - self.frame.lock().unwrap().unset_maximized(); - } - } - - pub fn fullscreen(&self) -> Option { - if *(self.fullscreen.lock().unwrap()) { - let current_monitor = self - .current_monitor() - .map(|current_monitor| RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(current_monitor), - }); - - Some(Fullscreen::Borderless(current_monitor)) - } else { - None - } - } - - pub fn set_fullscreen(&self, fullscreen: Option) { - match fullscreen { - Some(Fullscreen::Exclusive(_)) => { - panic!("Wayland doesn't support exclusive fullscreen") - } - Some(Fullscreen::Borderless(monitor)) => { - let monitor = - monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { - PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), - #[cfg(feature = "x11")] - PlatformMonitorHandle::X(_) => None, - }); - - self.frame.lock().unwrap().set_fullscreen(monitor.as_ref()); - } - None => self.frame.lock().unwrap().unset_fullscreen(), - } - } - - pub fn set_theme(&self, theme: T) { - self.frame.lock().unwrap().set_theme(theme) - } - - #[inline] - pub fn set_cursor_icon(&self, cursor: CursorIcon) { - let mut cursor_manager = self.cursor_manager.lock().unwrap(); - cursor_manager.set_cursor_icon(cursor); - } - - #[inline] - pub fn set_cursor_visible(&self, visible: bool) { - let mut cursor_manager = self.cursor_manager.lock().unwrap(); - cursor_manager.set_cursor_visible(visible); - } - - #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - *self.cursor_grab_changed.lock().unwrap() = Some(grab); - Ok(()) - } - - #[inline] - pub fn set_cursor_position(&self, _pos: Position) -> Result<(), ExternalError> { - Err(ExternalError::NotSupported(NotSupportedError::new())) - } - - pub fn display(&self) -> &Display { - &*self.display - } - - pub fn surface(&self) -> &wl_surface::WlSurface { - &self.surface - } - - pub fn current_monitor(&self) -> Option { - let output = get_outputs(&self.surface).last()?.clone(); - Some(MonitorHandle { - proxy: output, - mgr: self.outputs.clone(), - }) - } - - pub fn available_monitors(&self) -> VecDeque { - available_monitors(&self.outputs) - } - - pub fn primary_monitor(&self) -> Option { - // Wayland doesn't have a notion of primary monitor. - None - } - - pub fn raw_window_handle(&self) -> WaylandHandle { - WaylandHandle { - surface: self.surface().as_ref().c_ptr() as *mut _, - display: self.display().as_ref().c_ptr() as *mut _, - ..WaylandHandle::empty() - } - } -} - -impl Drop for Window { - fn drop(&mut self) { - *(self.kill_switch.0.lock().unwrap()) = true; - *(self.kill_switch.1.lock().unwrap()) = true; - } -} - -/* - * Internal store for windows - */ - -struct InternalWindow { - surface: wl_surface::WlSurface, - // TODO: CONVERT TO LogicalSizes - new_size: Option<(u32, u32)>, - size: Arc>, - need_refresh: Arc>, - fullscreen: Arc>, - need_frame_refresh: Arc>, - cursor_grab_changed: Arc>>, - closed: bool, - kill_switch: Arc>, - frame: Weak>>, - current_scale_factor: i32, - new_scale_factor: Option, - decorated: Arc>, - pending_decorations_action: Arc>>, -} - -pub struct WindowStore { - windows: Vec, -} - -pub struct WindowStoreForEach<'a> { - pub new_size: Option<(u32, u32)>, - pub size: &'a Mutex<(u32, u32)>, - pub prev_scale_factor: i32, - pub new_scale_factor: Option, - pub closed: bool, - pub grab_cursor: Option, - pub surface: &'a wl_surface::WlSurface, - pub wid: WindowId, - pub frame: Option>>>, - pub decorations_action: Option, -} - -impl WindowStore { - pub fn new() -> WindowStore { - WindowStore { - windows: Vec::new(), - } - } - - pub fn find_wid(&self, surface: &wl_surface::WlSurface) -> Option { - for window in &self.windows { - if surface.as_ref().equals(&window.surface.as_ref()) { - return Some(make_wid(surface)); - } - } - None - } - - pub fn cleanup(&mut self) -> Vec { - let mut pruned = Vec::new(); - self.windows.retain(|w| { - if *w.kill_switch.lock().unwrap() { - // window is dead, cleanup - pruned.push(make_wid(&w.surface)); - w.surface.destroy(); - false - } else { - true - } - }); - pruned - } - - pub fn new_seat(&self, seat: &wl_seat::WlSeat) { - for window in &self.windows { - if let Some(w) = window.frame.upgrade() { - w.lock().unwrap().new_seat(seat); - } - } - } - - fn scale_factor_change(&mut self, surface: &wl_surface::WlSurface, new: i32) { - for window in &mut self.windows { - if surface.as_ref().equals(&window.surface.as_ref()) { - window.new_scale_factor = Some(new); - *(window.need_refresh.lock().unwrap()) = true; - } - } - } - - pub fn for_each(&mut self, mut f: F) - where - F: FnMut(WindowStoreForEach<'_>), - { - for window in &mut self.windows { - let prev_scale_factor = window.current_scale_factor; - if let Some(scale_factor) = window.new_scale_factor { - window.current_scale_factor = scale_factor; - } - let frame = window.frame.upgrade(); - let decorations_action = { window.pending_decorations_action.lock().unwrap().take() }; - f(WindowStoreForEach { - new_size: window.new_size.take(), - size: &window.size, - prev_scale_factor, - new_scale_factor: window.new_scale_factor.take(), - closed: window.closed, - grab_cursor: window.cursor_grab_changed.lock().unwrap().take(), - surface: &window.surface, - wid: make_wid(&window.surface), - frame, - decorations_action, - }); - // avoid re-spamming the event - window.closed = false; - } - } - - pub fn for_each_redraw_trigger(&mut self, mut f: F) - where - F: FnMut(bool, bool, WindowId, Option>>>), - { - for window in &mut self.windows { - let frame = window.frame.upgrade(); - f( - replace(&mut *window.need_refresh.lock().unwrap(), false), - replace(&mut *window.need_frame_refresh.lock().unwrap(), false), - make_wid(&window.surface), - frame, - ); - } - } -} diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs new file mode 100644 index 0000000000..271e8058fd --- /dev/null +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -0,0 +1,656 @@ +use std::collections::VecDeque; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::client::Display; + +use sctk::reexports::calloop; + +use sctk::window::{ + ARGBColor, ButtonColorSpec, ColorSpec, ConceptConfig, ConceptFrame, Decorations, +}; + +use raw_window_handle::unix::WaylandHandle; + +use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}; +use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; +use crate::monitor::MonitorHandle as RootMonitorHandle; +use crate::platform::unix::{ARGBColor as LocalARGBColor, Button, ButtonState, Element, Theme}; +use crate::platform_impl::{ + MonitorHandle as PlatformMonitorHandle, OsError, + PlatformSpecificWindowBuilderAttributes as PlatformAttributes, +}; +use crate::window::{CursorIcon, Fullscreen, WindowAttributes}; + +use super::env::WindowingFeatures; +use super::event_loop::WinitState; +use super::output::{MonitorHandle, OutputManagerHandle}; +use super::{EventLoopWindowTarget, WindowId}; + +pub mod shim; + +use shim::{WindowHandle, WindowRequest, WindowUpdate}; + +pub struct Window { + /// Window id. + window_id: WindowId, + + /// The Wayland display. + display: Display, + + /// The underlying wl_surface. + surface: WlSurface, + + /// The current window size. + size: Arc>>, + + /// A handle to output manager. + output_manager_handle: OutputManagerHandle, + + /// Event loop proxy to wake it up. + event_loop_awakener: calloop::ping::Ping, + + /// Fullscreen state. + fullscreen: Arc, + + /// Available windowing features. + windowing_features: WindowingFeatures, + + /// Requests that SCTK window should perform. + window_requests: Arc>>, +} + +impl Window { + pub fn new( + event_loop_window_target: &EventLoopWindowTarget, + attributes: WindowAttributes, + platform_attributes: PlatformAttributes, + ) -> Result { + let surface = event_loop_window_target + .env + .create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| { + let winit_state = dispatch_data.get::().unwrap(); + + // Get the window that receiced the event. + let window_id = super::make_wid(&surface); + let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap(); + + // Set pending scale factor. + window_update.scale_factor = Some(scale); + window_update.redraw_requested = true; + + surface.set_buffer_scale(scale); + }) + .detach(); + + let scale_factor = sctk::get_surface_scale_factor(&surface); + + let window_id = super::make_wid(&surface); + let fullscreen = Arc::new(AtomicBool::new(false)); + let fullscreen_clone = fullscreen.clone(); + + let (width, height) = attributes + .inner_size + .map(|size| size.to_logical::(scale_factor as f64).into()) + .unwrap_or((800, 600)); + + let theme_manager = event_loop_window_target.theme_manager.clone(); + let mut window = event_loop_window_target + .env + .create_window::( + surface.clone(), + Some(theme_manager), + (width, height), + move |event, mut dispatch_data| { + use sctk::window::{Event, State}; + + let winit_state = dispatch_data.get::().unwrap(); + let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap(); + + match event { + Event::Refresh => { + window_update.refresh_frame = true; + } + Event::Configure { new_size, states } => { + let is_fullscreen = states.contains(&State::Fullscreen); + fullscreen_clone.store(is_fullscreen, Ordering::Relaxed); + + window_update.refresh_frame = true; + window_update.redraw_requested = true; + if let Some((w, h)) = new_size { + window_update.size = Some(LogicalSize::new(w, h)); + } + } + Event::Close => { + window_update.close_window = true; + } + } + }, + ) + .map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?; + + // Set decorations. + if attributes.decorations { + window.set_decorate(Decorations::FollowServer); + } else { + window.set_decorate(Decorations::None); + } + + // Min dimensions. + let min_size = attributes + .min_inner_size + .map(|size| size.to_logical::(scale_factor as f64).into()); + window.set_min_size(min_size); + + // Max dimensions. + let max_size = attributes + .min_inner_size + .map(|size| size.to_logical::(scale_factor as f64).into()); + window.set_max_size(max_size); + + // Set Wayland specific window attributes. + if let Some(app_id) = platform_attributes.app_id { + window.set_app_id(app_id); + } + + // Set common window attributes. + // + // We set resizable after other attributes, since it touches min and max size under + // the hood. + window.set_resizable(attributes.resizable); + window.set_title(attributes.title); + + // Set fullscreen/maximized if so was requested. + match attributes.fullscreen { + Some(Fullscreen::Exclusive(_)) => { + warn!("`Fullscreen::Exclusive` is ignored on Wayland") + } + Some(Fullscreen::Borderless(monitor)) => { + let monitor = + monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { + PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), + #[cfg(feature = "x11")] + PlatformMonitorHandle::X(_) => None, + }); + + window.set_fullscreen(monitor.as_ref()); + } + None => { + if attributes.maximized { + window.set_maximized(); + } + } + } + + let size = Arc::new(Mutex::new(LogicalSize::new(width, height))); + + // We should trigger redraw and commit the surface for the newly created window. + let mut window_update = WindowUpdate::new(); + window_update.refresh_frame = true; + window_update.redraw_requested = true; + + let window_id = super::make_wid(&surface); + let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64))); + + // Create a handle that performs all the requests on underlying sctk a window. + let window_handle = WindowHandle::new(window, size.clone(), window_requests.clone()); + + let mut winit_state = event_loop_window_target.state.borrow_mut(); + + winit_state.window_map.insert(window_id, window_handle); + + winit_state + .window_updates + .insert(window_id, WindowUpdate::new()); + + let windowing_features = event_loop_window_target.windowing_features; + + // Send all updates to the server. + let wayland_source = &event_loop_window_target.wayland_source; + let event_loop_handle = &event_loop_window_target.event_loop_handle; + + // To make our window usable for drawing right away we must `ack` a `configure` + // from the server, the acking part here is done by SCTK window frame, so we just + // need to sync with server so it'll be done automatically for us. + event_loop_handle.with_source(&wayland_source, |event_queue| { + let event_queue = event_queue.queue(); + let _ = event_queue.sync_roundtrip(&mut *winit_state, |_, _, _| unreachable!()); + }); + + // We all praise GNOME for these 3 lines of pure magic. If we don't do that, + // GNOME will shrink our window a bit for the size of the decorations. I guess it + // happens because we haven't committed them with buffers to the server. + let window_handle = winit_state.window_map.get_mut(&window_id).unwrap(); + window_handle.window.refresh(); + + let output_manager_handle = event_loop_window_target.output_manager.handle(); + + let window = Self { + window_id, + surface, + display: event_loop_window_target.display.clone(), + output_manager_handle, + size, + window_requests, + event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(), + fullscreen, + windowing_features, + }; + + Ok(window) + } +} + +impl Window { + #[inline] + pub fn id(&self) -> WindowId { + self.window_id + } + + #[inline] + pub fn set_title(&self, title: &str) { + let title_request = WindowRequest::Title(title.to_owned()); + self.window_requests.lock().unwrap().push(title_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_visible(&self, _visible: bool) { + // Not possible on Wayland. + } + + #[inline] + pub fn outer_position(&self) -> Result, NotSupportedError> { + Err(NotSupportedError::new()) + } + + #[inline] + pub fn inner_position(&self) -> Result, NotSupportedError> { + Err(NotSupportedError::new()) + } + + #[inline] + pub fn set_outer_position(&self, _: Position) { + // Not possible on Wayland. + } + + pub fn inner_size(&self) -> PhysicalSize { + self.size + .lock() + .unwrap() + .to_physical(self.scale_factor() as f64) + } + + #[inline] + pub fn request_redraw(&self) { + let redraw_request = WindowRequest::Redraw; + self.window_requests.lock().unwrap().push(redraw_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn outer_size(&self) -> PhysicalSize { + self.size + .lock() + .unwrap() + .to_physical(self.scale_factor() as f64) + } + + #[inline] + pub fn set_inner_size(&self, size: Size) { + let scale_factor = self.scale_factor() as f64; + + let size = size.to_logical::(scale_factor); + *self.size.lock().unwrap() = size; + + let frame_size_request = WindowRequest::FrameSize(size); + self.window_requests + .lock() + .unwrap() + .push(frame_size_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_min_inner_size(&self, dimensions: Option) { + let scale_factor = self.scale_factor() as f64; + let size = dimensions.map(|size| size.to_logical::(scale_factor)); + + let min_size_request = WindowRequest::MinSize(size); + self.window_requests.lock().unwrap().push(min_size_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_max_inner_size(&self, dimensions: Option) { + let scale_factor = self.scale_factor() as f64; + let size = dimensions.map(|size| size.to_logical::(scale_factor)); + + let max_size_request = WindowRequest::MaxSize(size); + self.window_requests.lock().unwrap().push(max_size_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_resizable(&self, resizable: bool) { + let resizeable_request = WindowRequest::Resizeable(resizable); + self.window_requests + .lock() + .unwrap() + .push(resizeable_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn scale_factor(&self) -> u32 { + // The scale factor from `get_surface_scale_factor` is always greater than zero, so + // u32 conversion is safe. + sctk::get_surface_scale_factor(&self.surface) as u32 + } + + #[inline] + pub fn set_decorations(&self, decorate: bool) { + let decorate_request = WindowRequest::Decorate(decorate); + self.window_requests.lock().unwrap().push(decorate_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_minimized(&self, minimized: bool) { + // You can't unminimize the window on Wayland. + if !minimized { + return; + } + + let minimize_request = WindowRequest::Minimize; + self.window_requests.lock().unwrap().push(minimize_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_maximized(&self, maximized: bool) { + let maximize_request = WindowRequest::Maximize(maximized); + self.window_requests.lock().unwrap().push(maximize_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn fullscreen(&self) -> Option { + if self.fullscreen.load(Ordering::Relaxed) { + let current_monitor = self.current_monitor().map(|monitor| RootMonitorHandle { + inner: PlatformMonitorHandle::Wayland(monitor), + }); + + Some(Fullscreen::Borderless(current_monitor)) + } else { + None + } + } + + #[inline] + pub fn set_fullscreen(&self, fullscreen: Option) { + let fullscreen_request = match fullscreen { + Some(Fullscreen::Exclusive(_)) => { + warn!("`Fullscreen::Exclusive` is ignored on Wayland"); + return; + } + Some(Fullscreen::Borderless(monitor)) => { + let monitor = + monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor { + PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy), + #[cfg(feature = "x11")] + PlatformMonitorHandle::X(_) => None, + }); + + WindowRequest::Fullscreen(monitor) + } + None => WindowRequest::UnsetFullscreen, + }; + + self.window_requests + .lock() + .unwrap() + .push(fullscreen_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_theme(&self, theme: T) { + // First buttons is minimize, then maximize, and then close. + let buttons: Vec<(ButtonColorSpec, ButtonColorSpec)> = + [Button::Minimize, Button::Maximize, Button::Close] + .iter() + .map(|button| { + let button = *button; + let idle_active_bg = theme + .button_color(button, ButtonState::Idle, false, true) + .into(); + let idle_inactive_bg = theme + .button_color(button, ButtonState::Idle, false, false) + .into(); + let idle_active_icon = theme + .button_color(button, ButtonState::Idle, true, true) + .into(); + let idle_inactive_icon = theme + .button_color(button, ButtonState::Idle, true, false) + .into(); + let idle_bg = ColorSpec { + active: idle_active_bg, + inactive: idle_inactive_bg, + }; + let idle_icon = ColorSpec { + active: idle_active_icon, + inactive: idle_inactive_icon, + }; + + let hovered_active_bg = theme + .button_color(button, ButtonState::Hovered, false, true) + .into(); + let hovered_inactive_bg = theme + .button_color(button, ButtonState::Hovered, false, false) + .into(); + let hovered_active_icon = theme + .button_color(button, ButtonState::Hovered, true, true) + .into(); + let hovered_inactive_icon = theme + .button_color(button, ButtonState::Hovered, true, false) + .into(); + let hovered_bg = ColorSpec { + active: hovered_active_bg, + inactive: hovered_inactive_bg, + }; + let hovered_icon = ColorSpec { + active: hovered_active_icon, + inactive: hovered_inactive_icon, + }; + + let disabled_active_bg = theme + .button_color(button, ButtonState::Disabled, false, true) + .into(); + let disabled_inactive_bg = theme + .button_color(button, ButtonState::Disabled, false, false) + .into(); + let disabled_active_icon = theme + .button_color(button, ButtonState::Disabled, true, true) + .into(); + let disabled_inactive_icon = theme + .button_color(button, ButtonState::Disabled, true, false) + .into(); + let disabled_bg = ColorSpec { + active: disabled_active_bg, + inactive: disabled_inactive_bg, + }; + let disabled_icon = ColorSpec { + active: disabled_active_icon, + inactive: disabled_inactive_icon, + }; + + let button_bg = ButtonColorSpec { + idle: idle_bg, + hovered: hovered_bg, + disabled: disabled_bg, + }; + let button_icon = ButtonColorSpec { + idle: idle_icon, + hovered: hovered_icon, + disabled: disabled_icon, + }; + + (button_icon, button_bg) + }) + .collect(); + + let minimize_button = Some(buttons[0]); + let maximize_button = Some(buttons[1]); + let close_button = Some(buttons[2]); + + // The first color is bar, then separator, and then text color. + let titlebar_colors: Vec = [Element::Bar, Element::Separator, Element::Text] + .iter() + .map(|element| { + let element = *element; + let active = theme.element_color(element, true).into(); + let inactive = theme.element_color(element, false).into(); + + ColorSpec { active, inactive } + }) + .collect(); + + let primary_color = titlebar_colors[0]; + let secondary_color = titlebar_colors[1]; + let title_color = titlebar_colors[2]; + + let title_font = theme.font(); + + let concept_config = ConceptConfig { + primary_color, + secondary_color, + title_color, + title_font, + minimize_button, + maximize_button, + close_button, + }; + + let theme_request = WindowRequest::Theme(concept_config); + self.window_requests.lock().unwrap().push(theme_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_cursor_icon(&self, cursor: CursorIcon) { + let cursor_icon_request = WindowRequest::NewCursorIcon(cursor); + self.window_requests + .lock() + .unwrap() + .push(cursor_icon_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_cursor_visible(&self, visible: bool) { + let cursor_visible_request = WindowRequest::ShowCursor(visible); + self.window_requests + .lock() + .unwrap() + .push(cursor_visible_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { + if !self.windowing_features.cursor_grab() { + return Err(ExternalError::NotSupported(NotSupportedError::new())); + } + + let cursor_grab_request = WindowRequest::GrabCursor(grab); + self.window_requests + .lock() + .unwrap() + .push(cursor_grab_request); + self.event_loop_awakener.ping(); + + Ok(()) + } + + #[inline] + pub fn set_cursor_position(&self, _: Position) -> Result<(), ExternalError> { + // XXX This is possible if the locked pointer is being used. We don't have any + // API for that right now, but it could be added in + // https://github.com/rust-windowing/winit/issues/1677. + // + // This function is essential for the locked pointer API. + // + // See pointer-constraints-unstable-v1.xml. + Err(ExternalError::NotSupported(NotSupportedError::new())) + } + + #[inline] + pub fn set_ime_position(&self, position: Position) { + let scale_factor = self.scale_factor() as f64; + let position = position.to_logical(scale_factor); + let ime_position_request = WindowRequest::IMEPosition(position); + self.window_requests + .lock() + .unwrap() + .push(ime_position_request); + self.event_loop_awakener.ping(); + } + + #[inline] + pub fn display(&self) -> &Display { + &self.display + } + + #[inline] + pub fn surface(&self) -> &WlSurface { + &self.surface + } + + #[inline] + pub fn current_monitor(&self) -> Option { + let output = sctk::get_surface_outputs(&self.surface).last()?.clone(); + Some(MonitorHandle::new(output)) + } + + #[inline] + pub fn available_monitors(&self) -> VecDeque { + self.output_manager_handle.available_outputs() + } + + #[inline] + pub fn primary_monitor(&self) -> Option { + None + } + + #[inline] + pub fn raw_window_handle(&self) -> WaylandHandle { + let display = self.display.get_display_ptr() as *mut _; + let surface = self.surface.as_ref().c_ptr() as *mut _; + + WaylandHandle { + display, + surface, + ..WaylandHandle::empty() + } + } +} + +impl From for ARGBColor { + fn from(color: LocalARGBColor) -> Self { + let a = color.a; + let r = color.r; + let g = color.g; + let b = color.b; + Self { a, r, g, b } + } +} + +impl Drop for Window { + fn drop(&mut self) { + let close_request = WindowRequest::Close; + self.window_requests.lock().unwrap().push(close_request); + self.event_loop_awakener.ping(); + } +} diff --git a/src/platform_impl/linux/wayland/window/shim.rs b/src/platform_impl/linux/wayland/window/shim.rs new file mode 100644 index 0000000000..59cf921a59 --- /dev/null +++ b/src/platform_impl/linux/wayland/window/shim.rs @@ -0,0 +1,388 @@ +use std::cell::Cell; +use std::sync::{Arc, Mutex}; + +use sctk::reexports::client::protocol::wl_output::WlOutput; + +use sctk::window::{ConceptConfig, ConceptFrame, Decorations, Window}; + +use crate::dpi::{LogicalPosition, LogicalSize}; + +use crate::event::WindowEvent; +use crate::platform_impl::wayland::event_loop::WinitState; +use crate::platform_impl::wayland::seat::pointer::WinitPointer; +use crate::platform_impl::wayland::seat::text_input::TextInputHandler; +use crate::platform_impl::wayland::WindowId; +use crate::window::CursorIcon; + +/// A request to SCTK window from Winit window. +#[derive(Debug, Clone)] +pub enum WindowRequest { + /// Set fullscreen. + /// + /// Passing `None` will set it on the current monitor. + Fullscreen(Option), + + /// Unset fullscreen. + UnsetFullscreen, + + /// Show cursor for the certain window or not. + ShowCursor(bool), + + /// Change the cursor icon. + NewCursorIcon(CursorIcon), + + /// Grab cursor. + GrabCursor(bool), + + /// Maximize the window. + Maximize(bool), + + /// Minimize the window. + Minimize, + + /// Request decorations change. + Decorate(bool), + + /// Make the window resizeable. + Resizeable(bool), + + /// Set the title for window. + Title(String), + + /// Min size. + MinSize(Option>), + + /// Max size. + MaxSize(Option>), + + /// New frame size. + FrameSize(LogicalSize), + + /// Set IME window position. + IMEPosition(LogicalPosition), + + /// Redraw was requested. + Redraw, + + /// A new theme for a concept frame was requested. + Theme(ConceptConfig), + + /// Window should be closed. + Close, +} + +/// Pending update to a window from SCTK window. +#[derive(Debug, Clone, Copy)] +pub struct WindowUpdate { + /// New window size. + pub size: Option>, + + /// New scale factor. + pub scale_factor: Option, + + /// Whether `redraw` was requested. + pub redraw_requested: bool, + + /// Wether the frame should be refreshed. + pub refresh_frame: bool, + + /// Close the window. + pub close_window: bool, +} + +impl WindowUpdate { + pub fn new() -> Self { + Self { + size: None, + scale_factor: None, + redraw_requested: false, + refresh_frame: false, + close_window: false, + } + } + + pub fn take(&mut self) -> Self { + let size = self.size.take(); + let scale_factor = self.scale_factor.take(); + + let redraw_requested = self.redraw_requested; + self.redraw_requested = false; + + let refresh_frame = self.refresh_frame; + self.refresh_frame = false; + + let close_window = self.close_window; + self.close_window = false; + + Self { + size, + scale_factor, + redraw_requested, + refresh_frame, + close_window, + } + } +} + +/// A handle to perform operations on SCTK window +/// and react to events. +pub struct WindowHandle { + /// An actual window. + pub window: Window, + + /// The current size of the window. + pub size: Arc>>, + + /// A pending requests to SCTK window. + pub pending_window_requests: Arc>>, + + /// Current cursor icon. + pub cursor_icon: Cell, + + /// Visible cursor or not. + cursor_visible: Cell, + + /// Cursor confined to the surface. + confined: Cell, + + /// Pointers over the current surface. + pointers: Vec, + + /// Text inputs on the current surface. + text_inputs: Vec, +} + +impl WindowHandle { + pub fn new( + window: Window, + size: Arc>>, + pending_window_requests: Arc>>, + ) -> Self { + Self { + window, + size, + pending_window_requests, + cursor_icon: Cell::new(CursorIcon::Default), + confined: Cell::new(false), + cursor_visible: Cell::new(true), + pointers: Vec::new(), + text_inputs: Vec::new(), + } + } + + pub fn set_cursor_grab(&self, grab: bool) { + // The new requested state matches the current confine status, return. + if self.confined.get() == grab { + return; + } + + self.confined.replace(grab); + + for pointer in self.pointers.iter() { + if self.confined.get() { + let surface = self.window.surface(); + pointer.confine(&surface); + } else { + pointer.unconfine(); + } + } + } + + /// Pointer appeared over the window. + pub fn pointer_entered(&mut self, pointer: WinitPointer) { + let position = self.pointers.iter().position(|p| *p == pointer); + + if position.is_none() { + if self.confined.get() { + let surface = self.window.surface(); + pointer.confine(&surface); + } + self.pointers.push(pointer); + } + + // Apply the current cursor style. + self.set_cursor_visible(self.cursor_visible.get()); + } + + /// Pointer left the window. + pub fn pointer_left(&mut self, pointer: WinitPointer) { + let position = self.pointers.iter().position(|p| *p == pointer); + + if let Some(position) = position { + let pointer = self.pointers.remove(position); + + // Drop the confined pointer. + if self.confined.get() { + pointer.unconfine(); + } + } + } + + pub fn text_input_entered(&mut self, text_input: TextInputHandler) { + if self + .text_inputs + .iter() + .find(|t| *t == &text_input) + .is_none() + { + self.text_inputs.push(text_input); + } + } + + pub fn text_input_left(&mut self, text_input: TextInputHandler) { + if let Some(position) = self.text_inputs.iter().position(|t| *t == text_input) { + self.text_inputs.remove(position); + } + } + + pub fn set_ime_position(&self, position: LogicalPosition) { + // XXX This won't fly unless user will have a way to request IME window per seat, since + // the ime windows will be overlapping, but winit doesn't expose API to specify for + // which seat we're setting IME position. + let (x, y) = (position.x as i32, position.y as i32); + for text_input in self.text_inputs.iter() { + text_input.set_ime_position(x, y); + } + } + + pub fn set_cursor_visible(&self, visible: bool) { + self.cursor_visible.replace(visible); + let cursor_icon = match visible { + true => Some(self.cursor_icon.get()), + false => None, + }; + + for pointer in self.pointers.iter() { + pointer.set_cursor(cursor_icon) + } + } + + pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) { + self.cursor_icon.replace(cursor_icon); + + if !self.cursor_visible.get() { + return; + } + + for pointer in self.pointers.iter() { + pointer.set_cursor(Some(cursor_icon)); + } + } +} + +#[inline] +pub fn handle_window_requests(winit_state: &mut WinitState) { + let window_map = &mut winit_state.window_map; + let window_updates = &mut winit_state.window_updates; + let mut windows_to_close: Vec = Vec::new(); + + // Process the rest of the events. + for (window_id, window_handle) in window_map.iter_mut() { + let mut requests = window_handle.pending_window_requests.lock().unwrap(); + for request in requests.drain(..) { + match request { + WindowRequest::Fullscreen(fullscreen) => { + window_handle.window.set_fullscreen(fullscreen.as_ref()); + } + WindowRequest::UnsetFullscreen => { + window_handle.window.unset_fullscreen(); + } + WindowRequest::ShowCursor(show_cursor) => { + window_handle.set_cursor_visible(show_cursor); + } + WindowRequest::NewCursorIcon(cursor_icon) => { + window_handle.set_cursor_icon(cursor_icon); + } + WindowRequest::IMEPosition(position) => { + window_handle.set_ime_position(position); + } + WindowRequest::GrabCursor(grab) => { + window_handle.set_cursor_grab(grab); + } + WindowRequest::Maximize(maximize) => { + if maximize { + window_handle.window.set_maximized(); + } else { + window_handle.window.unset_maximized(); + } + } + WindowRequest::Minimize => { + window_handle.window.set_minimized(); + } + WindowRequest::Decorate(decorate) => { + let decorations = match decorate { + true => Decorations::FollowServer, + false => Decorations::None, + }; + + window_handle.window.set_decorate(decorations); + + // We should refresh the frame to apply decorations change. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::Resizeable(resizeable) => { + window_handle.window.set_resizable(resizeable); + + // We should refresh the frame to update button state. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::Title(title) => { + window_handle.window.set_title(title); + + // We should refresh the frame to draw new title. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::MinSize(size) => { + let size = size.map(|size| (size.width, size.height)); + window_handle.window.set_min_size(size); + + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.redraw_requested = true; + } + WindowRequest::MaxSize(size) => { + let size = size.map(|size| (size.width, size.height)); + window_handle.window.set_max_size(size); + + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.redraw_requested = true; + } + WindowRequest::FrameSize(size) => { + // Set new size. + window_handle.window.resize(size.width, size.height); + + // We should refresh the frame after resize. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::Redraw => { + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.redraw_requested = true; + } + WindowRequest::Theme(concept_config) => { + window_handle.window.set_frame_config(concept_config); + + // We should refresh the frame to apply new theme. + let window_update = window_updates.get_mut(&window_id).unwrap(); + window_update.refresh_frame = true; + } + WindowRequest::Close => { + // The window was requested to be closed. + windows_to_close.push(*window_id); + + // Send event that the window was destroyed. + let event_sink = &mut winit_state.event_sink; + event_sink.push_window_event(WindowEvent::Destroyed, *window_id); + } + }; + } + } + + // Close the windows. + for window in windows_to_close { + let _ = window_map.remove(&window); + let _ = window_updates.remove(&window); + } +} diff --git a/src/window.rs b/src/window.rs index d6cd8e6c4e..ed8e7af3d3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -614,7 +614,7 @@ impl Window { /// /// The dock and the menu bar are always disabled in fullscreen mode. /// - **iOS:** Can only be called on the main thread. - /// - **Wayland:** Does not support exclusive fullscreen mode. + /// - **Wayland:** Does not support exclusive fullscreen mode and will no-op a request. /// - **Windows:** Screen saver is disabled in fullscreen mode. /// - **Android:** Unsupported. #[inline] @@ -677,7 +677,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland / Windows:** Unsupported. + /// - **iOS / Android / Web / Windows:** Unsupported. #[inline] pub fn set_ime_position>(&self, position: P) { self.window.set_ime_position(position.into()) @@ -708,9 +708,12 @@ impl Window { /// Grabs the cursor, preventing it from leaving the window. /// + /// There's no guarantee that the cursor will be hidden. You should + /// hide it by yourself if you want so. + /// /// ## Platform-specific /// - /// - **macOS / Wayland:** This locks the cursor in a fixed location, which looks visually awkward. + /// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward. /// - **iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`]. #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { From b9f3d333e41464457f6e42640793bf88b9563727 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Thu, 1 Oct 2020 01:19:15 +0300 Subject: [PATCH 192/239] Update SCTK to 0.12 SCTK was a bit behind on Wayland protocol version, and so this release brings it up to date. It also cleans up 'Environment'. --- CHANGELOG.md | 1 + Cargo.toml | 4 ++-- src/platform_impl/linux/wayland/event_loop/mod.rs | 6 +----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dc3fd1836..9fc06c8f65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ - **Breaking:** On Wayland, `Theme` trait was reworked. - On Wayland, disable maximize button for non-resizable window. - On Wayland, added support for `set_ime_position`. +- On Wayland, fix crash on startup since GNOME 3.37.90. # 0.22.2 (2020-05-16) diff --git a/Cargo.toml b/Cargo.toml index b10c4b8a2a..d8a1f5d1cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,8 +81,8 @@ features = [ ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] -wayland-client = { version = "0.27", features = [ "dlopen"] , optional = true } -sctk = { package = "smithay-client-toolkit", version = "0.11", optional = true } +wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true } +sctk = { package = "smithay-client-toolkit", version = "0.12", optional = true } mio = "0.6" mio-extras = "2.0" x11-dl = { version = "2.18.5", optional = true } diff --git a/src/platform_impl/linux/wayland/event_loop/mod.rs b/src/platform_impl/linux/wayland/event_loop/mod.rs index 9e7cdde304..8c2d86a5c9 100644 --- a/src/platform_impl/linux/wayland/event_loop/mod.rs +++ b/src/platform_impl/linux/wayland/event_loop/mod.rs @@ -99,11 +99,7 @@ impl EventLoop { let display_proxy = display.attach(event_queue.token()); // Setup environment. - let env = Environment::init(&display_proxy, WinitEnv::new()); - - // Issue 2 sync roundtrips to initialize environment. - event_queue.sync_roundtrip(&mut (), |_, _, _| unreachable!())?; - event_queue.sync_roundtrip(&mut (), |_, _, _| unreachable!())?; + let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?; // Create event loop. let event_loop = calloop::EventLoop::::new()?; From fc336a76bfa158c972a6303ce638359343260fad Mon Sep 17 00:00:00 2001 From: Nathan Lilienthal Date: Thu, 1 Oct 2020 22:07:09 -0400 Subject: [PATCH 193/239] Fix incorrect modifiers state on startup Fixes #1563. --- CHANGELOG.md | 1 + .../linux/x11/event_processor.rs | 56 ++++++++++++------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fc06c8f65..f151fcfb97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ - On Wayland, disable maximize button for non-resizable window. - On Wayland, added support for `set_ime_position`. - On Wayland, fix crash on startup since GNOME 3.37.90. +- On X11, fix incorrect modifiers state on startup. # 0.22.2 (2020-05-16) diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 976c17b62a..ab87d76d1e 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -20,6 +20,9 @@ use crate::{ event_loop::EventLoopWindowTarget as RootELW, }; +/// The X11 documentation states: "Keycodes lie in the inclusive range [8,255]". +const KEYCODE_OFFSET: u8 = 8; + pub(super) struct EventProcessor { pub(super) dnd: Dnd, pub(super) ime_receiver: ImeReceiver, @@ -565,7 +568,7 @@ impl EventProcessor { // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with // a keycode of 0. if keycode != 0 { - let scancode = keycode - 8; + let scancode = keycode - KEYCODE_OFFSET as u32; let keysym = wt.xconn.lookup_keysym(xkev); let virtual_keycode = events::keysym_to_element(keysym as c_uint); @@ -921,9 +924,12 @@ impl EventProcessor { }); // Issue key press events for all pressed keys - self.handle_pressed_keys( + Self::handle_pressed_keys( + &wt, window_id, ElementState::Pressed, + &self.mod_keymap, + &mut self.device_mod_state, &mut callback, ); } @@ -942,9 +948,12 @@ impl EventProcessor { let window_id = mkwid(xev.event); // Issue key release events for all pressed keys - self.handle_pressed_keys( + Self::handle_pressed_keys( + &wt, window_id, ElementState::Released, + &self.mod_keymap, + &mut self.device_mod_state, &mut callback, ); @@ -1081,10 +1090,10 @@ impl EventProcessor { let device_id = mkdid(xev.sourceid); let keycode = xev.detail; - if keycode < 8 { + let scancode = keycode - KEYCODE_OFFSET as i32; + if scancode < 0 { return; } - let scancode = (keycode - 8) as u32; let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode); let virtual_keycode = events::keysym_to_element(keysym as c_uint); let modifiers = self.device_mod_state.modifiers(); @@ -1093,7 +1102,7 @@ impl EventProcessor { callback(Event::DeviceEvent { device_id, event: DeviceEvent::Key(KeyboardInput { - scancode, + scancode: scancode as u32, virtual_keycode, state, modifiers, @@ -1221,30 +1230,37 @@ impl EventProcessor { } fn handle_pressed_keys( - &self, + wt: &super::EventLoopWindowTarget, window_id: crate::window::WindowId, state: ElementState, + mod_keymap: &ModifierKeymap, + device_mod_state: &mut ModifierKeyState, callback: &mut F, ) where F: FnMut(Event<'_, T>), { - let wt = get_xtarget(&self.target); - let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); - let modifiers = self.device_mod_state.modifiers(); - - // Get the set of keys currently pressed and apply Key events to each - let keys = wt.xconn.query_keymap(); - - for keycode in &keys { - if keycode < 8 { - continue; - } - - let scancode = (keycode - 8) as u32; + let modifiers = device_mod_state.modifiers(); + + // Update modifiers state and emit key events based on which keys are currently pressed. + for keycode in wt + .xconn + .query_keymap() + .into_iter() + .filter(|k| *k >= KEYCODE_OFFSET) + { + let scancode = (keycode - KEYCODE_OFFSET) as u32; let keysym = wt.xconn.keycode_to_keysym(keycode); let virtual_keycode = events::keysym_to_element(keysym as c_uint); + if let Some(modifier) = mod_keymap.get_modifier(keycode as ffi::KeyCode) { + device_mod_state.key_event( + ElementState::Pressed, + keycode as ffi::KeyCode, + modifier, + ); + } + #[allow(deprecated)] callback(Event::WindowEvent { window_id, From d18afb4a505783e4447393c879c292559b83195d Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 2 Oct 2020 18:05:07 +0300 Subject: [PATCH 194/239] Release 0.23.0 --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f151fcfb97..b9b08085d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 0.23.0 (2020-10-02) + - On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode. - On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop. - On Unix, X11 and Wayland are now optional features (enabled by default) diff --git a/Cargo.toml b/Cargo.toml index d8a1f5d1cd..8233aadb36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.22.2" +version = "0.23.0" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index 86a31b9b5e..daff218581 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```toml [dependencies] -winit = "0.22.2" +winit = "0.23.0" ``` ## [Documentation](https://docs.rs/winit) From 676fb947f2a8366c650bb9788f80657b6d7865df Mon Sep 17 00:00:00 2001 From: Max de Danschutter <43446207+maxded@users.noreply.github.com> Date: Thu, 8 Oct 2020 19:44:41 +0200 Subject: [PATCH 195/239] Added WindowHasFocus and WindowLostFocus events to Android (#1733) * Added WindowHasFocus and WindowLostFocus events to Android * Update changelog --- CHANGELOG.md | 2 ++ src/platform_impl/android/mod.rs | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b08085d9..ffc58f6a49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. + # 0.23.0 (2020-10-02) - On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode. diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 471dd15d54..be19af4f87 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -148,6 +148,28 @@ impl EventLoop { ); } } + Event::WindowHasFocus => { + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::Focused(true), + } + ); + } + Event::WindowLostFocus => { + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::Focused(false), + } + ); + } _ => {} }, Some(EventSource::InputQueue) => { From 5a78fe33e84023ca4c89cc30ebeebac0fe89e8f0 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Sat, 10 Oct 2020 06:31:51 +0200 Subject: [PATCH 196/239] Fix failing assertion on start-up with Safari (#1736) The initial media query that's used to watch for device pixel ratio changes should match. Unfortunately it doesn't with Safari and the corresponding assertion fails. This is because resolution is not a supported support property for queries. As a workaround, this patch extends the query to optionally match for the webkit specific DPR property directly. This fixes #1734 --- src/platform_impl/web/web_sys/scaling.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform_impl/web/web_sys/scaling.rs b/src/platform_impl/web/web_sys/scaling.rs index 2129a165d7..08b52ae306 100644 --- a/src/platform_impl/web/web_sys/scaling.rs +++ b/src/platform_impl/web/web_sys/scaling.rs @@ -59,9 +59,9 @@ impl ScaleChangeDetectorInternal { // We add 0.0001 to the lower and upper bounds such that it won't fail // due to floating point precision limitations. let media_query = format!( - "(min-resolution: {:.4}dppx) and (max-resolution: {:.4}dppx)", - current_scale - 0.0001, - current_scale + 0.0001, + "(min-resolution: {min_scale:.4}dppx) and (max-resolution: {max_scale:.4}dppx), + (-webkit-min-device-pixel-ratio: {min_scale:.4}) and (-webkit-max-device-pixel-ratio: {max_scale:.4})", + min_scale = current_scale - 0.0001, max_scale= current_scale + 0.0001, ); let mql = MediaQueryListHandle::new(&media_query, closure); if let Some(mql) = &mql { From 6343059bc0165c87930912c80e831d1f235a7591 Mon Sep 17 00:00:00 2001 From: Jim Porter <826865+jimporter@users.noreply.github.com> Date: Wed, 14 Oct 2020 03:23:34 -0700 Subject: [PATCH 197/239] Fix Windows transparency behavior to support fully-opaque regions (#1621) This patch removes an unneeded workaround for transparent windows on the Windows platform. In addition, it simplifies a couple of related API calls: * Remove the `CreateRectRgn` call, since we want the entire window's region to have blur behind it, and `DwnEnableBlurBehindWindow` does that by default. * Remove the `color_key` for `SetLayeredWindowAttributes`, since it's not used (we're not passing `winuser::LWA_COLORKEY` to the flags). --- CHANGELOG.md | 1 + src/platform_impl/windows/window.rs | 25 +++++-------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffc58f6a49..c360b614be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. +- On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 6253c94373..bd2362facc 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -23,7 +23,6 @@ use winapi::{ ole2, oleidl::LPDROPTARGET, shobjidl_core::{CLSID_TaskbarList, ITaskbarList2}, - wingdi::{CreateRectRgn, DeleteObject}, winnt::LPCWSTR, winuser, }, @@ -708,33 +707,19 @@ unsafe fn init( // making the window transparent if attributes.transparent && !pl_attribs.no_redirection_bitmap { - let region = CreateRectRgn(0, 0, -1, -1); // makes the window transparent - let bb = dwmapi::DWM_BLURBEHIND { - dwFlags: dwmapi::DWM_BB_ENABLE | dwmapi::DWM_BB_BLURREGION, + dwFlags: dwmapi::DWM_BB_ENABLE, fEnable: 1, - hRgnBlur: region, + hRgnBlur: ptr::null_mut(), fTransitionOnMaximized: 0, }; dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb); - DeleteObject(region as _); if attributes.decorations { - // HACK: When opaque (opacity 255), there is a trail whenever - // the transparent window is moved. By reducing it to 254, - // the window is rendered properly. - let opacity = 254; - - // The color key can be any value except for black (0x0). - let color_key = 0x0030c100; - - winuser::SetLayeredWindowAttributes( - real_window.0, - color_key, - opacity, - winuser::LWA_ALPHA, - ); + let opacity = 255; + + winuser::SetLayeredWindowAttributes(real_window.0, 0, opacity, winuser::LWA_ALPHA); } } From 96809ac65998576e26e95b879bb63fab6ae2b7ca Mon Sep 17 00:00:00 2001 From: Murarth Date: Thu, 15 Oct 2020 11:33:06 -0700 Subject: [PATCH 198/239] Fix warnings (#1742) --- src/lib.rs | 2 +- src/platform_impl/web/mod.rs | 3 +++ src/platform_impl/web/stdweb/canvas.rs | 4 +--- src/platform_impl/web/stdweb/mod.rs | 2 -- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4a45022c36..e0f3c2994e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,7 +130,7 @@ //! [`raw_window_handle`]: ./window/struct.Window.html#method.raw_window_handle #![deny(rust_2018_idioms)] -#![deny(intra_doc_link_resolution_failure)] +#![deny(broken_intra_doc_links)] #[allow(unused_imports)] #[macro_use] diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index ed170b83b2..1eb479173c 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -17,6 +17,9 @@ // incoming events (from the registered handlers) and ensuring they are passed to the user in a // compliant way. +// Silence warnings from use of deprecated stdweb backend +#![allow(deprecated)] + mod device; mod error; mod event_loop; diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index b9e0c9d015..cd952abe35 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -15,9 +15,7 @@ use stdweb::web::event::{ PointerOutEvent, PointerOverEvent, PointerUpEvent, }; use stdweb::web::html_element::CanvasElement; -use stdweb::web::{ - document, EventListenerHandle, IChildNode, IElement, IEventTarget, IHtmlElement, -}; +use stdweb::web::{document, EventListenerHandle, IElement, IEventTarget, IHtmlElement}; pub struct Canvas { /// Note: resizing the CanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index 23009b28f4..e7a6122791 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -63,8 +63,6 @@ pub fn scale_factor() -> f64 { } pub fn set_canvas_size(raw: &CanvasElement, size: Size) { - use stdweb::*; - let scale_factor = scale_factor(); let physical_size = size.to_physical::(scale_factor); From ee3996cac6766780d0f52b734d5c0ad104da52da Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 18 Oct 2020 02:05:08 +0300 Subject: [PATCH 199/239] Feature gate more dependencies Wayland backend is self contained and only requires smithay-client-toolkit to work, thus feature gate the rest of the deps that are only for X11. --- Cargo.toml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8233aadb36..6a316d0cff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux default = ["x11", "wayland"] web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"] stdweb = ["std_web", "instant/stdweb"] -x11 = ["x11-dl"] +x11 = ["x11-dl", "mio", "mio-extras", "percent-encoding", "parking_lot"] wayland = ["wayland-client", "sctk"] [dependencies] @@ -41,21 +41,23 @@ ndk = "0.2.0" ndk-sys = "0.2.0" ndk-glue = "0.2.0" -[target.'cfg(target_os = "ios")'.dependencies] -objc = "0.2.3" +[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] +objc = "0.2.7" [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.23" +cocoa = "0.24" core-foundation = "0.9" core-graphics = "0.22" dispatch = "0.2.0" -objc = "0.2.6" [target.'cfg(target_os = "macos")'.dependencies.core-video-sys] version = "0.1.4" default_features = false features = ["display_link"] +[target.'cfg(target_os = "windows")'.dependencies] +parking_lot = "0.11" + [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" features = [ @@ -83,13 +85,11 @@ features = [ [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true } sctk = { package = "smithay-client-toolkit", version = "0.12", optional = true } -mio = "0.6" -mio-extras = "2.0" +mio = { version = "0.6", optional = true } +mio-extras = { version = "2.0", optional = true } x11-dl = { version = "2.18.5", optional = true } -percent-encoding = "2.0" - -[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot] -version = "0.11" +percent-encoding = { version = "2.0", optional = true } +parking_lot = { version = "0.11.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies.web_sys] package = "web-sys" From 7c543a43a9563f7691ff62f3ef8c8415e867ac55 Mon Sep 17 00:00:00 2001 From: Alex Butler Date: Mon, 19 Oct 2020 15:15:23 +0100 Subject: [PATCH 200/239] Windows: Fix alt tab bordless fullscreen (#1740) --- CHANGELOG.md | 1 + src/platform_impl/windows/window.rs | 9 ++++++++- src/platform_impl/windows/window_state.rs | 15 ++++++++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c360b614be..229b5596f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. +- On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index bd2362facc..b53347502f 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -489,7 +489,14 @@ impl Window { // Update window style WindowState::set_window_flags(window_state_lock, window.0, |f| { - f.set(WindowFlags::MARKER_FULLSCREEN, fullscreen.is_some()) + f.set( + WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN, + matches!(fullscreen, Some(Fullscreen::Exclusive(_))), + ); + f.set( + WindowFlags::MARKER_BORDERLESS_FULLSCREEN, + matches!(fullscreen, Some(Fullscreen::Borderless(_))), + ); }); // Update window bounds diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 124090813c..9ea7c2dacb 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -71,7 +71,8 @@ bitflags! { /// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is /// included here to make masking easier. - const MARKER_FULLSCREEN = 1 << 9; + const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 9; + const MARKER_BORDERLESS_FULLSCREEN = 1 << 13; /// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`. /// In most cases, it's okay to let those parameters change the state. However, when we're @@ -89,7 +90,7 @@ bitflags! { WindowFlags::RESIZABLE.bits | WindowFlags::MAXIMIZED.bits ); - const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits; + const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits; const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; } @@ -176,9 +177,11 @@ impl MouseProperties { impl WindowFlags { fn mask(mut self) -> WindowFlags { - if self.contains(WindowFlags::MARKER_FULLSCREEN) { + if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) { + self &= WindowFlags::FULLSCREEN_AND_MASK; + self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK; + } else if self.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN) { self &= WindowFlags::FULLSCREEN_AND_MASK; - self |= WindowFlags::FULLSCREEN_OR_MASK; } if !self.contains(WindowFlags::VISIBLE) { self &= WindowFlags::INVISIBLE_AND_MASK; @@ -319,7 +322,9 @@ impl WindowFlags { // We generally don't want style changes here to affect window // focus, but for fullscreen windows they must be activated // (i.e. focused) so that they appear on top of the taskbar - if !new.contains(WindowFlags::MARKER_FULLSCREEN) { + if !new.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) + && !new.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN) + { flags |= winuser::SWP_NOACTIVATE; } From fbd3918d3af183e5c3d76b5644672e54d87fa84c Mon Sep 17 00:00:00 2001 From: Vickles <64216837+vickles@users.noreply.github.com> Date: Mon, 19 Oct 2020 15:35:01 +0100 Subject: [PATCH 201/239] Add prefix byte for extended scancodes on Windows (#1679) --- CHANGELOG.md | 1 + src/platform_impl/windows/event.rs | 30 +++++++++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 229b5596f6..104031ef4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. - On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. +- **Breaking:** On Windows, include prefix byte in scancodes. # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 4b3b68d919..161cf6b2de 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -343,6 +343,7 @@ pub fn handle_extended_keys( extended: bool, ) -> Option<(c_int, UINT)> { // Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/ + scancode = if extended { 0xE000 } else { 0x0000 } | scancode; let vkey = match vkey { winuser::VK_SHIFT => unsafe { winuser::MapVirtualKeyA(scancode, winuser::MAPVK_VSC_TO_VK_EX) as _ @@ -363,20 +364,23 @@ pub fn handle_extended_keys( } _ => { match scancode { - // This is only triggered when using raw input. Without this check, we get two events whenever VK_PAUSE is - // pressed, the first one having scancode 0x1D but vkey VK_PAUSE... - 0x1D if vkey == winuser::VK_PAUSE => return None, - // ...and the second having scancode 0x45 but an unmatched vkey! - 0x45 => winuser::VK_PAUSE, - // VK_PAUSE and VK_SCROLL have the same scancode when using modifiers, alongside incorrect vkey values. - 0x46 => { - if extended { - scancode = 0x45; - winuser::VK_PAUSE - } else { - winuser::VK_SCROLL - } + // When VK_PAUSE is pressed it emits a LeftControl + NumLock scancode event sequence, but reports VK_PAUSE + // as the virtual key on both events, or VK_PAUSE on the first event or 0xFF when using raw input. + // Don't emit anything for the LeftControl event in the pair... + 0xE01D if vkey == winuser::VK_PAUSE => return None, + // ...and emit the Pause event for the second event in the pair. + 0x45 if vkey == winuser::VK_PAUSE || vkey == 0xFF as _ => { + scancode = 0xE059; + winuser::VK_PAUSE } + // VK_PAUSE has an incorrect vkey value when used with modifiers. VK_PAUSE also reports a different + // scancode when used with modifiers than when used without + 0xE046 => { + scancode = 0xE059; + winuser::VK_PAUSE + } + // VK_SCROLL has an incorrect vkey value when used with modifiers. + 0x46 => winuser::VK_SCROLL, _ => vkey, } } From 037d4121a105c0d1e69956ff6804b7cfdbd9bc8b Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 20 Oct 2020 03:30:19 +0300 Subject: [PATCH 202/239] On Wayland, fix 'with_min_inner_size' disabling resize Building window with 'set_min_inner_size' was setting 'max_inner_size' under the hood, thus completely disabling window resize, since the window isn't resizeable on Wayland when its minimum size is equal to its maximum size. --- CHANGELOG.md | 1 + src/platform_impl/linux/wayland/window/mod.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 104031ef4d..7d224047ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. - **Breaking:** On Windows, include prefix byte in scancodes. +- On Wayland, fix window not being resizeable when using `with_min_inner_size` in `WindowBuilder`. # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 271e8058fd..d51f47a7a2 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -145,7 +145,7 @@ impl Window { // Max dimensions. let max_size = attributes - .min_inner_size + .max_inner_size .map(|size| size.to_logical::(scale_factor as f64).into()); window.set_max_size(max_size); From 8aa1be8336898c57910675502e3888c2a699f558 Mon Sep 17 00:00:00 2001 From: Waridley Date: Wed, 21 Oct 2020 23:14:33 -0500 Subject: [PATCH 203/239] On Unix, fix cross-compiling to wasm32 Aborting compilation by using 'compile_error!' macro in build.rs was resulting in failing cross compilation, thus this commit removes build.rs. The compilation will now be aborted on existing 'compile_error!' macros in corresponding platform sources. --- CHANGELOG.md | 1 + build.rs | 21 --------------------- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 build.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d224047ad..c3c425b545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. - **Breaking:** On Windows, include prefix byte in scancodes. - On Wayland, fix window not being resizeable when using `with_min_inner_size` in `WindowBuilder`. +- On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland. # 0.23.0 (2020-10-02) diff --git a/build.rs b/build.rs deleted file mode 100644 index f1c4e9bf89..0000000000 --- a/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -#[cfg(all( - any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ), - not(feature = "x11"), - not(feature = "wayland") -))] -compile_error!("at least one of the \"x11\"/\"wayland\" features must be enabled"); - -#[cfg(all( - target_arch = "wasm32", - not(feature = "web-sys"), - not(feature = "stdweb") -))] -compile_error!("at least one of the \"web-sys\"/\"stdweb\" features must be enabled"); - -fn main() {} From 66c117e599209ca1fb95219669ed161c1d3cc3ed Mon Sep 17 00:00:00 2001 From: qthree Date: Sat, 24 Oct 2020 00:04:18 +0700 Subject: [PATCH 204/239] [Windows] Fix use after free during window destruction (#1746) --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c425b545..5f75ec13d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - **Breaking:** On Windows, include prefix byte in scancodes. - On Wayland, fix window not being resizeable when using `with_min_inner_size` in `WindowBuilder`. - On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland. +- On Windows, fix use after free crash during window destruction. # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index a6d15fb87f..27af039fb6 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -810,7 +810,10 @@ unsafe extern "system" fn public_window_callback( event: Destroyed, }); subclass_input.event_loop_runner.remove_window(window); + 0 + } + winuser::WM_NCDESTROY => { drop(subclass_input); Box::from_raw(subclass_input_ptr as *mut SubclassInput); 0 From 33fb62bb259b05dcfd6f319328b2f90bd387df13 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Thu, 29 Oct 2020 22:13:21 +0100 Subject: [PATCH 205/239] Fix WindowEvent::ReceivedCharacter on web (#1747) * Fix WindowEvent::ReceivedCharacter on web The event was never sent to the application because of the unconditional preventDefault() call on keydown. Fixes #1741 * Don't scroll when pressing space on a focused canvas After reaching keypress, we should prevent further propagation. Relates to #1741 --- CHANGELOG.md | 1 + src/platform_impl/web/stdweb/canvas.rs | 20 ++++++++++++++++---- src/platform_impl/web/web_sys/canvas.rs | 14 +++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f75ec13d6..bf0ed4759d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - On Wayland, fix window not being resizeable when using `with_min_inner_size` in `WindowBuilder`. - On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland. - On Windows, fix use after free crash during window destruction. +- On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input. # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index cd952abe35..c68ec911aa 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -10,9 +10,9 @@ use stdweb::js; use stdweb::traits::IPointerEvent; use stdweb::unstable::TryInto; use stdweb::web::event::{ - BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, IEvent, KeyDownEvent, - KeyPressEvent, KeyUpEvent, MouseWheelEvent, PointerDownEvent, PointerMoveEvent, - PointerOutEvent, PointerOverEvent, PointerUpEvent, + BlurEvent, ConcreteEvent, FocusEvent, FullscreenChangeEvent, IEvent, IKeyboardEvent, + KeyDownEvent, KeyPressEvent, KeyUpEvent, ModifierKey, MouseWheelEvent, PointerDownEvent, + PointerMoveEvent, PointerOutEvent, PointerOverEvent, PointerUpEvent, }; use stdweb::web::html_element::CanvasElement; use stdweb::web::{document, EventListenerHandle, IElement, IEventTarget, IHtmlElement}; @@ -136,7 +136,17 @@ impl Canvas { F: 'static + FnMut(ScanCode, Option, ModifiersState), { self.on_keyboard_press = Some(self.add_user_event(move |event: KeyDownEvent| { - event.prevent_default(); + // event.prevent_default() would suppress subsequent on_received_character() calls. That + // supression is correct for key sequences like Tab/Shift-Tab, Ctrl+R, PgUp/Down to + // scroll, etc. We should not do it for key sequences that result in meaningful character + // input though. + let event_key = &event.key(); + let is_key_string = event_key.len() == 1 || !event_key.is_ascii(); + let is_shortcut_modifiers = (event.ctrl_key() || event.alt_key()) + && !event.get_modifier_state(ModifierKey::AltGr); + if !is_key_string || is_shortcut_modifiers { + event.prevent_default(); + } handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -155,6 +165,8 @@ impl Canvas { // viable/compatible alternative as of now. `beforeinput` is still widely // unsupported. self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| { + // Supress further handling to stop keys like the space key from scrolling the page. + event.prevent_default(); handler(event::codepoint(&event)); })); } diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 46d9531157..ae2fe0e186 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -157,7 +157,17 @@ impl Canvas { self.on_keyboard_press = Some(self.common.add_user_event( "keydown", move |event: KeyboardEvent| { - event.prevent_default(); + // event.prevent_default() would suppress subsequent on_received_character() calls. That + // supression is correct for key sequences like Tab/Shift-Tab, Ctrl+R, PgUp/Down to + // scroll, etc. We should not do it for key sequences that result in meaningful character + // input though. + let event_key = &event.key(); + let is_key_string = event_key.len() == 1 || !event_key.is_ascii(); + let is_shortcut_modifiers = + (event.ctrl_key() || event.alt_key()) && !event.get_modifier_state("AltGr"); + if !is_key_string || is_shortcut_modifiers { + event.prevent_default(); + } handler( event::scan_code(&event), event::virtual_key_code(&event), @@ -179,6 +189,8 @@ impl Canvas { self.on_received_character = Some(self.common.add_user_event( "keypress", move |event: KeyboardEvent| { + // Supress further handling to stop keys like the space key from scrolling the page. + event.prevent_default(); handler(event::codepoint(&event)); }, )); From be850e483af07cba6124311acab2cacd5925ba95 Mon Sep 17 00:00:00 2001 From: Brad Date: Thu, 29 Oct 2020 16:23:46 -0500 Subject: [PATCH 206/239] Document Android raw_window_handle requirements (#1749) * Add docs describing raw_winow_handle on android * Run cargo fmt * Change raw_window_handle panic to give more info --- src/platform_impl/android/mod.rs | 2 +- src/window.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index be19af4f87..c0f80dcab0 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -505,7 +505,7 @@ impl Window { let a_native_window = if let Some(native_window) = ndk_glue::native_window().as_ref() { unsafe { native_window.ptr().as_mut() as *mut _ as *mut _ } } else { - panic!("native window null"); + panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events."); }; let mut handle = raw_window_handle::android::AndroidHandle::empty(); handle.a_native_window = a_native_window; diff --git a/src/window.rs b/src/window.rs index ed8e7af3d3..c8f45d1067 100644 --- a/src/window.rs +++ b/src/window.rs @@ -784,6 +784,12 @@ impl Window { } unsafe impl raw_window_handle::HasRawWindowHandle for Window { + /// Returns a `raw_window_handle::RawWindowHandle` for the Window + /// + /// ## Platform-specific + /// + /// - **Android:** Only available after receiving the Resumed event and before Suspended. *If you* + /// *try to get the handle outside of that period, this function will panic*! fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { self.window.raw_window_handle() } From 3a077ff21125e9b130ab18596e9a363b5d9dcb86 Mon Sep 17 00:00:00 2001 From: Mikko Lehtonen Date: Mon, 2 Nov 2020 23:06:00 +0200 Subject: [PATCH 207/239] macos: Fix compile on aarch64 --- CHANGELOG.md | 1 + src/platform_impl/macos/util/async.rs | 3 ++- src/platform_impl/macos/view.rs | 2 +- src/platform_impl/macos/window.rs | 10 +++++----- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf0ed4759d..4a7fff41e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland. - On Windows, fix use after free crash during window destruction. - On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input. +- On macOS, fix compilation when targeting aarch64 # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index 977ba32807..96f8e5b099 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -10,6 +10,7 @@ use cocoa::{ }; use dispatch::Queue; use objc::rc::autoreleasepool; +use objc::runtime::NO; use crate::{ dpi::LogicalSize, @@ -167,7 +168,7 @@ pub unsafe fn set_maximized_async( } else { shared_state_lock.saved_standard_frame() }; - ns_window.setFrame_display_(new_rect, 0); + ns_window.setFrame_display_(new_rect, NO); } trace!("Unlocked shared state in `set_maximized`"); diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 2c379e0ac6..523cb665d5 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -388,7 +388,7 @@ extern "C" fn has_marked_text(this: &Object, _sel: Sel) -> BOOL { trace!("Triggered `hasMarkedText`"); let marked_text: id = *this.get_ivar("markedText"); trace!("Completed `hasMarkedText`"); - (marked_text.length() > 0) as i8 + (marked_text.length() > 0) as BOOL } } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 191ce60a9f..b28c860f08 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -653,7 +653,7 @@ impl UnownedWindow { self.set_style_mask_async(curr_mask); } - is_zoomed != 0 + is_zoomed != NO } fn saved_style(&self, shared_state: &mut SharedState) -> NSWindowStyleMask { @@ -1168,14 +1168,14 @@ unsafe fn set_min_inner_size(window: V, mut min_size: Logica // If necessary, resize the window to match constraint if current_rect.size.width < min_size.width { current_rect.size.width = min_size.width; - window.setFrame_display_(current_rect, 0) + window.setFrame_display_(current_rect, NO) } if current_rect.size.height < min_size.height { // The origin point of a rectangle is at its bottom left in Cocoa. // To ensure the window's top-left point remains the same: current_rect.origin.y += current_rect.size.height - min_size.height; current_rect.size.height = min_size.height; - window.setFrame_display_(current_rect, 0) + window.setFrame_display_(current_rect, NO) } } @@ -1192,13 +1192,13 @@ unsafe fn set_max_inner_size(window: V, mut max_size: Logica // If necessary, resize the window to match constraint if current_rect.size.width > max_size.width { current_rect.size.width = max_size.width; - window.setFrame_display_(current_rect, 0) + window.setFrame_display_(current_rect, NO) } if current_rect.size.height > max_size.height { // The origin point of a rectangle is at its bottom left in Cocoa. // To ensure the window's top-left point remains the same: current_rect.origin.y += current_rect.size.height - max_size.height; current_rect.size.height = max_size.height; - window.setFrame_display_(current_rect, 0) + window.setFrame_display_(current_rect, NO) } } From 45e4fd6ec110ca41253df447f332966da62a9d01 Mon Sep 17 00:00:00 2001 From: Murarth Date: Thu, 5 Nov 2020 16:42:03 -0700 Subject: [PATCH 208/239] X11: Fix `request_redraw` not waking the event loop (#1756) --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/mod.rs | 64 +++++++++++++++------------ src/platform_impl/linux/x11/window.rs | 14 +++--- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a7fff41e8..903c3c700c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - On Windows, fix use after free crash during window destruction. - On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input. - On macOS, fix compilation when targeting aarch64 +- On X11, fix `Window::request_redraw` not waking the event loop. # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 0849c0f919..ffb9fc6e65 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -24,7 +24,7 @@ pub use self::{ use std::{ cell::RefCell, - collections::{HashMap, HashSet}, + collections::HashMap, ffi::CStr, mem::{self, MaybeUninit}, ops::Deref, @@ -32,7 +32,7 @@ use std::{ ptr, rc::Rc, slice, - sync::{mpsc, Arc, Mutex, Weak}, + sync::{mpsc, Arc, Weak}, time::{Duration, Instant}, }; @@ -58,6 +58,7 @@ use crate::{ const X_TOKEN: Token = Token(0); const USER_TOKEN: Token = Token(1); +const REDRAW_TOKEN: Token = Token(2); pub struct EventLoopWindowTarget { xconn: Arc, @@ -67,13 +68,14 @@ pub struct EventLoopWindowTarget { root: ffi::Window, ime: RefCell, windows: RefCell>>, - pending_redraws: Arc>>, + redraw_sender: Sender, _marker: ::std::marker::PhantomData, } pub struct EventLoop { poll: Poll, event_processor: EventProcessor, + redraw_channel: Receiver, user_channel: Receiver, user_sender: Sender, target: Rc>, @@ -174,32 +176,16 @@ impl EventLoop { xconn.update_cached_wm_info(root); - let pending_redraws: Arc>> = Default::default(); - let mut mod_keymap = ModifierKeymap::new(); mod_keymap.reset_from_x_connection(&xconn); - let target = Rc::new(RootELW { - p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { - ime, - root, - windows: Default::default(), - _marker: ::std::marker::PhantomData, - ime_sender, - xconn, - wm_delete_window, - net_wm_ping, - pending_redraws: pending_redraws.clone(), - }), - _marker: ::std::marker::PhantomData, - }); - let poll = Poll::new().unwrap(); let (user_sender, user_channel) = channel(); + let (redraw_sender, redraw_channel) = channel(); poll.register( - &EventedFd(&get_xtarget(&target).xconn.x11_fd), + &EventedFd(&xconn.x11_fd), X_TOKEN, Ready::readable(), PollOpt::level(), @@ -214,6 +200,29 @@ impl EventLoop { ) .unwrap(); + poll.register( + &redraw_channel, + REDRAW_TOKEN, + Ready::readable(), + PollOpt::level(), + ) + .unwrap(); + + let target = Rc::new(RootELW { + p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { + ime, + root, + windows: Default::default(), + _marker: ::std::marker::PhantomData, + ime_sender, + xconn, + wm_delete_window, + net_wm_ping, + redraw_sender, + }), + _marker: ::std::marker::PhantomData, + }); + let event_processor = EventProcessor { target: target.clone(), dnd, @@ -239,6 +248,7 @@ impl EventLoop { let result = EventLoop { poll, + redraw_channel, user_channel, user_sender, event_processor, @@ -277,8 +287,6 @@ impl EventLoop { // Process all pending events self.drain_events(&mut callback, &mut control_flow); - let wt = get_xtarget(&self.target); - // Empty the user event buffer { while let Ok(event) = self.user_channel.try_recv() { @@ -301,12 +309,10 @@ impl EventLoop { } // Empty the redraw requests { - // Release the lock to prevent deadlock - let windows: Vec<_> = wt.pending_redraws.lock().unwrap().drain().collect(); - - for wid in windows { + while let Ok(window_id) = self.redraw_channel.try_recv() { + let window_id = crate::window::WindowId(super::WindowId::X(window_id)); sticky_exit_callback( - Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))), + Event::RedrawRequested(window_id), &self.target, &mut control_flow, &mut callback, @@ -408,7 +414,7 @@ impl EventLoop { super::WindowId::X(wid), )) = event { - wt.pending_redraws.lock().unwrap().insert(wid); + wt.redraw_sender.send(wid).unwrap(); } else { callback(event, window_target, control_flow); } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 058b3aa703..47be20c86e 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1,8 +1,6 @@ use raw_window_handle::unix::XlibHandle; use std::{ - cmp, - collections::HashSet, - env, + cmp, env, ffi::CString, mem::{self, replace, MaybeUninit}, os::raw::*, @@ -12,6 +10,7 @@ use std::{ }; use libc; +use mio_extras::channel::Sender; use parking_lot::Mutex; use crate::{ @@ -104,7 +103,7 @@ pub struct UnownedWindow { cursor_visible: Mutex, ime_sender: Mutex, pub shared_state: Mutex, - pending_redraws: Arc<::std::sync::Mutex>>, + redraw_sender: Sender, } impl UnownedWindow { @@ -249,7 +248,7 @@ impl UnownedWindow { cursor_visible: Mutex::new(true), ime_sender: Mutex::new(event_loop.ime_sender.clone()), shared_state: SharedState::new(guessed_monitor, window_attrs.visible), - pending_redraws: event_loop.pending_redraws.clone(), + redraw_sender: event_loop.redraw_sender.clone(), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window @@ -1314,10 +1313,7 @@ impl UnownedWindow { #[inline] pub fn request_redraw(&self) { - self.pending_redraws - .lock() - .unwrap() - .insert(WindowId(self.xwindow)); + self.redraw_sender.send(WindowId(self.xwindow)).unwrap(); } #[inline] From cbeb51b43674c477ea03e97178694838ea26d8b2 Mon Sep 17 00:00:00 2001 From: Murarth Date: Sat, 7 Nov 2020 11:46:37 -0700 Subject: [PATCH 209/239] X11: Fix multiple RedrawRequested events per event loop iteration (#1758) * X11: Fix multiple RedrawRequested per event loop iteration * Prevent infinite loop --- src/platform_impl/linux/x11/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index ffb9fc6e65..d06936c55b 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -24,7 +24,7 @@ pub use self::{ use std::{ cell::RefCell, - collections::HashMap, + collections::{HashMap, HashSet}, ffi::CStr, mem::{self, MaybeUninit}, ops::Deref, @@ -309,7 +309,13 @@ impl EventLoop { } // Empty the redraw requests { + let mut windows = HashSet::new(); + while let Ok(window_id) = self.redraw_channel.try_recv() { + windows.insert(window_id); + } + + for window_id in windows { let window_id = crate::window::WindowId(super::WindowId::X(window_id)); sticky_exit_callback( Event::RedrawRequested(window_id), From edf396b1a49836c4bbdf8b7fcb2341bfdb29194a Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Tue, 10 Nov 2020 22:55:29 +0100 Subject: [PATCH 210/239] On Wayland, add missing mappings for numpad arrows The mappings for 'keysyms::XKB_KEY_KP_{Up,Down,Left,Rigt}' were missing making the arrow keys on the numpad not sending proper 'VirtualKeyCode's. --- CHANGELOG.md | 1 + src/platform_impl/linux/wayland/seat/keyboard/keymap.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 903c3c700c..fa17fc7981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input. - On macOS, fix compilation when targeting aarch64 - On X11, fix `Window::request_redraw` not waking the event loop. +- On Wayland, the keypad arrow keys are now recognized. # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs index 717a68dfd9..991afff2c9 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs @@ -149,6 +149,10 @@ pub fn keysym_to_vkey(keysym: u32) -> Option { keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End), + keysyms::XKB_KEY_KP_Left => Some(VirtualKeyCode::Left), + keysyms::XKB_KEY_KP_Up => Some(VirtualKeyCode::Up), + keysyms::XKB_KEY_KP_Right => Some(VirtualKeyCode::Right), + keysyms::XKB_KEY_KP_Down => Some(VirtualKeyCode::Down), // => Some(VirtualKeyCode::OEM102), keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period), // => Some(VirtualKeyCode::Playpause), From 66859607a35f7e46a25346511657d3b7ada939ca Mon Sep 17 00:00:00 2001 From: msiglreith Date: Thu, 12 Nov 2020 20:49:44 +0100 Subject: [PATCH 211/239] Rename desktop eventloop extensions to run_return extension (#1738) --- CHANGELOG.md | 1 + examples/window_run_return.rs | 2 +- src/lib.rs | 10 +++++----- src/platform/mod.rs | 4 ++-- src/platform/{desktop.rs => run_return.rs} | 10 +++++----- 5 files changed, 14 insertions(+), 13 deletions(-) rename src/platform/{desktop.rs => run_return.rs} (82%) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa17fc7981..0195304ec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - On macOS, fix compilation when targeting aarch64 - On X11, fix `Window::request_redraw` not waking the event loop. - On Wayland, the keypad arrow keys are now recognized. +- **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`. # 0.23.0 (2020-10-02) diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index 066aa05452..6bedfcd82c 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -15,7 +15,7 @@ fn main() { use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - platform::desktop::EventLoopExtDesktop, + platform::run_return::EventLoopExtRunReturn, window::WindowBuilder, }; let mut event_loop = EventLoop::new(); diff --git a/src/lib.rs b/src/lib.rs index e0f3c2994e..51f4a8634a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,10 +35,10 @@ //! entire program terminates. //! //! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator`-based event loop -//! model, since that can't be implemented properly on web and mobile platforms and works poorly on -//! most desktop platforms. However, this model can be re-implemented to an extent on desktops with -//! [`EventLoopExtDesktop::run_return`]. See that method's documentation for more reasons about why -//! it's discouraged, beyond mobile/web compatibility reasons. +//! model, since that can't be implemented properly on some platforms (e.g web, iOS) and works poorly on +//! most other platforms. However, this model can be re-implemented to an extent with +//! [`EventLoopExtRunReturn::run_return`]. See that method's documentation for more reasons about why +//! it's discouraged, beyond compatibility reasons. //! //! //! ```no_run @@ -109,7 +109,7 @@ //! window visible only once you're ready to render into it. //! //! [`EventLoop`]: event_loop::EventLoop -//! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return +//! [`EventLoopExtRunReturn::run_return`]: ./platform/run_return/trait.EventLoopExtRunReturn.html#tymethod.run_return //! [`EventLoop::new()`]: event_loop::EventLoop::new //! [event_loop_run]: event_loop::EventLoop::run //! [`ControlFlow`]: event_loop::ControlFlow diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 27ecde1837..1ee5fce2bb 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -11,7 +11,7 @@ //! //! And the following platform-specific module: //! -//! - `desktop` (available on `windows`, `unix`, and `macos`) +//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`) //! //! However only the module corresponding to the platform you're compiling to will be available. @@ -21,5 +21,5 @@ pub mod macos; pub mod unix; pub mod windows; -pub mod desktop; +pub mod run_return; pub mod web; diff --git a/src/platform/desktop.rs b/src/platform/run_return.rs similarity index 82% rename from src/platform/desktop.rs rename to src/platform/run_return.rs index a49d71c377..932d9e38a3 100644 --- a/src/platform/desktop.rs +++ b/src/platform/run_return.rs @@ -14,8 +14,8 @@ use crate::{ event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, }; -/// Additional methods on `EventLoop` that are specific to desktop platforms. -pub trait EventLoopExtDesktop { +/// Additional methods on `EventLoop` to return control flow to the caller. +pub trait EventLoopExtRunReturn { /// A type provided by the user that can be passed through `Event::UserEvent`. type UserEvent; @@ -25,12 +25,12 @@ pub trait EventLoopExtDesktop { /// control flow to the caller when `control_flow` is set to `ControlFlow::Exit`. /// /// # Caveats - /// Despite its apperance at first glance, this is *not* a perfect replacement for + /// Despite its appearance at first glance, this is *not* a perfect replacement for /// `poll_events`. For example, this function will not return on Windows or macOS while a /// window is getting resized, resulting in all application logic outside of the /// `event_handler` closure not running until the resize operation ends. Other OS operations /// may also result in such freezes. This behavior is caused by fundamental limitations in the - /// underyling OS APIs, which cannot be hidden by Winit without severe stability reprecussions. + /// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions. /// /// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary. fn run_return(&mut self, event_handler: F) @@ -42,7 +42,7 @@ pub trait EventLoopExtDesktop { ); } -impl EventLoopExtDesktop for EventLoop { +impl EventLoopExtRunReturn for EventLoop { type UserEvent = T; fn run_return(&mut self, event_handler: F) From 1c38f113b31314012291ceffde8d25c4ae5e034e Mon Sep 17 00:00:00 2001 From: Max de Danschutter <43446207+maxded@users.noreply.github.com> Date: Thu, 19 Nov 2020 18:56:24 +0100 Subject: [PATCH 212/239] Remove println call from Android's eventloop --- src/platform_impl/android/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index c0f80dcab0..0b4812e3c6 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -175,7 +175,6 @@ impl EventLoop { Some(EventSource::InputQueue) => { if let Some(input_queue) = ndk_glue::input_queue().as_ref() { while let Some(event) = input_queue.get_event() { - println!("event {:?}", event); if let Some(event) = input_queue.pre_dispatch(event) { let window_id = window::WindowId(WindowId); let device_id = event::DeviceId(DeviceId); From 165e51d850eaff84083d7976b39231ca46c293b2 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Sun, 22 Nov 2020 01:53:56 +0300 Subject: [PATCH 213/239] On Wayland, increase default font size in CSD This commit increased default font size from 11 to 17 making it identical to the one SCTK is using under the hood, since it's more readable. --- CHANGELOG.md | 1 + src/platform/unix.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0195304ec3..b7e3f8efab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - On X11, fix `Window::request_redraw` not waking the event loop. - On Wayland, the keypad arrow keys are now recognized. - **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`. +- On Wayland, default font size in CSD increased from 11 to 17. # 0.23.0 (2020-10-02) diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 02472dd8c5..1c464f6d8e 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -486,13 +486,13 @@ pub trait Theme: Send + 'static { /// Font name and the size for the title bar. /// - /// By default the font is `sans-serif` at the size of 11. + /// By default the font is `sans-serif` at the size of 17. /// /// Returning `None` means that title won't be drawn. fn font(&self) -> Option<(String, f32)> { // Not having any title isn't something desirable for the users, so setting it to // something generic. - Some((String::from("sans-serif"), 11.)) + Some((String::from("sans-serif"), 17.)) } } From 77d5d20391ca2f236c629cffbbdbc12d9c77f5ed Mon Sep 17 00:00:00 2001 From: Viktor Zoutman Date: Tue, 24 Nov 2020 23:05:29 +0100 Subject: [PATCH 214/239] Windows: Delayed Message Boxes Fix. (#1769) --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7e3f8efab..8a536e6068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On Windows, fix bug causing message boxes to appear delayed. - On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. - On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 27af039fb6..37817cb51b 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1973,7 +1973,8 @@ unsafe extern "system" fn thread_event_target_callback( } } - 0 + // Default WM_PAINT behaviour. This makes sure modals and popups are shown immediatly when opening them. + commctrl::DefSubclassProc(window, msg, wparam, lparam) } winuser::WM_INPUT_DEVICE_CHANGE => { From f79efec7ef5c90b9aaeea3800bb733b8340fbefd Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Thu, 26 Nov 2020 01:20:35 +0100 Subject: [PATCH 215/239] Fix deprecation warning in the window icon example --- examples/window_icon.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/window_icon.rs b/examples/window_icon.rs index aaa6bc0ba7..6c79625505 100644 --- a/examples/window_icon.rs +++ b/examples/window_icon.rs @@ -49,7 +49,7 @@ fn load_icon(path: &Path) -> Icon { let (icon_rgba, icon_width, icon_height) = { let image = image::open(path) .expect("Failed to open icon path") - .into_rgba(); + .into_rgba8(); let (width, height) = image.dimensions(); let rgba = image.into_raw(); (rgba, width, height) From 0861a353d6299004c33e3ac0396f8c9d946a55e3 Mon Sep 17 00:00:00 2001 From: Max de Danschutter <43446207+maxded@users.noreply.github.com> Date: Fri, 27 Nov 2020 03:03:08 +0100 Subject: [PATCH 216/239] Add 'request_user_attention' to Window This commit introduces a cross platform way to request a user attention to the window via a 'request_user_attention' method on a Window struct. This method is inspired by macOS's 'request_user_attention' method and thus reuses its signature and semantics to some extent. --- CHANGELOG.md | 3 +++ src/platform/macos.rs | 29 -------------------- src/platform/unix.rs | 14 ---------- src/platform_impl/android/mod.rs | 2 ++ src/platform_impl/ios/window.rs | 8 +++++- src/platform_impl/linux/mod.rs | 12 ++++++++- src/platform_impl/linux/x11/window.rs | 36 ++++++++++++------------- src/platform_impl/macos/window.rs | 31 ++++++++++++---------- src/platform_impl/web/window.rs | 9 ++++++- src/platform_impl/windows/window.rs | 33 ++++++++++++++++++++++- src/window.rs | 38 +++++++++++++++++++++++++++ 11 files changed, 136 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a536e6068..aaeebeafc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ - On X11, fix `Window::request_redraw` not waking the event loop. - On Wayland, the keypad arrow keys are now recognized. - **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`. +- Added `request_user_attention` method to `Window`. +- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`. +- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`. - On Wayland, default font size in CSD increased from 11 to 17. # 0.23.0 (2020-10-02) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index f6f18d448e..f66076b9f5 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -9,26 +9,6 @@ use crate::{ window::{Window, WindowBuilder}, }; -/// Corresponds to `NSRequestUserAttentionType`. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum RequestUserAttentionType { - /// Corresponds to `NSCriticalRequest`. - /// - /// Dock icon will bounce until the application is focused. - Critical, - - /// Corresponds to `NSInformationalRequest`. - /// - /// Dock icon will bounce once. - Informational, -} - -impl Default for RequestUserAttentionType { - fn default() -> Self { - RequestUserAttentionType::Critical - } -} - /// Additional methods on `Window` that are specific to MacOS. pub trait WindowExtMacOS { /// Returns a pointer to the cocoa `NSWindow` that is used by this window. @@ -41,10 +21,6 @@ pub trait WindowExtMacOS { /// The pointer will become invalid when the `Window` is destroyed. fn ns_view(&self) -> *mut c_void; - /// Request user attention, causing the application's dock icon to bounce. - /// Note that this has no effect if the application is already focused. - fn request_user_attention(&self, request_type: RequestUserAttentionType); - /// Returns whether or not the window is in simple fullscreen mode. fn simple_fullscreen(&self) -> bool; @@ -75,11 +51,6 @@ impl WindowExtMacOS for Window { self.window.ns_view() } - #[inline] - fn request_user_attention(&self, request_type: RequestUserAttentionType) { - self.window.request_user_attention(request_type) - } - #[inline] fn simple_fullscreen(&self) -> bool { self.window.simple_fullscreen() diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 1c464f6d8e..8662e45690 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -213,10 +213,6 @@ pub trait WindowExtUnix { #[cfg(feature = "x11")] fn xlib_xconnection(&self) -> Option>; - /// Set window urgency hint (`XUrgencyHint`). Only relevant on X. - #[cfg(feature = "x11")] - fn set_urgent(&self, is_urgent: bool); - /// This function returns the underlying `xcb_connection_t` of an xlib `Display`. /// /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). @@ -297,16 +293,6 @@ impl WindowExtUnix for Window { } } - #[inline] - #[cfg(feature = "x11")] - fn set_urgent(&self, is_urgent: bool) { - match self.window { - LinuxWindow::X(ref w) => w.set_urgent(is_urgent), - #[cfg(feature = "wayland")] - _ => (), - } - } - #[inline] #[cfg(feature = "x11")] fn xcb_connection(&self) -> Option<*mut raw::c_void> { diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 0b4812e3c6..eae00d199b 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -484,6 +484,8 @@ impl Window { pub fn set_ime_position(&self, _position: Position) {} + pub fn request_user_attention(&self, _request_type: Option) {} + pub fn set_cursor_icon(&self, _: window::CursorIcon) {} pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index d83b494153..90c1fc2da7 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -22,7 +22,9 @@ use crate::{ }, monitor, view, EventLoopWindowTarget, MonitorHandle, }, - window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId}, + window::{ + CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + }, }; pub struct Inner { @@ -260,6 +262,10 @@ impl Inner { warn!("`Window::set_ime_position` is ignored on iOS") } + pub fn request_user_attention(&self, _request_type: Option) { + warn!("`Window::request_user_attention` is ignored on iOS") + } + // Allow directly accessing the current monitor internally without unwrapping. fn current_monitor_inner(&self) -> RootMonitorHandle { unsafe { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 7bc91bdec7..5dea52c078 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -30,7 +30,7 @@ use crate::{ event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - window::{CursorIcon, Fullscreen, WindowAttributes}, + window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, }; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; @@ -418,6 +418,16 @@ impl Window { x11_or_wayland!(match self; Window(w) => w.set_ime_position(position)) } + #[inline] + pub fn request_user_attention(&self, _request_type: Option) { + match self { + #[cfg(feature = "x11")] + &Window::X(ref w) => w.request_user_attention(_request_type), + #[cfg(feature = "wayland")] + _ => (), + } + } + #[inline] pub fn request_redraw(&self) { x11_or_wayland!(match self; Window(w) => w.request_redraw()) diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 47be20c86e..0fa64cf164 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -22,7 +22,7 @@ use crate::{ MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, - window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, + window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes}, }; use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError}; @@ -523,23 +523,6 @@ impl UnownedWindow { ) } - #[inline] - pub fn set_urgent(&self, is_urgent: bool) { - let mut wm_hints = self - .xconn - .get_wm_hints(self.xwindow) - .expect("`XGetWMHints` failed"); - if is_urgent { - (*wm_hints).flags |= ffi::XUrgencyHint; - } else { - (*wm_hints).flags &= !ffi::XUrgencyHint; - } - self.xconn - .set_wm_hints(self.xwindow, wm_hints) - .flush() - .expect("Failed to set urgency hint"); - } - fn set_netwm( &self, operation: util::StateOperation, @@ -1306,6 +1289,23 @@ impl UnownedWindow { self.set_ime_position_physical(x, y); } + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + let mut wm_hints = self + .xconn + .get_wm_hints(self.xwindow) + .expect("`XGetWMHints` failed"); + if request_type.is_some() { + (*wm_hints).flags |= ffi::XUrgencyHint; + } else { + (*wm_hints).flags &= !ffi::XUrgencyHint; + } + self.xconn + .set_wm_hints(self.xwindow, wm_hints) + .flush() + .expect("Failed to set urgency hint"); + } + #[inline] pub fn id(&self) -> WindowId { WindowId(self.xwindow) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index b28c860f08..7e69b53dca 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -16,7 +16,7 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, + platform::macos::{ActivationPolicy, WindowExtMacOS}, platform_impl::platform::{ app_state::AppState, app_state::INTERRUPT_EVENT_LOOP_EXIT, @@ -28,7 +28,9 @@ use crate::{ window_delegate::new_delegate, OsError, }, - window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId}, + window::{ + CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + }, }; use cocoa::{ appkit::{ @@ -977,6 +979,19 @@ impl UnownedWindow { } } + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + let ns_request_type = request_type.map(|ty| match ty { + UserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest, + UserAttentionType::Informational => NSRequestUserAttentionType::NSInformationalRequest, + }); + unsafe { + if let Some(ty) = ns_request_type { + NSApp().requestUserAttention_(ty); + } + } + } + #[inline] // Allow directly accessing the current monitor internally without unwrapping. pub(crate) fn current_monitor_inner(&self) -> RootMonitorHandle { @@ -1030,18 +1045,6 @@ impl WindowExtMacOS for UnownedWindow { *self.ns_view as *mut _ } - #[inline] - fn request_user_attention(&self, request_type: RequestUserAttentionType) { - unsafe { - NSApp().requestUserAttention_(match request_type { - RequestUserAttentionType::Critical => NSRequestUserAttentionType::NSCriticalRequest, - RequestUserAttentionType::Informational => { - NSRequestUserAttentionType::NSInformationalRequest - } - }); - } - } - #[inline] fn simple_fullscreen(&self) -> bool { let shared_state_lock = self.shared_state.lock().unwrap(); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 4719044ef4..463705bfea 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -3,7 +3,9 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::event; use crate::icon::Icon; use crate::monitor::MonitorHandle as RootMH; -use crate::window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWI}; +use crate::window::{ + CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWI, +}; use raw_window_handle::web::WebHandle; @@ -268,6 +270,11 @@ impl Window { // Currently a no-op as it does not seem there is good support for this on web } + #[inline] + pub fn request_user_attention(&self, _request_type: Option) { + // Currently an intentional no-op + } + #[inline] // Allow directly accessing the current monitor internally without unwrapping. fn current_monitor_inner(&self) -> RootMH { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index b53347502f..592307b7bb 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -43,7 +43,7 @@ use crate::{ window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, - window::{CursorIcon, Fullscreen, WindowAttributes}, + window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, }; /// The Win32 implementation of the main `Window` object. @@ -621,6 +621,37 @@ impl Window { warn!("`Window::set_ime_position` is ignored on Windows") } + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + let window = self.window.clone(); + let active_window_handle = unsafe { winuser::GetActiveWindow() }; + if window.0 == active_window_handle { + return; + } + + self.thread_executor.execute_in_thread(move || unsafe { + let (flags, count) = request_type + .map(|ty| match ty { + UserAttentionType::Critical => { + (winuser::FLASHW_ALL | winuser::FLASHW_TIMERNOFG, u32::MAX) + } + UserAttentionType::Informational => { + (winuser::FLASHW_TRAY | winuser::FLASHW_TIMERNOFG, 0) + } + }) + .unwrap_or((winuser::FLASHW_STOP, 0)); + + let mut flash_info = winuser::FLASHWINFO { + cbSize: mem::size_of::() as UINT, + hwnd: window.0, + dwFlags: flags, + uCount: count, + dwTimeout: 0, + }; + winuser::FlashWindowEx(&mut flash_info); + }); + } + #[inline] pub fn is_dark_mode(&self) -> bool { self.window_state.lock().is_dark_mode diff --git a/src/window.rs b/src/window.rs index c8f45d1067..b0951bc845 100644 --- a/src/window.rs +++ b/src/window.rs @@ -682,6 +682,23 @@ impl Window { pub fn set_ime_position>(&self, position: P) { self.window.set_ime_position(position.into()) } + + /// Requests user attention to the window, this has no effect if the application + /// is already focused. How requesting for user attention manifests is platform dependent, + /// see `UserAttentionType` for details. + /// + /// Providing `None` will unset the request for user attention. Unsetting the request for + /// user attention might not be done automatically by the WM when the window receives input. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Web / Wayland:** Unsupported. + /// - **macOS:** `None` has no effect. + /// - **X11:** Requests for user attention must be manually cleared. + #[inline] + pub fn request_user_attention(&self, request_type: Option) { + self.window.request_user_attention(request_type) + } } /// Cursor functions. @@ -874,3 +891,24 @@ pub enum Theme { Light, Dark, } + +/// ## Platform-specific +/// +/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between `Critical` and `Informational`. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum UserAttentionType { + /// ## Platform-specific + /// - **macOS:** Bounces the dock icon until the application is in focus. + /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. + Critical, + /// ## Platform-specific + /// - **macOS:** Bounces the dock icon once. + /// - **Windows:** Flashes the taskbar button until the application is in focus. + Informational, +} + +impl Default for UserAttentionType { + fn default() -> Self { + UserAttentionType::Informational + } +} From 5700359a617d42b3674d59fe45fd19ed3676595d Mon Sep 17 00:00:00 2001 From: Max de Danschutter <43446207+maxded@users.noreply.github.com> Date: Sat, 28 Nov 2020 17:41:11 +0100 Subject: [PATCH 217/239] Android: support multi-touch (#1776) --- CHANGELOG.md | 3 +- src/platform_impl/android/mod.rs | 54 ++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaeebeafc3..c588aceb93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,5 @@ # Unreleased -- On Windows, fix bug causing message boxes to appear delayed. - On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. - On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. @@ -17,6 +16,8 @@ - **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`. - **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`. - On Wayland, default font size in CSD increased from 11 to 17. +- On Windows, fix bug causing message boxes to appear delayed. +- On Android, support multi-touch. # 0.23.0 (2020-10-02) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index eae00d199b..ddd1eb961a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -181,37 +181,45 @@ impl EventLoop { match &event { InputEvent::MotionEvent(motion_event) => { let phase = match motion_event.action() { - MotionAction::Down => Some(event::TouchPhase::Started), - MotionAction::Up => Some(event::TouchPhase::Ended), + MotionAction::Down | MotionAction::PointerDown => { + Some(event::TouchPhase::Started) + } + MotionAction::Up | MotionAction::PointerUp => { + Some(event::TouchPhase::Ended) + } MotionAction::Move => Some(event::TouchPhase::Moved), MotionAction::Cancel => { Some(event::TouchPhase::Cancelled) } _ => None, // TODO mouse events }; - let pointer = motion_event.pointer_at_index(0); - let location = PhysicalPosition { - x: pointer.x() as _, - y: pointer.y() as _, - }; if let Some(phase) = phase { - let event = event::Event::WindowEvent { - window_id, - event: event::WindowEvent::Touch(event::Touch { - device_id, - phase, - location, - id: 0, - force: None, - }), - }; - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event - ); + for pointer in motion_event.pointers() { + let location = PhysicalPosition { + x: pointer.x() as _, + y: pointer.y() as _, + }; + + let event = event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Touch( + event::Touch { + device_id, + phase, + location, + id: pointer.pointer_id() as u64, + force: None, + }, + ), + }; + call_event_handler!( + event_handler, + self.window_target(), + control_flow, + event + ); + } } } InputEvent::KeyEvent(_) => {} // TODO From 6ddee9a8acede06421df0ccab3b65c019a6c3a0c Mon Sep 17 00:00:00 2001 From: Viktor Zoutman Date: Mon, 30 Nov 2020 19:04:26 +0100 Subject: [PATCH 218/239] Ability to force a theme on Windows (#1666) --- CHANGELOG.md | 3 +++ FEATURES.md | 2 +- src/platform/windows.rs | 19 ++++++++++++----- src/platform_impl/windows/dark_mode.rs | 21 ++++++++++++------ src/platform_impl/windows/event_loop.rs | 26 +++++++++++------------ src/platform_impl/windows/mod.rs | 3 +++ src/platform_impl/windows/window.rs | 13 ++++++------ src/platform_impl/windows/window_state.rs | 11 ++++++---- src/window.rs | 2 +- 9 files changed, 63 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c588aceb93..9946438c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +- **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`. +- On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme. +- On Windows, fix bug causing message boxes to appear delayed. - On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. - On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. diff --git a/FEATURES.md b/FEATURES.md index ca28c417f1..1e77e9c534 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -117,7 +117,7 @@ If your PR makes notable changes to Winit's features, please update this section * Setting the taskbar icon * Setting the parent window * `WS_EX_NOREDIRECTIONBITMAP` support -* Theme the title bar according to Windows 10 Dark Mode setting +* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme ### macOS * Window activation policy diff --git a/src/platform/windows.rs b/src/platform/windows.rs index a040be363f..05839d89c1 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -13,7 +13,7 @@ use crate::{ event_loop::EventLoop, monitor::MonitorHandle, platform_impl::{EventLoop as WindowsEventLoop, WinIcon}, - window::{BadIcon, Icon, Window, WindowBuilder}, + window::{BadIcon, Icon, Theme, Window, WindowBuilder}, }; /// Additional methods on `EventLoop` that are specific to Windows. @@ -81,8 +81,8 @@ pub trait WindowExtWindows { /// This sets `ICON_BIG`. A good ceiling here is 256x256. fn set_taskbar_icon(&self, taskbar_icon: Option); - /// Whether the system theme is currently Windows 10's "Dark Mode". - fn is_dark_mode(&self) -> bool; + /// Returns the current window theme. + fn theme(&self) -> Theme; } impl WindowExtWindows for Window { @@ -102,8 +102,8 @@ impl WindowExtWindows for Window { } #[inline] - fn is_dark_mode(&self) -> bool { - self.window.is_dark_mode() + fn theme(&self) -> Theme { + self.window.theme() } } @@ -125,6 +125,9 @@ pub trait WindowBuilderExtWindows { /// If you need COM API with `COINIT_MULTITHREADED` you must initialize it before calling any winit functions. /// See https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks for more information. fn with_drag_and_drop(self, flag: bool) -> WindowBuilder; + + /// Forces a theme or uses the system settings if `None` was provided. + fn with_theme(self, theme: Option) -> WindowBuilder; } impl WindowBuilderExtWindows for WindowBuilder { @@ -151,6 +154,12 @@ impl WindowBuilderExtWindows for WindowBuilder { self.platform_specific.drag_and_drop = flag; self } + + #[inline] + fn with_theme(mut self, theme: Option) -> WindowBuilder { + self.platform_specific.preferred_theme = theme; + self + } } /// Additional methods on `MonitorHandle` that are specific to Windows. diff --git a/src/platform_impl/windows/dark_mode.rs b/src/platform_impl/windows/dark_mode.rs index 3c7c16eebf..a229e555dc 100644 --- a/src/platform_impl/windows/dark_mode.rs +++ b/src/platform_impl/windows/dark_mode.rs @@ -14,6 +14,8 @@ use winapi::{ um::{libloaderapi, uxtheme, winuser}, }; +use crate::window::Theme; + lazy_static! { static ref WIN10_BUILD_VERSION: Option = { // FIXME: RtlGetVersion is a documented windows API, @@ -70,11 +72,14 @@ lazy_static! { static ref LIGHT_THEME_NAME: Vec = widestring(""); } -/// Attempt to set dark mode on a window, if necessary. -/// Returns true if dark mode was set, false if not. -pub fn try_dark_mode(hwnd: HWND) -> bool { +/// Attempt to set a theme on a window, if necessary. +/// Returns the theme that was picked +pub fn try_theme(hwnd: HWND, preferred_theme: Option) -> Theme { if *DARK_MODE_SUPPORTED { - let is_dark_mode = should_use_dark_mode(); + let is_dark_mode = match preferred_theme { + Some(theme) => theme == Theme::Dark, + None => should_use_dark_mode(), + }; let theme_name = if is_dark_mode { DARK_THEME_NAME.as_ptr() @@ -84,10 +89,12 @@ pub fn try_dark_mode(hwnd: HWND) -> bool { let status = unsafe { uxtheme::SetWindowTheme(hwnd, theme_name as _, std::ptr::null()) }; - status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) - } else { - false + if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) { + return Theme::Dark; + } } + + Theme::Light } fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 37817cb51b..a6794e4e2a 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -36,7 +36,7 @@ use crate::{ event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ - dark_mode::try_dark_mode, + dark_mode::try_theme, dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, drop_handler::FileDropHandler, event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, @@ -1874,20 +1874,20 @@ unsafe extern "system" fn public_window_callback( winuser::WM_SETTINGCHANGE => { use crate::event::WindowEvent::ThemeChanged; - let is_dark_mode = try_dark_mode(window); - let mut window_state = subclass_input.window_state.lock(); - let changed = window_state.is_dark_mode != is_dark_mode; + let preferred_theme = subclass_input.window_state.lock().preferred_theme; - if changed { - use crate::window::Theme::*; - let theme = if is_dark_mode { Dark } else { Light }; + if preferred_theme == None { + let new_theme = try_theme(window, preferred_theme); + let mut window_state = subclass_input.window_state.lock(); - window_state.is_dark_mode = is_dark_mode; - mem::drop(window_state); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ThemeChanged(theme), - }); + if window_state.current_theme != new_theme { + window_state.current_theme = new_theme; + mem::drop(window_state); + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ThemeChanged(new_theme), + }); + } } commctrl::DefSubclassProc(window, msg, wparam, lparam) diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 498cdfb80c..5b3f4e1174 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -13,6 +13,7 @@ pub use self::icon::WinIcon as PlatformIcon; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; +use crate::window::Theme; #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { @@ -20,6 +21,7 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub taskbar_icon: Option, pub no_redirection_bitmap: bool, pub drag_and_drop: bool, + pub preferred_theme: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -29,6 +31,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { taskbar_icon: None, no_redirection_bitmap: false, drag_and_drop: true, + preferred_theme: None, } } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 592307b7bb..580fa958f3 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -34,7 +34,7 @@ use crate::{ icon::Icon, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ - dark_mode::try_dark_mode, + dark_mode::try_theme, dpi::{dpi_to_scale_factor, hwnd_dpi}, drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, @@ -43,7 +43,7 @@ use crate::{ window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, - window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, + window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes}, }; /// The Win32 implementation of the main `Window` object. @@ -653,8 +653,8 @@ impl Window { } #[inline] - pub fn is_dark_mode(&self) -> bool { - self.window_state.lock().is_dark_mode + pub fn theme(&self) -> Theme { + self.window_state.lock().current_theme } } @@ -764,14 +764,15 @@ unsafe fn init( // If the system theme is dark, we need to set the window theme now // before we update the window flags (and possibly show the // window for the first time). - let dark_mode = try_dark_mode(real_window.0); + let current_theme = try_theme(real_window.0, pl_attribs.preferred_theme); let window_state = { let window_state = WindowState::new( &attributes, pl_attribs.taskbar_icon, scale_factor, - dark_mode, + current_theme, + pl_attribs.preferred_theme, ); let window_state = Arc::new(Mutex::new(window_state)); WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 9ea7c2dacb..1369fe7210 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -3,7 +3,7 @@ use crate::{ event::ModifiersState, icon::Icon, platform_impl::platform::{event_loop, util}, - window::{CursorIcon, Fullscreen, WindowAttributes}, + window::{CursorIcon, Fullscreen, Theme, WindowAttributes}, }; use parking_lot::MutexGuard; use std::{io, ptr}; @@ -31,7 +31,8 @@ pub struct WindowState { pub modifiers_state: ModifiersState, pub fullscreen: Option, - pub is_dark_mode: bool, + pub current_theme: Theme, + pub preferred_theme: Option, pub high_surrogate: Option, window_flags: WindowFlags, } @@ -101,7 +102,8 @@ impl WindowState { attributes: &WindowAttributes, taskbar_icon: Option, scale_factor: f64, - is_dark_mode: bool, + current_theme: Theme, + preferred_theme: Option, ) -> WindowState { WindowState { mouse: MouseProperties { @@ -122,7 +124,8 @@ impl WindowState { modifiers_state: ModifiersState::default(), fullscreen: None, - is_dark_mode, + current_theme, + preferred_theme, high_surrogate: None, window_flags: WindowFlags::empty(), } diff --git a/src/window.rs b/src/window.rs index b0951bc845..8cf6e0669b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -886,7 +886,7 @@ pub enum Fullscreen { Borderless(Option), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum Theme { Light, Dark, From 8fb7aa5cef1c3bee28730dc771c7ddb16ba47c66 Mon Sep 17 00:00:00 2001 From: Marnix Kuijs <44985093+MarnixKuijs@users.noreply.github.com> Date: Wed, 2 Dec 2020 21:13:42 +0100 Subject: [PATCH 219/239] Android: Improved multi-touch (#1783) * Improved multi-touch * Update feature matrix * Generate cancelled events for all pointers * Changed back features matrix layout * Reduced code duplication * Updated changelog * Revert changelog update --- FEATURES.md | 2 +- src/platform_impl/android/mod.rs | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 1e77e9c534..dcd91ed3ea 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -202,7 +202,7 @@ Legend: |Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ | |Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ | -|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❌ | +|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ | |Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | |Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ | diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index ddd1eb961a..b8df45151b 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -193,14 +193,27 @@ impl EventLoop { } _ => None, // TODO mouse events }; - if let Some(phase) = phase { - for pointer in motion_event.pointers() { + let pointers: Box< + dyn Iterator>, + > = match phase { + event::TouchPhase::Started + | event::TouchPhase::Ended => Box::new( + std::iter::once(motion_event.pointer_at_index( + motion_event.pointer_index(), + )), + ), + event::TouchPhase::Moved + | event::TouchPhase::Cancelled => { + Box::new(motion_event.pointers()) + } + }; + + for pointer in pointers { let location = PhysicalPosition { x: pointer.x() as _, y: pointer.y() as _, }; - let event = event::Event::WindowEvent { window_id, event: event::WindowEvent::Touch( From c5620efc9c5f44f9755a64359bb9fee9f92a4ded Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Wed, 9 Dec 2020 23:11:25 +0300 Subject: [PATCH 220/239] On Wayland, don't drop extra mouse buttons This commit forwards "unknown" Wayland mouse buttons downstream via 'MouseButton::Other'. Possible values for those could be found in . Also, since Wayland just forwards buttons from the kernel, which are 'u16', we must adjust 'MouseButton::Other' to take 'u16' instead of 'u8'. --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- src/event.rs | 2 +- .../linux/wayland/seat/pointer/handlers.rs | 14 +++++++++----- src/platform_impl/linux/x11/event_processor.rs | 2 +- src/platform_impl/windows/event_loop.rs | 4 ++-- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9946438c70..d6372d4cd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ - On Wayland, default font size in CSD increased from 11 to 17. - On Windows, fix bug causing message boxes to appear delayed. - On Android, support multi-touch. +- On Wayland, extra mouse buttons are not dropped anymore. +- **Breaking**: `MouseButton::Other` now uses `u16`. # 0.23.0 (2020-10-02) diff --git a/Cargo.toml b/Cargo.toml index 6a316d0cff..f501f8c151 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ raw-window-handle = "0.3" bitflags = "1" [dev-dependencies] -image = "0.23" +image = "0.23.12" simple_logger = "1.9" [target.'cfg(target_os = "android")'.dependencies] diff --git a/src/event.rs b/src/event.rs index 5d89c7db0b..f39ddaac50 100644 --- a/src/event.rs +++ b/src/event.rs @@ -745,7 +745,7 @@ pub enum MouseButton { Left, Right, Middle, - Other(u8), + Other(u16), } /// Describes a difference in the mouse scroll wheel state. diff --git a/src/platform_impl/linux/wayland/seat/pointer/handlers.rs b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs index 06165a49f9..7d291713fa 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/handlers.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/handlers.rs @@ -17,6 +17,11 @@ use crate::platform_impl::wayland::{self, DeviceId}; use super::{PointerData, WinitPointer}; +// These values are comming from . +const BTN_LEFT: u32 = 0x110; +const BTN_RIGHT: u32 = 0x111; +const BTN_MIDDLE: u32 = 0x112; + #[inline] pub(super) fn handle_pointer( pointer: ThemedPointer, @@ -153,11 +158,10 @@ pub(super) fn handle_pointer( }; let button = match button { - 0x110 => MouseButton::Left, - 0x111 => MouseButton::Right, - 0x112 => MouseButton::Middle, - // TODO - figure out the translation. - _ => return, + BTN_LEFT => MouseButton::Left, + BTN_RIGHT => MouseButton::Right, + BTN_MIDDLE => MouseButton::Middle, + button => MouseButton::Other(button as u16), }; event_sink.push_window_event( diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index ab87d76d1e..65ed4d9ff1 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -709,7 +709,7 @@ impl EventProcessor { event: MouseInput { device_id, state, - button: Other(x as u8), + button: Other(x as u16), modifiers, }, }), diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index a6794e4e2a..6b3d287a83 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1307,7 +1307,7 @@ unsafe extern "system" fn public_window_callback( event: MouseInput { device_id: DEVICE_ID, state: Pressed, - button: Other(xbutton as u8), + button: Other(xbutton), modifiers: event::get_key_mods(), }, }); @@ -1329,7 +1329,7 @@ unsafe extern "system" fn public_window_callback( event: MouseInput { device_id: DEVICE_ID, state: Released, - button: Other(xbutton as u8), + button: Other(xbutton), modifiers: event::get_key_mods(), }, }); From db038d943c336599404d667d52ea879da3cc2ab0 Mon Sep 17 00:00:00 2001 From: moko256 Date: Thu, 10 Dec 2020 05:16:59 +0900 Subject: [PATCH 221/239] On Windows, implement 'Window::set_ime_position' with IMM API --- CHANGELOG.md | 1 + Cargo.toml | 1 + examples/set_ime_position.rs | 54 +++++++++++++++++++++++++++++ src/platform_impl/windows/window.rs | 24 +++++++++++-- src/window.rs | 2 +- 5 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 examples/set_ime_position.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d6372d4cd1..57f574e6d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On Windows, implement `Window::set_ime_position`. - **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`. - On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme. - On Windows, fix bug causing message boxes to appear delayed. diff --git a/Cargo.toml b/Cargo.toml index f501f8c151..068b2bc460 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ features = [ "commctrl", "dwmapi", "errhandlingapi", + "imm", "hidusage", "libloaderapi", "objbase", diff --git a/examples/set_ime_position.rs b/examples/set_ime_position.rs new file mode 100644 index 0000000000..1b2eecc373 --- /dev/null +++ b/examples/set_ime_position.rs @@ -0,0 +1,54 @@ +use simple_logger::SimpleLogger; +use winit::{ + dpi::PhysicalPosition, + event::{ElementState, Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +fn main() { + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new().build(&event_loop).unwrap(); + window.set_title("A fantastic window!"); + + println!("Ime position will system default"); + println!("Click to set ime position to cursor's"); + + let mut cursor_position = PhysicalPosition::new(0.0, 0.0); + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CursorMoved { position, .. }, + .. + } => { + cursor_position = position; + } + Event::WindowEvent { + event: + WindowEvent::MouseInput { + state: ElementState::Released, + .. + }, + .. + } => { + println!( + "Setting ime position to {}, {}", + cursor_position.x, cursor_position.y + ); + window.set_ime_position(cursor_position); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + *control_flow = ControlFlow::Exit; + return; + } + _ => (), + } + }); +} diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 580fa958f3..c444863e37 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -18,7 +18,9 @@ use winapi::{ windef::{HWND, POINT, RECT}, }, um::{ - combaseapi, dwmapi, libloaderapi, + combaseapi, dwmapi, + imm::{CFS_POINT, COMPOSITIONFORM}, + libloaderapi, objbase::COINIT_APARTMENTTHREADED, ole2, oleidl::LPDROPTARGET, @@ -616,9 +618,25 @@ impl Window { self.window_state.lock().taskbar_icon = taskbar_icon; } + pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) { + if unsafe { winuser::GetSystemMetrics(winuser::SM_IMMENABLED) } != 0 { + let mut composition_form = COMPOSITIONFORM { + dwStyle: CFS_POINT, + ptCurrentPos: POINT { x, y }, + rcArea: unsafe { mem::zeroed() }, + }; + unsafe { + let himc = winapi::um::imm::ImmGetContext(self.window.0); + winapi::um::imm::ImmSetCompositionWindow(himc, &mut composition_form); + winapi::um::imm::ImmReleaseContext(self.window.0, himc); + } + } + } + #[inline] - pub fn set_ime_position(&self, _position: Position) { - warn!("`Window::set_ime_position` is ignored on Windows") + pub fn set_ime_position(&self, spot: Position) { + let (x, y) = spot.to_physical::(self.scale_factor()).into(); + self.set_ime_position_physical(x, y); } #[inline] diff --git a/src/window.rs b/src/window.rs index 8cf6e0669b..9ed2de06a9 100644 --- a/src/window.rs +++ b/src/window.rs @@ -677,7 +677,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Windows:** Unsupported. + /// - **iOS / Android / Web:** Unsupported. #[inline] pub fn set_ime_position>(&self, position: P) { self.window.set_ime_position(position.into()) From 6f70fd90b97175d0776f04237730e2b255af29cd Mon Sep 17 00:00:00 2001 From: Viktor Zoutman Date: Thu, 10 Dec 2020 12:09:08 +0100 Subject: [PATCH 222/239] Windows: Changed thread_event_target_callback's WM_DESTROY to WM_NCDESTROY (#1780) --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f574e6d3..ee61604043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory. - On Windows, implement `Window::set_ime_position`. - **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`. - On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 6b3d287a83..3ccd55c1d0 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1939,7 +1939,7 @@ unsafe extern "system" fn thread_event_target_callback( // the closure to catch_unwind directly so that the match body indendation wouldn't change and // the git blame and history would be preserved. let callback = || match msg { - winuser::WM_DESTROY => { + winuser::WM_NCDESTROY => { Box::from_raw(subclass_input); drop(subclass_input); 0 From 6db308f1e9388986f8c0332d37adf95d051712a3 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Thu, 10 Dec 2020 19:12:46 +0300 Subject: [PATCH 223/239] Release 0.24.0 --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee61604043..559cbc8d13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# 0.24.0 (2020-12-09) - On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory. - On Windows, implement `Window::set_ime_position`. diff --git a/Cargo.toml b/Cargo.toml index 068b2bc460..1c776cd049 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.23.0" +version = "0.24.0" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index daff218581..e387ec0d04 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ```toml [dependencies] -winit = "0.23.0" +winit = "0.24.0" ``` ## [Documentation](https://docs.rs/winit) From 39573d65d053219385e0333ee50cf10cf477bd99 Mon Sep 17 00:00:00 2001 From: relrelb Date: Sun, 13 Dec 2020 20:06:53 +0200 Subject: [PATCH 224/239] Windows: Preserve minimized/maximized state in fullscreen (#1784) --- CHANGELOG.md | 19 ++++++---- src/platform_impl/windows/window.rs | 46 ++++++----------------- src/platform_impl/windows/window_state.rs | 17 ++++----- 3 files changed, 31 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 559cbc8d13..15880942fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,25 +1,30 @@ +# Unreleased + +- On Windows, fix fullscreen not preserving minimized/maximized state. + # 0.24.0 (2020-12-09) - On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory. - On Windows, implement `Window::set_ime_position`. - **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`. +- **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`. - On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme. - On Windows, fix bug causing message boxes to appear delayed. -- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. -- On Windows, fix alt-tab behaviour by removing borderless fullscreen "always on top" flag. +- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false. +- On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag. - On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions. - **Breaking:** On Windows, include prefix byte in scancodes. -- On Wayland, fix window not being resizeable when using `with_min_inner_size` in `WindowBuilder`. +- On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`. - On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland. -- On Windows, fix use after free crash during window destruction. +- On Windows, fix use-after-free crash during window destruction. - On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input. -- On macOS, fix compilation when targeting aarch64 +- On macOS, fix compilation when targeting aarch64. - On X11, fix `Window::request_redraw` not waking the event loop. - On Wayland, the keypad arrow keys are now recognized. - **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`. - Added `request_user_attention` method to `Window`. -- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`. -- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`. +- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`. +- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`. - On Wayland, default font size in CSD increased from 11 to 17. - On Windows, fix bug causing message boxes to appear delayed. - On Android, support multi-touch. diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index c444863e37..b0eb22316a 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -403,20 +403,6 @@ impl Window { drop(window_state_lock); self.thread_executor.execute_in_thread(move || { - let mut window_state_lock = window_state.lock(); - - // Save window bounds before entering fullscreen - match (&old_fullscreen, &fullscreen) { - (&None, &Some(_)) => { - let client_rect = util::get_client_rect(window.0).unwrap(); - window_state_lock.saved_window = Some(SavedWindow { - client_rect, - scale_factor: window_state_lock.scale_factor, - }); - } - _ => (), - } - // Change video mode if we're transitioning to or from exclusive // fullscreen match (&old_fullscreen, &fullscreen) { @@ -490,7 +476,7 @@ impl Window { } // Update window style - WindowState::set_window_flags(window_state_lock, window.0, |f| { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set( WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN, matches!(fullscreen, Some(Fullscreen::Exclusive(_))), @@ -504,6 +490,15 @@ impl Window { // Update window bounds match &fullscreen { Some(fullscreen) => { + // Save window bounds before entering fullscreen + let placement = unsafe { + let mut placement = mem::zeroed(); + winuser::GetWindowPlacement(window.0, &mut placement); + placement + }; + + window_state.lock().saved_window = Some(SavedWindow { placement }); + let monitor = match &fullscreen { Fullscreen::Exclusive(video_mode) => video_mode.monitor(), Fullscreen::Borderless(Some(monitor)) => monitor.clone(), @@ -530,27 +525,10 @@ impl Window { } None => { let mut window_state_lock = window_state.lock(); - if let Some(SavedWindow { - client_rect, - scale_factor, - }) = window_state_lock.saved_window.take() - { - window_state_lock.scale_factor = scale_factor; + if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() { drop(window_state_lock); - let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap(); - unsafe { - winuser::SetWindowPos( - window.0, - ptr::null_mut(), - client_rect.left, - client_rect.top, - client_rect.right - client_rect.left, - client_rect.bottom - client_rect.top, - winuser::SWP_ASYNCWINDOWPOS - | winuser::SWP_NOZORDER - | winuser::SWP_NOACTIVATE, - ); + winuser::SetWindowPlacement(window.0, &placement); winuser::InvalidateRgn(window.0, ptr::null_mut(), 0); } } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 1369fe7210..d9b43f0612 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -39,8 +39,7 @@ pub struct WindowState { #[derive(Clone)] pub struct SavedWindow { - pub client_rect: RECT, - pub scale_factor: f64, + pub placement: winuser::WINDOWPLACEMENT, } #[derive(Clone)] @@ -86,11 +85,6 @@ bitflags! { const MINIMIZED = 1 << 12; - const FULLSCREEN_AND_MASK = !( - WindowFlags::DECORATIONS.bits | - WindowFlags::RESIZABLE.bits | - WindowFlags::MAXIMIZED.bits - ); const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits; const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; @@ -181,10 +175,7 @@ impl MouseProperties { impl WindowFlags { fn mask(mut self) -> WindowFlags { if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) { - self &= WindowFlags::FULLSCREEN_AND_MASK; self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK; - } else if self.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN) { - self &= WindowFlags::FULLSCREEN_AND_MASK; } if !self.contains(WindowFlags::VISIBLE) { self &= WindowFlags::INVISIBLE_AND_MASK; @@ -235,6 +226,12 @@ impl WindowFlags { style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU; style_ex |= WS_EX_ACCEPTFILES; + if self.intersects( + WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN, + ) { + style &= !WS_OVERLAPPEDWINDOW; + } + (style, style_ex) } From 932cbe40bff9f034d7aaad590470d44880fa14c9 Mon Sep 17 00:00:00 2001 From: Samuel Date: Tue, 15 Dec 2020 03:31:13 -0300 Subject: [PATCH 225/239] On Windows, fix bug causing mouse capture to not be released. (#1797) --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 24 +++++++++++++++-------- src/platform_impl/windows/window_state.rs | 4 ++-- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15880942fc..602c209da9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On Windows, fix bug causing mouse capture to not be released. - On Windows, fix fullscreen not preserving minimized/maximized state. # 0.24.0 (2020-12-09) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3ccd55c1d0..2a24958587 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -619,15 +619,17 @@ fn subclass_event_target_window( /// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of /// the window. unsafe fn capture_mouse(window: HWND, window_state: &mut WindowState) { - window_state.mouse.buttons_down += 1; + window_state.mouse.capture_count += 1; winuser::SetCapture(window); } /// Release mouse input, stopping windows on this thread from receiving mouse input when the cursor /// is outside the window. -unsafe fn release_mouse(window_state: &mut WindowState) { - window_state.mouse.buttons_down = window_state.mouse.buttons_down.saturating_sub(1); - if window_state.mouse.buttons_down == 0 { +unsafe fn release_mouse(mut window_state: parking_lot::MutexGuard<'_, WindowState>) { + window_state.mouse.capture_count = window_state.mouse.capture_count.saturating_sub(1); + if window_state.mouse.capture_count == 0 { + // ReleaseCapture() causes a WM_CAPTURECHANGED where we lock the window_state. + drop(window_state); winuser::ReleaseCapture(); } } @@ -1192,7 +1194,7 @@ unsafe extern "system" fn public_window_callback( ElementState::Released, MouseButton::Left, WindowEvent::MouseInput, }; - release_mouse(&mut *subclass_input.window_state.lock()); + release_mouse(subclass_input.window_state.lock()); update_modifiers(window, subclass_input); @@ -1234,7 +1236,7 @@ unsafe extern "system" fn public_window_callback( ElementState::Released, MouseButton::Right, WindowEvent::MouseInput, }; - release_mouse(&mut *subclass_input.window_state.lock()); + release_mouse(subclass_input.window_state.lock()); update_modifiers(window, subclass_input); @@ -1276,7 +1278,7 @@ unsafe extern "system" fn public_window_callback( ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput, }; - release_mouse(&mut *subclass_input.window_state.lock()); + release_mouse(subclass_input.window_state.lock()); update_modifiers(window, subclass_input); @@ -1320,7 +1322,7 @@ unsafe extern "system" fn public_window_callback( }; let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); - release_mouse(&mut *subclass_input.window_state.lock()); + release_mouse(subclass_input.window_state.lock()); update_modifiers(window, subclass_input); @@ -1336,6 +1338,12 @@ unsafe extern "system" fn public_window_callback( 0 } + winuser::WM_CAPTURECHANGED => { + // window lost mouse capture + subclass_input.window_state.lock().mouse.capture_count = 0; + 0 + } + winuser::WM_TOUCH => { let pcount = LOWORD(wparam as DWORD) as usize; let mut inputs = Vec::with_capacity(pcount); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index d9b43f0612..70c5330aed 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -45,7 +45,7 @@ pub struct SavedWindow { #[derive(Clone)] pub struct MouseProperties { pub cursor: CursorIcon, - pub buttons_down: u32, + pub capture_count: u32, cursor_flags: CursorFlags, pub last_position: Option>, } @@ -102,7 +102,7 @@ impl WindowState { WindowState { mouse: MouseProperties { cursor: CursorIcon::default(), - buttons_down: 0, + capture_count: 0, cursor_flags: CursorFlags::empty(), last_position: None, }, From c05952b813a84492338debc75bb0ac21ebc767dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20R=C3=83=C2=B8yset?= Date: Sun, 20 Dec 2020 12:54:42 +0100 Subject: [PATCH 226/239] On Windows, improve handling of window destruction (#1798) --- src/platform_impl/windows/event_loop.rs | 83 ++++++++++++++++++++++--- src/platform_impl/windows/window.rs | 2 + 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 2a24958587..da3c48d3eb 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -4,6 +4,7 @@ mod runner; use parking_lot::Mutex; use std::{ + cell::Cell, collections::VecDeque, marker::PhantomData, mem, panic, ptr, @@ -86,6 +87,8 @@ pub(crate) struct SubclassInput { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, pub file_drop_handler: Option, + pub subclass_removed: Cell, + pub recurse_depth: Cell, } impl SubclassInput { @@ -616,6 +619,17 @@ fn subclass_event_target_window( } } +fn remove_event_target_window_subclass(window: HWND) { + let removal_result = unsafe { + commctrl::RemoveWindowSubclass( + window, + Some(thread_event_target_callback::), + THREAD_EVENT_TARGET_SUBCLASS_ID, + ) + }; + assert_eq!(removal_result, 1); +} + /// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of /// the window. unsafe fn capture_mouse(window: HWND, window_state: &mut WindowState) { @@ -650,6 +664,17 @@ pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) assert_eq!(subclass_result, 1); } +fn remove_window_subclass(window: HWND) { + let removal_result = unsafe { + commctrl::RemoveWindowSubclass( + window, + Some(public_window_callback::), + WINDOW_SUBCLASS_ID, + ) + }; + assert_eq!(removal_result, 1); +} + fn normalize_pointer_pressure(pressure: u32) -> Option { match pressure { 1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)), @@ -752,11 +777,41 @@ unsafe extern "system" fn public_window_callback( msg: UINT, wparam: WPARAM, lparam: LPARAM, - _: UINT_PTR, + uidsubclass: UINT_PTR, subclass_input_ptr: DWORD_PTR, ) -> LRESULT { - let subclass_input = &*(subclass_input_ptr as *const SubclassInput); + let subclass_input_ptr = subclass_input_ptr as *mut SubclassInput; + let (result, subclass_removed, recurse_depth) = { + let subclass_input = &*subclass_input_ptr; + subclass_input + .recurse_depth + .set(subclass_input.recurse_depth.get() + 1); + + let result = + public_window_callback_inner(window, msg, wparam, lparam, uidsubclass, subclass_input); + + let subclass_removed = subclass_input.subclass_removed.get(); + let recurse_depth = subclass_input.recurse_depth.get() - 1; + subclass_input.recurse_depth.set(recurse_depth); + (result, subclass_removed, recurse_depth) + }; + + if subclass_removed && recurse_depth == 0 { + Box::from_raw(subclass_input_ptr); + } + + result +} + +unsafe fn public_window_callback_inner( + window: HWND, + msg: UINT, + wparam: WPARAM, + lparam: LPARAM, + _: UINT_PTR, + subclass_input: &SubclassInput, +) -> LRESULT { winuser::RedrawWindow( subclass_input.event_loop_runner.thread_msg_target(), ptr::null(), @@ -816,8 +871,8 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_NCDESTROY => { - drop(subclass_input); - Box::from_raw(subclass_input_ptr as *mut SubclassInput); + remove_window_subclass::(window); + subclass_input.subclass_removed.set(true); 0 } @@ -1931,8 +1986,7 @@ unsafe extern "system" fn thread_event_target_callback( _: UINT_PTR, subclass_input_ptr: DWORD_PTR, ) -> LRESULT { - let subclass_input = &mut *(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput); - let runner = subclass_input.event_loop_runner.clone(); + let subclass_input = Box::from_raw(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput); if msg != winuser::WM_PAINT { winuser::RedrawWindow( @@ -1943,13 +1997,15 @@ unsafe extern "system" fn thread_event_target_callback( ); } + let mut subclass_removed = false; + // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing // the closure to catch_unwind directly so that the match body indendation wouldn't change and // the git blame and history would be preserved. let callback = || match msg { winuser::WM_NCDESTROY => { - Box::from_raw(subclass_input); - drop(subclass_input); + remove_event_target_window_subclass::(window); + subclass_removed = true; 0 } // Because WM_PAINT comes after all other messages, we use it during modal loops to detect @@ -2156,5 +2212,14 @@ unsafe extern "system" fn thread_event_target_callback( _ => commctrl::DefSubclassProc(window, msg, wparam, lparam), }; - runner.catch_unwind(callback).unwrap_or(-1) + let result = subclass_input + .event_loop_runner + .catch_unwind(callback) + .unwrap_or(-1); + if subclass_removed { + mem::drop(subclass_input); + } else { + Box::into_raw(subclass_input); + } + result } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index b0eb22316a..0d7a5a7c39 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -114,6 +114,8 @@ impl Window { window_state: win.window_state.clone(), event_loop_runner: event_loop.runner_shared.clone(), file_drop_handler, + subclass_removed: Cell::new(false), + recurse_depth: Cell::new(0), }; event_loop::subclass_window(win.window.0, subclass_input); From 38fccebe1fbc4226c75d6180e5317bd93c024951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20R=C3=83=C2=B8yset?= Date: Sun, 20 Dec 2020 17:59:46 +0100 Subject: [PATCH 227/239] On Windows, change the default window size (#1805) --- CHANGELOG.md | 1 + src/platform_impl/windows/window.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 602c209da9..3ea826f739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600). - On Windows, fix bug causing mouse capture to not be released. - On Windows, fix fullscreen not preserving minimized/maximized state. diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 0d7a5a7c39..0eb7616094 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -785,7 +785,7 @@ unsafe fn init( let dimensions = attributes .inner_size - .unwrap_or_else(|| PhysicalSize::new(1024, 768).into()); + .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); win.set_inner_size(dimensions); if attributes.maximized { // Need to set MAXIMIZED after setting `inner_size` as From 9d63fc7ca0f445de3cea1018729a200e8e7dd6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20R=C3=83=C2=B8yset?= Date: Tue, 5 Jan 2021 17:39:13 +0100 Subject: [PATCH 228/239] On Windows, set the cursor icon when the cursor first enters a window (#1807) --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ea826f739..ea0df5a7b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor. - On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600). - On Windows, fix bug causing mouse capture to not be released. - On Windows, fix fullscreen not preserving minimized/maximized state. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index da3c48d3eb..2b518e9ed6 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -20,7 +20,7 @@ use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR}; use winapi::{ shared::{ - minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WPARAM}, + minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WORD, WPARAM}, windef::{HWND, POINT, RECT}, windowsx, winerror, }, @@ -1664,11 +1664,11 @@ unsafe fn public_window_callback_inner( winuser::WM_SETCURSOR => { let set_cursor_to = { let window_state = subclass_input.window_state.lock(); - if window_state - .mouse - .cursor_flags() - .contains(CursorFlags::IN_WINDOW) - { + // The return value for the preceding `WM_NCHITTEST` message is conveniently + // provided through the low-order word of lParam. We use that here since + // `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement. + let in_client_area = LOWORD(lparam as DWORD) == winuser::HTCLIENT as WORD; + if in_client_area { Some(window_state.mouse.cursor) } else { None From d1a7749df56656e356840112ebcbb3c9f34de248 Mon Sep 17 00:00:00 2001 From: alula <6276139+alula@users.noreply.github.com> Date: Tue, 12 Jan 2021 08:25:56 +0100 Subject: [PATCH 229/239] Android: Do not mark unhandled events as handled. (#1820) --- CHANGELOG.md | 1 + src/platform_impl/android/mod.rs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0df5a7b0..143be66dfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600). - On Windows, fix bug causing mouse capture to not be released. - On Windows, fix fullscreen not preserving minimized/maximized state. +- On Android, unimplemented events are marked as unhandled on the native event loop. # 0.24.0 (2020-12-09) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index b8df45151b..31447ddda4 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -176,6 +176,7 @@ impl EventLoop { if let Some(input_queue) = ndk_glue::input_queue().as_ref() { while let Some(event) = input_queue.get_event() { if let Some(event) = input_queue.pre_dispatch(event) { + let mut handled = true; let window_id = window::WindowId(WindowId); let device_id = event::DeviceId(DeviceId); match &event { @@ -191,7 +192,10 @@ impl EventLoop { MotionAction::Cancel => { Some(event::TouchPhase::Cancelled) } - _ => None, // TODO mouse events + _ => { + handled = false; + None // TODO mouse events + } }; if let Some(phase) = phase { let pointers: Box< @@ -235,9 +239,12 @@ impl EventLoop { } } } - InputEvent::KeyEvent(_) => {} // TODO + InputEvent::KeyEvent(_) => { + // TODO + handled = false; + } }; - input_queue.finish_event(event, true); + input_queue.finish_event(event, handled); } } } From 05fe983757a85fb79267c1a3b1015aebd8c32894 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Wed, 13 Jan 2021 23:02:55 +0100 Subject: [PATCH 230/239] android: Use event identifier instead of userdata pointer (#1826) ndk-glue currently sets both the `ident` field and user-data pointer to `0` or `1` for the event pipe and input queue respectively, to tell these sources apart. While it works to reinterpret this `data` pointer as integer identifier it shouldn't be abused for that, in particular when one may wish to provide extra information with an event in the future; then the `data` field is used as pointer (or abused as abstract value) for that. --- src/platform_impl/android/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 31447ddda4..add163a05f 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -30,7 +30,7 @@ enum EventSource { fn poll(poll: Poll) -> Option { match poll { - Poll::Event { data, .. } => match data as usize { + Poll::Event { ident, .. } => match ident { 0 => Some(EventSource::Callback), 1 => Some(EventSource::InputQueue), _ => unreachable!(), From ae5e58ac478a833fd6ace0be0d4373f5b4096e51 Mon Sep 17 00:00:00 2001 From: Marnix Kuijs Date: Mon, 18 Jan 2021 22:48:42 +0100 Subject: [PATCH 231/239] Windows gamepads --- CHANGELOG.md | 12 + Cargo.toml | 3 + examples/gamepad.rs | 48 +++ examples/gamepad_rumble.rs | 60 +++ src/event.rs | 187 ++------- src/event/device.rs | 363 +++++++++++++++++ src/lib.rs | 4 +- src/platform/windows.rs | 40 +- src/platform_impl/windows/event_loop.rs | 452 +++++++++++++++------ src/platform_impl/windows/gamepad.rs | 87 ++++ src/platform_impl/windows/mod.rs | 168 ++++++-- src/platform_impl/windows/raw_input.rs | 518 ++++++++++++++++++++++-- src/platform_impl/windows/util.rs | 8 +- src/platform_impl/windows/window.rs | 6 +- src/platform_impl/windows/xinput.rs | 330 +++++++++++++++ src/util.rs | 29 ++ tests/send_objects.rs | 4 +- 17 files changed, 1965 insertions(+), 354 deletions(-) create mode 100644 examples/gamepad.rs create mode 100644 examples/gamepad_rumble.rs create mode 100644 src/event/device.rs create mode 100644 src/platform_impl/windows/gamepad.rs create mode 100644 src/platform_impl/windows/xinput.rs create mode 100644 src/util.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 143be66dfd..04054ba0ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ - On Windows, fix bug causing mouse capture to not be released. - On Windows, fix fullscreen not preserving minimized/maximized state. - On Android, unimplemented events are marked as unhandled on the native event loop. +- Overhaul device event API: + - **Breaking**: `Event::DeviceEvent` split into `MouseEvent`, `KeyboardEvent`, and `GamepadEvent`. + - **Breaking**: Remove `DeviceEvent::Text` variant. + - **Breaking**: `DeviceId` split into `MouseId`, `KeyboardId`, and `GamepadHandle`. + - **Breaking**: Removed device IDs from `WindowEvent` variants. + - Add `enumerate` function on device ID types to list all attached devices of that type. + - Add `is_connected` function on device ID types check if the specified device is still available. + - **Breaking**: On Windows, rename `DeviceIdExtWindows` to `DeviceExtWindows`. + - Add `handle` function to retrieve the underlying `HANDLE`. +- On Windows, fix duplicate device events getting sent if Winit managed multiple windows. +- On Windows, raw mouse events now report Mouse4 and Mouse5 presses and releases. +- Added gamepad support on Windows via raw input and XInput. # 0.24.0 (2020-12-09) diff --git a/Cargo.toml b/Cargo.toml index 1c776cd049..4e078b6aeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ features = ["display_link"] [target.'cfg(target_os = "windows")'.dependencies] parking_lot = "0.11" +rusty-xinput = "1" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" @@ -66,6 +67,7 @@ features = [ "dwmapi", "errhandlingapi", "imm", + "hidpi", "hidusage", "libloaderapi", "objbase", @@ -81,6 +83,7 @@ features = [ "wingdi", "winnt", "winuser", + "xinput" ] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] diff --git a/examples/gamepad.rs b/examples/gamepad.rs new file mode 100644 index 0000000000..dd09ac85bd --- /dev/null +++ b/examples/gamepad.rs @@ -0,0 +1,48 @@ +use winit::event::device::{GamepadEvent, GamepadHandle}; +use winit::event::{Event, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::WindowBuilder; + +fn main() { + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_title("The world's worst video game") + .build(&event_loop) + .unwrap(); + + println!("enumerating gamepads:"); + for gamepad in GamepadHandle::enumerate(&event_loop) { + println!( + " gamepad={:?}\tport={:?}\tbattery level={:?}", + gamepad, + gamepad.port(), + gamepad.battery_level() + ); + } + + let deadzone = 0.12; + + event_loop.run(move |event, _, control_flow| { + match event { + Event::GamepadEvent(gamepad_handle, event) => { + match event { + // Discard any Axis events that has a corresponding Stick event. + GamepadEvent::Axis { stick: true, .. } => (), + + // Discard any Stick event that falls inside the stick's deadzone. + GamepadEvent::Stick { + x_value, y_value, .. + } if (x_value.powi(2) + y_value.powi(2)).sqrt() < deadzone => (), + + _ => println!("[{:?}] {:#?}", gamepad_handle, event), + } + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + } + }); +} diff --git a/examples/gamepad_rumble.rs b/examples/gamepad_rumble.rs new file mode 100644 index 0000000000..8ae6ccc8c9 --- /dev/null +++ b/examples/gamepad_rumble.rs @@ -0,0 +1,60 @@ +use std::time::Instant; +use winit::event_loop::EventLoop; + +#[derive(Debug, Clone)] +enum Rumble { + None, + Left, + Right, +} + +fn main() { + let event_loop = EventLoop::new(); + + // You should generally use `GamepadEvent::Added/Removed` to detect gamepads, as doing that will + // allow you to more easily support gamepad hotswapping. However, we're using `enumerate` here + // because it makes this example more concise. + let gamepads = winit::event::device::GamepadHandle::enumerate(&event_loop).collect::>(); + + let rumble_patterns = &[ + (0.5, Rumble::None), + (2.0, Rumble::Left), + (0.5, Rumble::None), + (2.0, Rumble::Right), + ]; + let mut rumble_iter = rumble_patterns.iter().cloned().cycle(); + + let mut active_pattern = rumble_iter.next().unwrap(); + let mut timeout = active_pattern.0; + let mut timeout_start = Instant::now(); + + event_loop.run(move |_, _, _| { + if timeout <= active_pattern.0 { + let t = (timeout / active_pattern.0) * std::f64::consts::PI; + let intensity = t.sin(); + + for g in &gamepads { + let result = match active_pattern.1 { + Rumble::Left => g.rumble(intensity, 0.0), + Rumble::Right => g.rumble(0.0, intensity), + Rumble::None => Ok(()), + }; + + if let Err(e) = result { + println!("Rumble failed: {:?}", e); + } + } + + timeout = (Instant::now() - timeout_start).as_millis() as f64 / 1000.0; + } else { + active_pattern = rumble_iter.next().unwrap(); + println!( + "Rumbling {:?} for {:?} seconds", + active_pattern.1, active_pattern.0 + ); + + timeout = 0.0; + timeout_start = Instant::now(); + } + }); +} diff --git a/src/event.rs b/src/event.rs index f39ddaac50..a6357d4af4 100644 --- a/src/event.rs +++ b/src/event.rs @@ -38,10 +38,11 @@ use std::path::PathBuf; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - platform_impl, window::{Theme, WindowId}, }; +pub mod device; + /// Describes a generic event. /// /// See the module-level docs for more information on the event loop manages each event. @@ -61,11 +62,12 @@ pub enum Event<'a, T: 'static> { event: WindowEvent<'a>, }, - /// Emitted when the OS sends an event to a device. - DeviceEvent { - device_id: DeviceId, - event: DeviceEvent, - }, + MouseEvent(device::MouseId, device::MouseEvent), + /// Emitted when a keyboard device has generated input. + KeyboardEvent(device::KeyboardId, device::KeyboardEvent), + HidEvent(device::HidId, device::HidEvent), + /// Emitted when a gamepad/joystick device has generated input. + GamepadEvent(device::GamepadHandle, device::GamepadEvent), /// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event) UserEvent(T), @@ -127,10 +129,10 @@ impl Clone for Event<'static, T> { event: event.clone(), }, UserEvent(event) => UserEvent(event.clone()), - DeviceEvent { device_id, event } => DeviceEvent { - device_id: *device_id, - event: event.clone(), - }, + MouseEvent(id, event) => MouseEvent(id.clone(), event.clone()), + KeyboardEvent(id, event) => KeyboardEvent(id.clone(), event.clone()), + HidEvent(id, event) => HidEvent(id.clone(), event.clone()), + GamepadEvent(id, event) => GamepadEvent(id.clone(), event.clone()), NewEvents(cause) => NewEvents(cause.clone()), MainEventsCleared => MainEventsCleared, RedrawRequested(wid) => RedrawRequested(*wid), @@ -148,7 +150,10 @@ impl<'a, T> Event<'a, T> { match self { UserEvent(_) => Err(self), WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }), - DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }), + MouseEvent(id, event) => Ok(MouseEvent(id, event)), + KeyboardEvent(id, event) => Ok(KeyboardEvent(id, event)), + HidEvent(id, event) => Ok(HidEvent(id, event)), + GamepadEvent(id, event) => Ok(GamepadEvent(id, event)), NewEvents(cause) => Ok(NewEvents(cause)), MainEventsCleared => Ok(MainEventsCleared), RedrawRequested(wid) => Ok(RedrawRequested(wid)), @@ -168,7 +173,10 @@ impl<'a, T> Event<'a, T> { .to_static() .map(|event| WindowEvent { window_id, event }), UserEvent(event) => Some(UserEvent(event)), - DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }), + MouseEvent(id, event) => Some(MouseEvent(id, event)), + KeyboardEvent(id, event) => Some(KeyboardEvent(id, event)), + HidEvent(id, event) => Some(HidEvent(id, event)), + GamepadEvent(id, event) => Some(GamepadEvent(id, event)), NewEvents(cause) => Some(NewEvents(cause)), MainEventsCleared => Some(MainEventsCleared), RedrawRequested(wid) => Some(RedrawRequested(wid)), @@ -249,7 +257,6 @@ pub enum WindowEvent<'a> { /// An event from the keyboard has been received. KeyboardInput { - device_id: DeviceId, input: KeyboardInput, /// If `true`, the event was generated synthetically by winit /// in one of the following circumstances: @@ -272,8 +279,6 @@ pub enum WindowEvent<'a> { /// The cursor has moved on the window. CursorMoved { - device_id: DeviceId, - /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. @@ -283,14 +288,13 @@ pub enum WindowEvent<'a> { }, /// The cursor has entered the window. - CursorEntered { device_id: DeviceId }, + CursorEntered, /// The cursor has left the window. - CursorLeft { device_id: DeviceId }, + CursorLeft, /// A mouse wheel movement or touchpad scroll occurred. MouseWheel { - device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] @@ -299,7 +303,6 @@ pub enum WindowEvent<'a> { /// An mouse button press has been received. MouseInput { - device_id: DeviceId, state: ElementState, button: MouseButton, #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] @@ -311,18 +314,7 @@ pub enum WindowEvent<'a> { /// At the moment, only supported on Apple forcetouch-capable macbooks. /// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad /// is being pressed) and stage (integer representing the click level). - TouchpadPressure { - device_id: DeviceId, - pressure: f32, - stage: i64, - }, - - /// Motion on some analog axis. May report data redundant to other, more specific events. - AxisMotion { - device_id: DeviceId, - axis: AxisId, - value: f64, - }, + TouchpadPressure { pressure: f32, stage: i64 }, /// Touch event has been received Touch(Touch), @@ -368,11 +360,9 @@ impl Clone for WindowEvent<'static> { ReceivedCharacter(c) => ReceivedCharacter(*c), Focused(f) => Focused(*f), KeyboardInput { - device_id, input, is_synthetic, } => KeyboardInput { - device_id: *device_id, input: *input, is_synthetic: *is_synthetic, }, @@ -380,62 +370,38 @@ impl Clone for WindowEvent<'static> { ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()), #[allow(deprecated)] CursorMoved { - device_id, position, modifiers, } => CursorMoved { - device_id: *device_id, position: *position, modifiers: *modifiers, }, - CursorEntered { device_id } => CursorEntered { - device_id: *device_id, - }, - CursorLeft { device_id } => CursorLeft { - device_id: *device_id, - }, + CursorEntered => CursorEntered, + CursorLeft => CursorLeft, #[allow(deprecated)] MouseWheel { - device_id, delta, phase, modifiers, } => MouseWheel { - device_id: *device_id, delta: *delta, phase: *phase, modifiers: *modifiers, }, #[allow(deprecated)] MouseInput { - device_id, state, button, modifiers, } => MouseInput { - device_id: *device_id, state: *state, button: *button, modifiers: *modifiers, }, - TouchpadPressure { - device_id, - pressure, - stage, - } => TouchpadPressure { - device_id: *device_id, + TouchpadPressure { pressure, stage } => TouchpadPressure { pressure: *pressure, stage: *stage, }, - AxisMotion { - device_id, - axis, - value, - } => AxisMotion { - device_id: *device_id, - axis: *axis, - value: *value, - }, Touch(touch) => Touch(*touch), ThemeChanged(theme) => ThemeChanged(theme.clone()), ScaleFactorChanged { .. } => { @@ -459,69 +425,44 @@ impl<'a> WindowEvent<'a> { ReceivedCharacter(c) => Some(ReceivedCharacter(c)), Focused(focused) => Some(Focused(focused)), KeyboardInput { - device_id, input, is_synthetic, } => Some(KeyboardInput { - device_id, input, is_synthetic, }), ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)), #[allow(deprecated)] CursorMoved { - device_id, position, modifiers, } => Some(CursorMoved { - device_id, position, modifiers, }), - CursorEntered { device_id } => Some(CursorEntered { device_id }), - CursorLeft { device_id } => Some(CursorLeft { device_id }), + CursorEntered => Some(CursorEntered), + CursorLeft => Some(CursorLeft), #[allow(deprecated)] MouseWheel { - device_id, delta, phase, modifiers, } => Some(MouseWheel { - device_id, delta, phase, modifiers, }), #[allow(deprecated)] MouseInput { - device_id, state, button, modifiers, } => Some(MouseInput { - device_id, state, button, modifiers, }), - TouchpadPressure { - device_id, - pressure, - stage, - } => Some(TouchpadPressure { - device_id, - pressure, - stage, - }), - AxisMotion { - device_id, - axis, - value, - } => Some(AxisMotion { - device_id, - axis, - value, - }), + TouchpadPressure { pressure, stage } => Some(TouchpadPressure { pressure, stage }), Touch(touch) => Some(Touch(touch)), ThemeChanged(theme) => Some(ThemeChanged(theme)), ScaleFactorChanged { .. } => None, @@ -529,73 +470,6 @@ impl<'a> WindowEvent<'a> { } } -/// Identifier of an input device. -/// -/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which -/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or -/// physical. Virtual devices typically aggregate inputs from multiple physical devices. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(pub(crate) platform_impl::DeviceId); - -impl DeviceId { - /// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return - /// value of this function is that it will always be equal to itself and to future values returned - /// by this function. No other guarantees are made. This may be equal to a real `DeviceId`. - /// - /// **Passing this into a winit function will result in undefined behavior.** - pub unsafe fn dummy() -> Self { - DeviceId(platform_impl::DeviceId::dummy()) - } -} - -/// Represents raw hardware events that are not associated with any particular window. -/// -/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person -/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because -/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs -/// may not match. -/// -/// Note that these events are delivered regardless of input focus. -#[derive(Clone, Debug, PartialEq)] -pub enum DeviceEvent { - Added, - Removed, - - /// Change in physical position of a pointing device. - /// - /// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`. - MouseMotion { - /// (x, y) change in position in unspecified units. - /// - /// Different devices may use different units. - delta: (f64, f64), - }, - - /// Physical scroll event - MouseWheel { - delta: MouseScrollDelta, - }, - - /// Motion on some analog axis. This event will be reported for all arbitrary input devices - /// that winit supports on this platform, including mouse devices. If the device is a mouse - /// device then this will be reported alongside the MouseMotion event. - Motion { - axis: AxisId, - value: f64, - }, - - Button { - button: ButtonId, - state: ElementState, - }, - - Key(KeyboardInput), - - Text { - codepoint: char, - }, -} - /// Describes a keyboard input event. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -651,7 +525,6 @@ pub enum TouchPhase { /// device against their face. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Touch { - pub device_id: DeviceId, pub phase: TouchPhase, pub location: PhysicalPosition, /// Describes how hard the screen was pressed. May be `None` if the platform @@ -731,7 +604,7 @@ pub type AxisId = u32; pub type ButtonId = u32; /// Describes the input state of a key. -#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ElementState { Pressed, diff --git a/src/event/device.rs b/src/event/device.rs new file mode 100644 index 0000000000..3fd7c08393 --- /dev/null +++ b/src/event/device.rs @@ -0,0 +1,363 @@ +//! Raw hardware events that are not associated with any particular window. +//! +//! Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person +//! game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because +//! window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs +//! may not match. +//! +//! All attached devices are guaranteed to emit an `Added` event upon the initialization of the event loop. +//! +//! Note that device events are always delivered regardless of window focus. + +use crate::{ + dpi::PhysicalPosition, + event::{AxisId, ButtonId, ElementState, KeyboardInput, ModifiersState, MouseButton}, + event_loop::EventLoop, + platform_impl, +}; +use std::{fmt, io}; + +/// A hint suggesting the type of button that was pressed. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GamepadButton { + Start, + Select, + + /// The north face button. + /// + /// * Nintendo: X + /// * Playstation: Triangle + /// * XBox: Y + North, + /// The south face button. + /// + /// * Nintendo: B + /// * Playstation: X + /// * XBox: A + South, + /// The east face button. + /// + /// * Nintendo: A + /// * Playstation: Circle + /// * XBox: B + East, + /// The west face button. + /// + /// * Nintendo: Y + /// * Playstation: Square + /// * XBox: X + West, + + LeftStick, + RightStick, + + LeftTrigger, + RightTrigger, + + LeftShoulder, + RightShoulder, + + DPadUp, + DPadDown, + DPadLeft, + DPadRight, +} + +/// A hint suggesting the type of axis that moved. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum GamepadAxis { + LeftStickX, + LeftStickY, + + RightStickX, + RightStickY, + + LeftTrigger, + RightTrigger, +} + +/// A given joystick's side on the gamepad. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Side { + Left, + Right, +} + +/// Raw mouse events. +/// +/// See the module-level docs for more information. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum MouseEvent { + /// A mouse device has been added. + Added, + /// A mouse device has been removed. + Removed, + /// A mouse button has been pressed or released. + Button { + state: ElementState, + button: MouseButton, + }, + /// Relative change in physical position of a pointing device. + /// + /// This represents raw, unfiltered physical motion, NOT the position of the mouse. Accordingly, + /// the values provided here are the change in position of the mouse since the previous + /// `MovedRelative` event. + MovedRelative(f64, f64), + /// Change in absolute position of a pointing device. + /// + /// The `PhysicalPosition` value is the new position of the cursor relative to the desktop. This + /// generally doesn't get output by standard mouse devices, but can get output from tablet devices. + MovedAbsolute(PhysicalPosition), + /// Change in rotation of mouse wheel. + Wheel(f64, f64), +} + +/// Raw keyboard events. +/// +/// See the module-level docs for more information. +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub enum KeyboardEvent { + /// A keyboard device has been added. + Added, + /// A keyboard device has been removed. + Removed, + /// A key has been pressed or released. + Input(KeyboardInput), + ModifiersChanged(ModifiersState), +} + +/// Raw HID event. +/// +/// See the module-level docs for more information. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum HidEvent { + /// A Human Interface Device device has been added. + Added, + /// A Human Interface Device device has been removed. + Removed, + /// A raw data packet has been received from the Human Interface Device. + Data(Box<[u8]>), +} + +/// Gamepad/joystick events. +/// +/// These can be generated by any of a variety of game controllers, including (but not limited to) +/// gamepads, joysicks, and HOTAS devices. +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] +pub enum GamepadEvent { + /// A gamepad/joystick device has been added. + Added, + /// A gamepad/joystick device has been removed. + Removed, + /// An analog axis value on the gamepad/joystick has changed. + /// + /// Winit does NOT provide [deadzone filtering](https://www.quora.com/What-does-the-term-joystick-deadzone-mean), + /// and such filtering may have to be provided by API users for joystick axes. + Axis { + axis_id: AxisId, + /// A hint regarding the physical axis that moved. + /// + /// On traditional gamepads (such as an X360 controller) this can be assumed to have a + /// non-`None` value; however, other joystick devices with more varied layouts generally won't + /// provide a value here. + /// + /// TODO: DISCUSS CONTROLLER MAPPING ONCE WE FIGURE OUT WHAT WE'RE DOING THERE. + axis: Option, + value: f64, + /// Whether or not this axis has also produced a [`GamepadEvent::Stick`] event. + stick: bool, + }, + /// A two-axis joystick's value has changed. + /// + /// This is mainly provided to assist with deadzone calculation, as proper deadzones should be + /// calculated via the combined distance of each joystick axis from the center of the joystick, + /// rather than per-axis. + /// + /// Note that this is only guaranteed to be emitted for traditionally laid out gamepads. More + /// complex joysticks generally don't report specifics of their layout to the operating system, + /// preventing Winit from automatically aggregating their axis input into two-axis stick events. + Stick { + /// The X axis' ID. + x_id: AxisId, + /// The Y axis' ID. + y_id: AxisId, + x_value: f64, + y_value: f64, + /// Which joystick side produced this event. + side: Side, + }, + Button { + button_id: ButtonId, + /// A hint regarding the location of the button. + /// + /// The caveats on the `Axis.hint` field also apply here. + button: Option, + state: ElementState, + }, +} + +/// Error reported if a rumble attempt unexpectedly failed. +#[derive(Debug)] +pub enum RumbleError { + /// The device is no longer connected. + DeviceNotConnected, + /// An unknown OS error has occured. + OsError(io::Error), +} + +/// A typed identifier for a mouse device. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct MouseId(pub(crate) platform_impl::MouseId); +/// A typed identifier for a keyboard device. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct KeyboardId(pub(crate) platform_impl::KeyboardId); +/// A typed if for a Human Interface Device. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct HidId(pub(crate) platform_impl::HidId); +/// A handle to a gamepad/joystick device. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct GamepadHandle(pub(crate) platform_impl::GamepadHandle); + +impl MouseId { + /// Returns a dummy `MouseId`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `MouseId`. + /// + /// **Passing this into a winit function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + MouseId(platform_impl::MouseId::dummy()) + } + + /// Enumerate all attached mouse devices. + pub fn enumerate(event_loop: &EventLoop) -> impl '_ + Iterator { + platform_impl::MouseId::enumerate(&event_loop.event_loop) + } + + /// Check to see if this mouse device is still connected. + pub fn is_connected(&self) -> bool { + self.0.is_connected() + } +} + +impl KeyboardId { + /// Returns a dummy `KeyboardId`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `KeyboardId`. + /// + /// **Passing this into a winit function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + KeyboardId(platform_impl::KeyboardId::dummy()) + } + + /// Enumerate all attached keyboard devices. + pub fn enumerate(event_loop: &EventLoop) -> impl '_ + Iterator { + platform_impl::KeyboardId::enumerate(&event_loop.event_loop) + } + + /// Check to see if this keyboard device is still connected. + pub fn is_connected(&self) -> bool { + self.0.is_connected() + } +} + +impl HidId { + /// Returns a dummy `HidId`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `HidId`. + /// + /// **Passing this into a winit function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + HidId(platform_impl::HidId::dummy()) + } + + /// Enumerate all attached keyboard devices. + pub fn enumerate(event_loop: &EventLoop) -> impl '_ + Iterator { + platform_impl::HidId::enumerate(&event_loop.event_loop) + } + + /// Check to see if this keyboard device is still connected. + pub fn is_connected(&self) -> bool { + self.0.is_connected() + } +} + +impl GamepadHandle { + /// Returns a dummy `GamepadHandle`, useful for unit testing. The only guarantee made about the return + /// value of this function is that it will always be equal to itself and to future values returned + /// by this function. No other guarantees are made. This may be equal to a real `GamepadHandle`. + /// + /// **Passing this into a winit function will result in undefined behavior.** + pub unsafe fn dummy() -> Self { + GamepadHandle(platform_impl::GamepadHandle::dummy()) + } + + /// Enumerate all attached gamepad/joystick devices. + pub fn enumerate(event_loop: &EventLoop) -> impl '_ + Iterator { + platform_impl::GamepadHandle::enumerate(&event_loop.event_loop) + } + + /// Check to see if this gamepad/joystick device is still connected. + pub fn is_connected(&self) -> bool { + self.0.is_connected() + } + + /// Attempts to set the rumble values for an attached controller. Input values are automatically + /// bound to a [`0.0`, `1.0`] range. + /// + /// Certain gamepads assign different usages to the left and right motors - for example, X360 + /// controllers treat the left motor as a low-frequency rumble and the right motor as a + /// high-frequency rumble. However, this cannot necessarily be assumed for all gamepad devices. + /// + /// Note that, if the given gamepad does not support rumble, no error value gets thrown. + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> { + self.0.rumble(left_speed, right_speed) + } + + /// Gets the port number assigned to the gamepad. + pub fn port(&self) -> Option { + self.0.port() + } + + /// Gets the controller's battery level. + /// + /// If the controller doesn't report a battery level, this returns `None`. + pub fn battery_level(&self) -> Option { + self.0.battery_level() + } +} + +/// TODO: IS THIS THE RIGHT ABSTRACTION FOR ALL PLATFORMS? +/// This is exposed in its current form because it's what Microsoft does for XInput, and that's my +/// (@Osspial's) main point of reference. If you're implementing this on a different platform and +/// that platform exposes battery level differently, please bring it up in the tracking issue! +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum BatteryLevel { + Empty, + Low, + Medium, + Full, +} + +impl fmt::Debug for MouseId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) + } +} + +impl fmt::Debug for KeyboardId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) + } +} + +impl fmt::Debug for HidId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) + } +} + +impl fmt::Debug for GamepadHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + self.0.fmt(f) + } +} diff --git a/src/lib.rs b/src/lib.rs index 51f4a8634a..54d051e4a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -156,7 +156,7 @@ pub mod event; pub mod event_loop; mod icon; pub mod monitor; +pub mod platform; mod platform_impl; +mod util; pub mod window; - -pub mod platform; diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 05839d89c1..11911f569e 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -9,7 +9,7 @@ use winapi::shared::windef::HWND; use crate::{ dpi::PhysicalSize, - event::DeviceId, + event::device::{GamepadHandle, KeyboardId, MouseId}, event_loop::EventLoop, monitor::MonitorHandle, platform_impl::{EventLoop as WindowsEventLoop, WinIcon}, @@ -183,19 +183,51 @@ impl MonitorHandleExtWindows for MonitorHandle { } } -/// Additional methods on `DeviceId` that are specific to Windows. -pub trait DeviceIdExtWindows { +/// Additional methods on device types that are specific to Windows. +pub trait DeviceExtWindows { /// Returns an identifier that persistently refers to this specific device. /// /// Will return `None` if the device is no longer available. fn persistent_identifier(&self) -> Option; + + /// Returns the handle of the device - `HANDLE`. + fn handle(&self) -> *mut c_void; +} + +impl DeviceExtWindows for MouseId { + #[inline] + fn persistent_identifier(&self) -> Option { + self.0.persistent_identifier() + } + + #[inline] + fn handle(&self) -> *mut c_void { + self.0.handle() as _ + } +} + +impl DeviceExtWindows for KeyboardId { + #[inline] + fn persistent_identifier(&self) -> Option { + self.0.persistent_identifier() + } + + #[inline] + fn handle(&self) -> *mut c_void { + self.0.handle() as _ + } } -impl DeviceIdExtWindows for DeviceId { +impl DeviceExtWindows for GamepadHandle { #[inline] fn persistent_identifier(&self) -> Option { self.0.persistent_identifier() } + + #[inline] + fn handle(&self) -> *mut c_void { + self.0.handle() as _ + } } /// Additional methods on `Icon` that are specific to Windows. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 2b518e9ed6..552875a0fb 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -4,8 +4,8 @@ mod runner; use parking_lot::Mutex; use std::{ - cell::Cell, - collections::VecDeque, + cell::{Cell, RefCell}, + collections::{HashMap, VecDeque}, marker::PhantomData, mem, panic, ptr, rc::Rc, @@ -33,7 +33,10 @@ use winapi::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, + event::{ + device::{GamepadEvent, HidEvent, KeyboardEvent, MouseEvent}, + Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent, + }, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ @@ -41,10 +44,12 @@ use crate::{ dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, drop_handler::FileDropHandler, event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, + gamepad::Gamepad, monitor::{self, MonitorHandle}, - raw_input, util, + raw_input::{self, get_raw_input_data, get_raw_mouse_button_state, RawInputData}, + util, window_state::{CursorFlags, WindowFlags, WindowState}, - wrap_device_id, WindowId, DEVICE_ID, + GamepadHandle, HidId, KeyboardId, MouseId, WindowId, }, window::{Fullscreen, WindowId as RootWindowId}, }; @@ -83,9 +88,22 @@ lazy_static! { get_function!("user32.dll", GetPointerPenInfo); } +#[derive(Debug)] +pub(crate) enum DeviceId { + Mouse(MouseId), + Keyboard(KeyboardId), + Hid(HidId), + Gamepad(GamepadHandle, Gamepad), +} + +pub(crate) struct SubclassSharedData { + pub runner_shared: EventLoopRunnerShared, + pub active_device_ids: RefCell>, +} + pub(crate) struct SubclassInput { pub window_state: Arc>, - pub event_loop_runner: EventLoopRunnerShared, + pub shared_data: Rc>, pub file_drop_handler: Option, pub subclass_removed: Cell, pub recurse_depth: Cell, @@ -93,18 +111,18 @@ pub(crate) struct SubclassInput { impl SubclassInput { unsafe fn send_event(&self, event: Event<'_, T>) { - self.event_loop_runner.send_event(event); + self.shared_data.runner_shared.send_event(event); } } struct ThreadMsgTargetSubclassInput { - event_loop_runner: EventLoopRunnerShared, + shared_data: Rc>, user_event_receiver: Receiver, } impl ThreadMsgTargetSubclassInput { unsafe fn send_event(&self, event: Event<'_, T>) { - self.event_loop_runner.send_event(event); + self.shared_data.runner_shared.send_event(event); } } @@ -116,7 +134,7 @@ pub struct EventLoop { pub struct EventLoopWindowTarget { thread_id: DWORD, thread_msg_target: HWND, - pub(crate) runner_shared: EventLoopRunnerShared, + pub(crate) shared_data: Rc>, } macro_rules! main_thread_check { @@ -161,11 +179,14 @@ impl EventLoop { thread::spawn(move || wait_thread(thread_id, send_thread_msg_target as HWND)); let wait_thread_id = get_wait_thread_id(); - let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); + let shared_data = Rc::new(SubclassSharedData { + runner_shared: Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)), + active_device_ids: RefCell::new(HashMap::default()), + }); let thread_msg_sender = - subclass_event_target_window(thread_msg_target, runner_shared.clone()); - raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target); + subclass_event_target_window(thread_msg_target, shared_data.clone()); + raw_input::register_for_raw_input(thread_msg_target); EventLoop { thread_msg_sender, @@ -173,7 +194,7 @@ impl EventLoop { p: EventLoopWindowTarget { thread_id, thread_msg_target, - runner_shared, + shared_data, }, _marker: PhantomData, }, @@ -201,13 +222,14 @@ impl EventLoop { unsafe { self.window_target .p + .shared_data .runner_shared .set_event_handler(move |event, control_flow| { event_handler(event, event_loop_windows_ref, control_flow) }); } - let runner = &self.window_target.p.runner_shared; + let runner = &self.window_target.p.shared_data.runner_shared; unsafe { let mut msg = mem::zeroed(); @@ -243,6 +265,70 @@ impl EventLoop { event_send: self.thread_msg_sender.clone(), } } + + fn devices( + &self, + f: impl FnMut(&DeviceId) -> Option, + ) -> impl '_ + Iterator { + // Flush WM_INPUT and WM_INPUT_DEVICE_CHANGE events so that the active_device_ids list is + // accurate. This is essential to make this function work if called before calling `run` or + // `run_return`. + unsafe { + let mut msg = mem::zeroed(); + loop { + let result = winuser::PeekMessageW( + &mut msg, + self.window_target.p.thread_msg_target, + winuser::WM_INPUT_DEVICE_CHANGE, + winuser::WM_INPUT, + 1, + ); + if 0 == result { + break; + } + winuser::TranslateMessage(&mut msg); + winuser::DispatchMessageW(&mut msg); + } + } + + self.window_target + .p + .shared_data + .active_device_ids + .borrow() + .values() + .filter_map(f) + .collect::>() + .into_iter() + } + + pub fn mouses(&self) -> impl '_ + Iterator { + self.devices(|d| match d { + DeviceId::Mouse(id) => Some(id.clone().into()), + _ => None, + }) + } + + pub fn keyboards(&self) -> impl '_ + Iterator { + self.devices(|d| match d { + DeviceId::Keyboard(id) => Some(id.clone().into()), + _ => None, + }) + } + + pub fn hids(&self) -> impl '_ + Iterator { + self.devices(|d| match d { + DeviceId::Hid(id) => Some(id.clone().into()), + _ => None, + }) + } + + pub fn gamepads(&self) -> impl '_ + Iterator { + self.devices(|d| match d { + DeviceId::Gamepad(handle, _) => Some(handle.clone().into()), + _ => None, + }) + } } impl EventLoopWindowTarget { @@ -597,13 +683,13 @@ fn create_event_target_window() -> HWND { fn subclass_event_target_window( window: HWND, - event_loop_runner: EventLoopRunnerShared, + shared_data: Rc>, ) -> Sender { unsafe { let (tx, rx) = mpsc::channel(); let subclass_input = ThreadMsgTargetSubclassInput { - event_loop_runner, + shared_data, user_event_receiver: rx, }; let input_ptr = Box::into_raw(Box::new(subclass_input)); @@ -651,7 +737,10 @@ unsafe fn release_mouse(mut window_state: parking_lot::MutexGuard<'_, WindowStat const WINDOW_SUBCLASS_ID: UINT_PTR = 0; const THREAD_EVENT_TARGET_SUBCLASS_ID: UINT_PTR = 1; pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) { - subclass_input.event_loop_runner.register_window(window); + subclass_input + .shared_data + .runner_shared + .register_window(window); let input_ptr = Box::into_raw(Box::new(subclass_input)); let subclass_result = unsafe { commctrl::SetWindowSubclass( @@ -813,7 +902,7 @@ unsafe fn public_window_callback_inner( subclass_input: &SubclassInput, ) -> LRESULT { winuser::RedrawWindow( - subclass_input.event_loop_runner.thread_msg_target(), + subclass_input.shared_data.runner_shared.thread_msg_target(), ptr::null(), ptr::null_mut(), winuser::RDW_INTERNALPAINT, @@ -866,7 +955,10 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: Destroyed, }); - subclass_input.event_loop_runner.remove_window(window); + subclass_input + .shared_data + .runner_shared + .remove_window(window); 0 } @@ -877,7 +969,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_PAINT => { - if subclass_input.event_loop_runner.should_buffer() { + if subclass_input.shared_data.runner_shared.should_buffer() { // this branch can happen in response to `UpdateWindow`, if win32 decides to // redraw the window outside the normal flow of the event loop. winuser::RedrawWindow( @@ -888,11 +980,14 @@ unsafe fn public_window_callback_inner( ); } else { let managing_redraw = - flush_paint_messages(Some(window), &subclass_input.event_loop_runner); + flush_paint_messages(Some(window), &subclass_input.shared_data.runner_shared); subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId(window)))); if managing_redraw { - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + subclass_input + .shared_data + .runner_shared + .redraw_events_cleared(); + process_control_flow(&subclass_input.shared_data.runner_shared); } } @@ -1062,9 +1157,7 @@ unsafe fn public_window_callback_inner( if mouse_was_outside_window { subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: CursorEntered { - device_id: DEVICE_ID, - }, + event: CursorEntered, }); // Calling TrackMouseEvent in order to receive mouse leave events. @@ -1094,7 +1187,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { - device_id: DEVICE_ID, position, modifiers: event::get_key_mods(), }, @@ -1115,9 +1207,7 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: CursorLeft { - device_id: DEVICE_ID, - }, + event: CursorLeft, }); 0 @@ -1135,7 +1225,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, modifiers: event::get_key_mods(), @@ -1157,7 +1246,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { - device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, modifiers: event::get_key_mods(), @@ -1179,7 +1267,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, input: KeyboardInput { state: Pressed, scancode, @@ -1211,7 +1298,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, input: KeyboardInput { state: Released, scancode, @@ -1235,7 +1321,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, button: Left, modifiers: event::get_key_mods(), @@ -1256,7 +1341,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, button: Left, modifiers: event::get_key_mods(), @@ -1277,7 +1361,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, button: Right, modifiers: event::get_key_mods(), @@ -1298,7 +1381,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, button: Right, modifiers: event::get_key_mods(), @@ -1319,7 +1401,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, button: Middle, modifiers: event::get_key_mods(), @@ -1340,7 +1421,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, button: Middle, modifiers: event::get_key_mods(), @@ -1362,7 +1442,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Pressed, button: Other(xbutton), modifiers: event::get_key_mods(), @@ -1384,7 +1463,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { - device_id: DEVICE_ID, state: Released, button: Other(xbutton), modifiers: event::get_key_mods(), @@ -1439,7 +1517,6 @@ unsafe fn public_window_callback_inner( location, force: None, // WM_TOUCH doesn't support pressure information id: input.dwID as u64, - device_id: DEVICE_ID, }), }); } @@ -1578,7 +1655,6 @@ unsafe fn public_window_callback_inner( location, force, id: pointer_info.pointerId as u64, - device_id: DEVICE_ID, }), }); } @@ -1601,7 +1677,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, input: KeyboardInput { scancode, virtual_keycode, @@ -1636,7 +1711,6 @@ unsafe fn public_window_callback_inner( subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, input: KeyboardInput { scancode, virtual_keycode, @@ -1973,7 +2047,8 @@ unsafe fn public_window_callback_inner( }; subclass_input - .event_loop_runner + .shared_data + .runner_shared .catch_unwind(callback) .unwrap_or(-1) } @@ -2015,8 +2090,8 @@ unsafe extern "system" fn thread_event_target_callback( // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw // events, `handling_events` will return false and we won't emit a second // `RedrawEventsCleared` event. - if subclass_input.event_loop_runner.handling_events() { - if subclass_input.event_loop_runner.should_buffer() { + if subclass_input.shared_data.runner_shared.handling_events() { + if subclass_input.shared_data.runner_shared.should_buffer() { // This branch can be triggered when a nested win32 event loop is triggered // inside of the `event_handler` callback. winuser::RedrawWindow( @@ -2030,10 +2105,13 @@ unsafe extern "system" fn thread_event_target_callback( // doesn't call WM_PAINT for the thread event target (i.e. this window). assert!(flush_paint_messages( None, - &subclass_input.event_loop_runner + &subclass_input.shared_data.runner_shared )); - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + subclass_input + .shared_data + .runner_shared + .redraw_events_cleared(); + process_control_flow(&subclass_input.shared_data.runner_shared); } } @@ -2042,116 +2120,233 @@ unsafe extern "system" fn thread_event_target_callback( } winuser::WM_INPUT_DEVICE_CHANGE => { - let event = match wparam as _ { - winuser::GIDC_ARRIVAL => DeviceEvent::Added, - winuser::GIDC_REMOVAL => DeviceEvent::Removed, - _ => unreachable!(), - }; + use super::raw_input::RawDeviceInfo; + + let handle = lparam as HANDLE; + + match wparam as _ { + winuser::GIDC_ARRIVAL => { + if let Some(handle_info) = raw_input::get_raw_input_device_info(handle) { + let (device, event) = match handle_info { + RawDeviceInfo::Mouse(_) => { + let mouse_id = MouseId(handle); + ( + DeviceId::Mouse(mouse_id), + Event::MouseEvent(mouse_id.into(), MouseEvent::Added), + ) + } + RawDeviceInfo::Keyboard(_) => { + let keyboard_id = KeyboardId(handle); + ( + DeviceId::Keyboard(keyboard_id), + Event::KeyboardEvent(keyboard_id.into(), KeyboardEvent::Added), + ) + } + RawDeviceInfo::Hid(_) => match Gamepad::new(handle) { + Some(gamepad) => { + let gamepad_handle = GamepadHandle { + handle, + shared_data: gamepad.shared_data(), + }; + + ( + DeviceId::Gamepad(gamepad_handle.clone(), gamepad), + Event::GamepadEvent( + gamepad_handle.into(), + GamepadEvent::Added, + ), + ) + } + None => { + let hid_id = HidId(handle); + ( + DeviceId::Hid(hid_id.into()), + Event::HidEvent(hid_id.into(), HidEvent::Added), + ) + } + }, + }; - subclass_input.send_event(Event::DeviceEvent { - device_id: wrap_device_id(lparam as _), - event, - }); + subclass_input + .shared_data + .active_device_ids + .borrow_mut() + .insert(handle, device); + subclass_input.send_event(event); + } + } + winuser::GIDC_REMOVAL => { + let removed_device = subclass_input + .shared_data + .active_device_ids + .borrow_mut() + .remove(&handle); + if let Some(device_id) = removed_device { + let event = match device_id { + DeviceId::Mouse(mouse_id) => { + Event::MouseEvent(mouse_id.into(), MouseEvent::Removed) + } + DeviceId::Keyboard(keyboard_id) => { + Event::KeyboardEvent(keyboard_id.into(), KeyboardEvent::Removed) + } + DeviceId::Hid(hid_id) => { + Event::HidEvent(hid_id.into(), HidEvent::Removed) + } + DeviceId::Gamepad(gamepad_handle, _) => { + Event::GamepadEvent(gamepad_handle.into(), GamepadEvent::Removed) + } + }; + subclass_input.send_event(event); + } + } + _ => unreachable!(), + } 0 } winuser::WM_INPUT => { use crate::event::{ - DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, ElementState::{Pressed, Released}, - MouseScrollDelta::LineDelta, + MouseButton, }; - if let Some(data) = raw_input::get_raw_input_data(lparam as _) { - let device_id = wrap_device_id(data.header.hDevice as _); - - if data.header.dwType == winuser::RIM_TYPEMOUSE { - let mouse = data.data.mouse(); + match get_raw_input_data(lparam as _) { + Some(RawInputData::Mouse { + device_handle, + raw_mouse, + }) => { + let mouse_handle = MouseId(device_handle).into(); - if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { - let x = mouse.lLastX as f64; - let y = mouse.lLastY as f64; + if util::has_flag(raw_mouse.usFlags, winuser::MOUSE_MOVE_ABSOLUTE) { + let x = raw_mouse.lLastX as f64; + let y = raw_mouse.lLastY as f64; - if x != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x }, - }); - } - - if y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y }, - }); + if x != 0.0 || y != 0.0 { + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::MovedAbsolute(PhysicalPosition { x, y }), + )); } + } else if util::has_flag(raw_mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { + let x = raw_mouse.lLastX as f64; + let y = raw_mouse.lLastY as f64; if x != 0.0 || y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseMotion { delta: (x, y) }, - }); + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::MovedRelative(x, y), + )); } } - if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { - let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { - delta: LineDelta(0.0, delta as f32), - }, - }); + if util::has_flag(raw_mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { + // TODO: HOW IS RAW WHEEL DELTA HANDLED ON OTHER PLATFORMS? + let delta = raw_mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::Wheel(0.0, delta as f64), + )); + } + // Check if there's horizontal wheel movement. + if util::has_flag(raw_mouse.usButtonFlags, 0x0800) { + // TODO: HOW IS RAW WHEEL DELTA HANDLED ON OTHER PLATFORMS? + let delta = raw_mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::Wheel(delta as f64, 0.0), + )); } - let button_state = raw_input::get_raw_mouse_button_state(mouse.usButtonFlags); - // Left, middle, and right, respectively. - for (index, state) in button_state.iter().enumerate() { - if let Some(state) = *state { - // This gives us consistency with X11, since there doesn't - // seem to be anything else reasonable to do for a mouse - // button ID. - let button = (index + 1) as _; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Button { button, state }, - }); - } + let button_state = get_raw_mouse_button_state(raw_mouse.usButtonFlags); + for (index, state) in button_state + .iter() + .cloned() + .enumerate() + .filter_map(|(i, state)| state.map(|s| (i, s))) + { + subclass_input.send_event(Event::MouseEvent( + mouse_handle, + MouseEvent::Button { + state, + button: match index { + 0 => MouseButton::Left, + 1 => MouseButton::Middle, + 2 => MouseButton::Right, + _ => MouseButton::Other(index as u16 - 2), + }, + }, + )); } - } else if data.header.dwType == winuser::RIM_TYPEKEYBOARD { - let keyboard = data.data.keyboard(); + } + Some(RawInputData::Keyboard { + device_handle, + raw_keyboard, + }) => { + let keyboard_id = KeyboardId(device_handle).into(); - let pressed = keyboard.Message == winuser::WM_KEYDOWN - || keyboard.Message == winuser::WM_SYSKEYDOWN; - let released = keyboard.Message == winuser::WM_KEYUP - || keyboard.Message == winuser::WM_SYSKEYUP; + let pressed = raw_keyboard.Message == winuser::WM_KEYDOWN + || raw_keyboard.Message == winuser::WM_SYSKEYDOWN; + let released = raw_keyboard.Message == winuser::WM_KEYUP + || raw_keyboard.Message == winuser::WM_SYSKEYUP; if pressed || released { let state = if pressed { Pressed } else { Released }; - let scancode = keyboard.MakeCode as _; - let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) - | util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _); - + let scancode = raw_keyboard.MakeCode as _; + let extended = util::has_flag(raw_keyboard.Flags, winuser::RI_KEY_E0 as _) + | util::has_flag(raw_keyboard.Flags, winuser::RI_KEY_E1 as _); if let Some((vkey, scancode)) = - handle_extended_keys(keyboard.VKey as _, scancode, extended) + handle_extended_keys(raw_keyboard.VKey as _, scancode, extended) { let virtual_keycode = vkey_to_winit_vkey(vkey); #[allow(deprecated)] - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Key(KeyboardInput { + subclass_input.send_event(Event::KeyboardEvent( + keyboard_id, + KeyboardEvent::Input(KeyboardInput { scancode, state, virtual_keycode, modifiers: event::get_key_mods(), }), - }); + )); + } + } + } + Some(RawInputData::Hid { + device_handle, + mut raw_hid, + }) => { + let mut gamepad_handle_opt: Option = None; + let mut gamepad_events = vec![]; + + { + let mut devices = subclass_input.shared_data.active_device_ids.borrow_mut(); + let device_id = devices.get_mut(&device_handle); + if let Some(DeviceId::Gamepad(gamepad_handle, ref mut gamepad)) = device_id + { + gamepad.update_state(&mut raw_hid.raw_input); + gamepad_events = gamepad.get_gamepad_events(); + gamepad_handle_opt = Some(gamepad_handle.clone().into()); + } + } + + if let Some(gamepad_handle) = gamepad_handle_opt { + for gamepad_event in gamepad_events { + subclass_input.send_event(Event::GamepadEvent( + gamepad_handle.clone(), + gamepad_event, + )); } + } else { + subclass_input.send_event(Event::HidEvent( + HidId(device_handle).into(), + HidEvent::Data(raw_hid.raw_input), + )); } } + None => (), } commctrl::DefSubclassProc(window, msg, wparam, lparam) @@ -2170,7 +2365,7 @@ unsafe extern "system" fn thread_event_target_callback( } _ if msg == *PROCESS_NEW_EVENTS_MSG_ID => { winuser::PostThreadMessageW( - subclass_input.event_loop_runner.wait_thread_id(), + subclass_input.shared_data.runner_shared.wait_thread_id(), *CANCEL_WAIT_UNTIL_MSG_ID, 0, 0, @@ -2179,7 +2374,7 @@ unsafe extern "system" fn thread_event_target_callback( // if the control_flow is WaitUntil, make sure the given moment has actually passed // before emitting NewEvents if let ControlFlow::WaitUntil(wait_until) = - subclass_input.event_loop_runner.control_flow() + subclass_input.shared_data.runner_shared.control_flow() { let mut msg = mem::zeroed(); while Instant::now() < wait_until { @@ -2206,14 +2401,15 @@ unsafe extern "system" fn thread_event_target_callback( } } } - subclass_input.event_loop_runner.poll(); + subclass_input.shared_data.runner_shared.poll(); 0 } _ => commctrl::DefSubclassProc(window, msg, wparam, lparam), }; let result = subclass_input - .event_loop_runner + .shared_data + .runner_shared .catch_unwind(callback) .unwrap_or(-1); if subclass_removed { diff --git a/src/platform_impl/windows/gamepad.rs b/src/platform_impl/windows/gamepad.rs new file mode 100644 index 0000000000..f4d3fb501d --- /dev/null +++ b/src/platform_impl/windows/gamepad.rs @@ -0,0 +1,87 @@ +use std::sync::Weak; + +use winapi::um::winnt::HANDLE; + +use crate::{ + event::device::{BatteryLevel, GamepadEvent, RumbleError}, + platform_impl::platform::raw_input::{get_raw_input_device_name, RawGamepad}, + platform_impl::platform::xinput::{self, XInputGamepad, XInputGamepadShared}, +}; + +#[derive(Debug)] +pub enum GamepadType { + Raw(RawGamepad), + XInput(XInputGamepad), +} + +#[derive(Clone)] +pub enum GamepadShared { + Raw(()), + XInput(Weak), + Dummy, +} + +#[derive(Debug)] +pub struct Gamepad { + handle: HANDLE, + backend: GamepadType, +} + +impl Gamepad { + pub fn new(handle: HANDLE) -> Option { + // TODO: Verify that this is an HID device + let name = get_raw_input_device_name(handle)?; + xinput::id_from_name(&name) + .and_then(XInputGamepad::new) + .map(GamepadType::XInput) + .or_else(|| RawGamepad::new(handle).map(GamepadType::Raw)) + .map(|backend| Gamepad { handle, backend }) + } + + pub unsafe fn update_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + match self.backend { + GamepadType::Raw(ref mut gamepad) => gamepad.update_state(raw_input_report), + GamepadType::XInput(ref mut gamepad) => gamepad.update_state(), + } + } + + pub fn get_gamepad_events(&self) -> Vec { + match self.backend { + GamepadType::Raw(ref gamepad) => gamepad.get_gamepad_events(), + GamepadType::XInput(ref gamepad) => gamepad.get_gamepad_events(), + } + } + + pub fn shared_data(&self) -> GamepadShared { + match self.backend { + GamepadType::Raw(_) => GamepadShared::Raw(()), + GamepadType::XInput(ref gamepad) => GamepadShared::XInput(gamepad.shared_data()), + } + } +} + +impl GamepadShared { + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> { + match self { + GamepadShared::Raw(_) | GamepadShared::Dummy => Ok(()), + GamepadShared::XInput(ref data) => data + .upgrade() + .map(|r| r.rumble(left_speed, right_speed)) + .unwrap_or(Err(RumbleError::DeviceNotConnected)), + } + } + + pub fn port(&self) -> Option { + match self { + GamepadShared::Raw(_) | GamepadShared::Dummy => None, + GamepadShared::XInput(ref data) => data.upgrade().map(|r| r.port()), + } + } + + pub fn battery_level(&self) -> Option { + match self { + GamepadShared::Raw(_) | GamepadShared::Dummy => None, + GamepadShared::XInput(ref data) => data.upgrade().and_then(|r| r.battery_level()), + } + } +} diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 5b3f4e1174..35e19cedd2 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -1,9 +1,17 @@ #![cfg(target_os = "windows")] -use winapi::{self, shared::windef::HWND}; +use std::{ + cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}, + fmt, + hash::{Hash, Hasher}, + ptr, +}; + +use winapi::{self, shared::windef::HWND, um::winnt::HANDLE}; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, + gamepad::GamepadShared, icon::WinIcon, monitor::{MonitorHandle, VideoMode}, window::Window, @@ -11,7 +19,7 @@ pub use self::{ pub use self::icon::WinIcon as PlatformIcon; -use crate::event::DeviceId as RootDeviceId; +use crate::event::device; use crate::icon::Icon; use crate::window::Theme; @@ -45,44 +53,152 @@ pub struct Cursor(pub *const winapi::ctypes::wchar_t); unsafe impl Send for Cursor {} unsafe impl Sync for Cursor {} +pub type OsError = std::io::Error; + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(u32); +pub struct WindowId(HWND); +unsafe impl Send for WindowId {} +unsafe impl Sync for WindowId {} -impl DeviceId { +impl WindowId { pub unsafe fn dummy() -> Self { - DeviceId(0) + use std::ptr::null_mut; + + WindowId(null_mut()) } } -impl DeviceId { - pub fn persistent_identifier(&self) -> Option { - if self.0 != 0 { - raw_input::get_raw_input_device_name(self.0 as _) - } else { - None +macro_rules! device_id { + ($name:ident, $enumerate:ident) => { + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub(crate) struct $name(HANDLE); + + unsafe impl Send for $name {} + unsafe impl Sync for $name {} + + impl $name { + pub unsafe fn dummy() -> Self { + Self(ptr::null_mut()) + } + + pub fn persistent_identifier(&self) -> Option { + raw_input::get_raw_input_device_name(self.0) + } + + pub fn is_connected(&self) -> bool { + raw_input::get_raw_input_device_info(self.0).is_some() + } + + #[inline(always)] + pub fn handle(&self) -> HANDLE { + self.0 + } + + pub fn enumerate<'a, T>( + event_loop: &'a EventLoop, + ) -> impl 'a + Iterator { + event_loop.$enumerate() + } } - } + + impl From<$name> for device::$name { + fn from(platform_id: $name) -> Self { + Self(platform_id) + } + } + }; } -// Constant device ID, to be removed when this backend is updated to report real device IDs. -const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0)); +device_id!(MouseId, mouses); +device_id!(KeyboardId, keyboards); +device_id!(HidId, hids); -fn wrap_device_id(id: u32) -> RootDeviceId { - RootDeviceId(DeviceId(id)) +#[derive(Clone)] +pub(crate) struct GamepadHandle { + handle: HANDLE, + shared_data: GamepadShared, } -pub type OsError = std::io::Error; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(HWND); -unsafe impl Send for WindowId {} -unsafe impl Sync for WindowId {} +unsafe impl Send for GamepadHandle where GamepadShared: Send {} +unsafe impl Sync for GamepadHandle where GamepadShared: Sync {} -impl WindowId { +impl GamepadHandle { pub unsafe fn dummy() -> Self { - use std::ptr::null_mut; + Self { + handle: ptr::null_mut(), + shared_data: GamepadShared::Dummy, + } + } - WindowId(null_mut()) + pub fn persistent_identifier(&self) -> Option { + raw_input::get_raw_input_device_name(self.handle) + } + + pub fn is_connected(&self) -> bool { + raw_input::get_raw_input_device_info(self.handle).is_some() + } + + #[inline(always)] + pub fn handle(&self) -> HANDLE { + self.handle + } + + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), device::RumbleError> { + self.shared_data.rumble(left_speed, right_speed) + } + + pub fn port(&self) -> Option { + self.shared_data.port() + } + + pub fn battery_level(&self) -> Option { + self.shared_data.battery_level() + } + + pub fn enumerate<'a, T>( + event_loop: &'a EventLoop, + ) -> impl 'a + Iterator { + event_loop.gamepads() + } +} + +impl From for device::GamepadHandle { + fn from(platform_id: GamepadHandle) -> Self { + Self(platform_id) + } +} + +impl fmt::Debug for GamepadHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.debug_tuple("GamepadHandle").field(&self.handle).finish() + } +} + +impl Eq for GamepadHandle {} +impl PartialEq for GamepadHandle { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.handle == other.handle + } +} + +impl Ord for GamepadHandle { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.handle.cmp(&other.handle) + } +} +impl PartialOrd for GamepadHandle { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + self.handle.partial_cmp(&other.handle) + } +} + +impl Hash for GamepadHandle { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.handle.hash(state); } } @@ -93,8 +209,10 @@ mod dpi; mod drop_handler; mod event; mod event_loop; +mod gamepad; mod icon; mod monitor; mod raw_input; mod window; mod window_state; +mod xinput; diff --git a/src/platform_impl/windows/raw_input.rs b/src/platform_impl/windows/raw_input.rs index 73b136a82f..a602392735 100644 --- a/src/platform_impl/windows/raw_input.rs +++ b/src/platform_impl/windows/raw_input.rs @@ -1,27 +1,43 @@ use std::{ + cmp::max, + fmt, mem::{self, size_of}, - ptr, + ptr, slice, }; use winapi::{ ctypes::wchar_t, shared::{ - hidusage::{HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC}, - minwindef::{TRUE, UINT, USHORT}, + hidpi::{ + HidP_GetButtonCaps, HidP_GetCaps, HidP_GetScaledUsageValue, HidP_GetUsageValue, + HidP_GetUsagesEx, HidP_GetValueCaps, HidP_Input, HIDP_STATUS_SUCCESS, HIDP_VALUE_CAPS, + PHIDP_PREPARSED_DATA, + }, + hidusage::{ + HID_USAGE_GENERIC_GAMEPAD, HID_USAGE_GENERIC_JOYSTICK, HID_USAGE_GENERIC_KEYBOARD, + HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC, + }, + minwindef::{INT, TRUE, UINT, USHORT}, windef::HWND, }, um::{ - winnt::HANDLE, + winnt::{HANDLE, PCHAR}, winuser::{ self, HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER, - RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RID_DEVICE_INFO, - RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, RID_INPUT, - RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, + RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDI_DEVICEINFO, RIDI_DEVICENAME, RIDI_PREPARSEDDATA, + RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE, + RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, }, }, }; -use crate::{event::ElementState, platform_impl::platform::util}; +use crate::{ + event::{ + device::{GamepadAxis, GamepadEvent}, + ElementState, + }, + platform_impl::platform::util, +}; #[allow(dead_code)] pub fn get_raw_input_device_list() -> Option> { @@ -51,8 +67,6 @@ pub fn get_raw_input_device_list() -> Option> { Some(buffer) } - -#[allow(dead_code)] pub enum RawDeviceInfo { Mouse(RID_DEVICE_INFO_MOUSE), Keyboard(RID_DEVICE_INFO_KEYBOARD), @@ -72,28 +86,27 @@ impl From for RawDeviceInfo { } } -#[allow(dead_code)] pub fn get_raw_input_device_info(handle: HANDLE) -> Option { let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() }; let info_size = size_of::() as UINT; info.cbSize = info_size; - let mut minimum_size = 0; + let mut data_size = info_size; let status = unsafe { winuser::GetRawInputDeviceInfoW( handle, RIDI_DEVICEINFO, &mut info as *mut _ as _, - &mut minimum_size, + &mut data_size, ) - }; + } as INT; - if status == UINT::max_value() || status == 0 { + if status <= 0 { return None; } - debug_assert_eq!(info_size, status); + debug_assert_eq!(info_size, status as _); Some(info.into()) } @@ -130,6 +143,43 @@ pub fn get_raw_input_device_name(handle: HANDLE) -> Option { Some(util::wchar_to_string(&name)) } +pub fn get_raw_input_pre_parse_info(handle: HANDLE) -> Option> { + let mut minimum_size = 0; + let status = unsafe { + winuser::GetRawInputDeviceInfoW( + handle, + RIDI_PREPARSEDDATA, + ptr::null_mut(), + &mut minimum_size, + ) + }; + + if status != 0 { + return None; + } + + let mut buf: Vec = Vec::with_capacity(minimum_size as _); + + let status = unsafe { + winuser::GetRawInputDeviceInfoW( + handle, + RIDI_PREPARSEDDATA, + buf.as_ptr() as _, + &mut minimum_size, + ) + }; + + if status == UINT::max_value() || status == 0 { + return None; + } + + debug_assert_eq!(minimum_size, status); + + unsafe { buf.set_len(minimum_size as _) }; + + Some(buf) +} + pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { let device_size = size_of::() as UINT; @@ -140,12 +190,12 @@ pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool { success == TRUE } -pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool { - // RIDEV_DEVNOTIFY: receive hotplug events - // RIDEV_INPUTSINK: receive events even if we're not in the foreground +pub fn register_for_raw_input(window_handle: HWND) -> bool { + // `RIDEV_DEVNOTIFY`: receive hotplug events + // `RIDEV_INPUTSINK`: receive events even if we're not in the foreground let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; - let devices: [RAWINPUTDEVICE; 2] = [ + let devices: [RAWINPUTDEVICE; 5] = [ RAWINPUTDEVICE { usUsagePage: HID_USAGE_PAGE_GENERIC, usUsage: HID_USAGE_GENERIC_MOUSE, @@ -158,27 +208,182 @@ pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> boo dwFlags: flags, hwndTarget: window_handle, }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: HID_USAGE_GENERIC_JOYSTICK, + dwFlags: flags, + hwndTarget: window_handle, + }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: HID_USAGE_GENERIC_GAMEPAD, + dwFlags: flags, + hwndTarget: window_handle, + }, + RAWINPUTDEVICE { + usUsagePage: HID_USAGE_PAGE_GENERIC, + usUsage: 0x08, // multi-axis + dwFlags: flags, + hwndTarget: window_handle, + }, ]; register_raw_input_devices(&devices) } -pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { - let mut data: RAWINPUT = unsafe { mem::zeroed() }; - let mut data_size = size_of::() as UINT; +pub enum RawInputData { + Mouse { + device_handle: HANDLE, + raw_mouse: winuser::RAWMOUSE, + }, + Keyboard { + device_handle: HANDLE, + raw_keyboard: winuser::RAWKEYBOARD, + }, + Hid { + device_handle: HANDLE, + raw_hid: RawHidData, + }, +} + +pub struct RawHidData { + pub hid_input_size: u32, + pub hid_input_count: u32, + pub raw_input: Box<[u8]>, +} + +pub fn get_raw_input_data(handle: HRAWINPUT) -> Option { + let mut data_size = 0; let header_size = size_of::() as UINT; - let status = unsafe { + // There are two classes of data this function can output: + // - Raw mouse and keyboard data + // - Raw HID data + // The first class (mouse and keyboard) is always going to write data formatted like the + // `RAWINPUT` struct, with no other data, and can be placed on the stack into `RAWINPUT`. + // The second class (raw HID data) writes the struct, and then a buffer of data appended to + // the end. That data needs to be heap-allocated so we can store all of it. + unsafe { winuser::GetRawInputData( handle, RID_INPUT, - &mut data as *mut _ as _, + ptr::null_mut(), &mut data_size, header_size, ) }; - if status == UINT::max_value() || status == 0 { + let (status, data): (INT, RawInputData); + + if data_size <= size_of::() as UINT { + // Since GetRawInputData is going to write... well, a buffer that's `RAWINPUT` bytes long + // and structured like `RAWINPUT`, we're just going to cut to the chase and write directly into + // a `RAWINPUT` struct. + let mut rawinput_data: RAWINPUT = unsafe { mem::zeroed() }; + + status = unsafe { + winuser::GetRawInputData( + handle, + RID_INPUT, + &mut rawinput_data as *mut RAWINPUT as *mut _, + &mut data_size, + header_size, + ) + } as INT; + + assert_ne!(-1, status); + + let device_handle = rawinput_data.header.hDevice; + + data = match rawinput_data.header.dwType { + winuser::RIM_TYPEMOUSE => { + let raw_mouse = unsafe { rawinput_data.data.mouse().clone() }; + RawInputData::Mouse { + device_handle, + raw_mouse, + } + } + winuser::RIM_TYPEKEYBOARD => { + let raw_keyboard = unsafe { rawinput_data.data.keyboard().clone() }; + RawInputData::Keyboard { + device_handle, + raw_keyboard, + } + } + winuser::RIM_TYPEHID => { + let hid_data = unsafe { rawinput_data.data.hid() }; + let buf_len = hid_data.dwSizeHid as usize * hid_data.dwCount as usize; + let data = unsafe { slice::from_raw_parts(hid_data.bRawData.as_ptr(), buf_len) }; + RawInputData::Hid { + device_handle, + raw_hid: RawHidData { + hid_input_size: hid_data.dwSizeHid, + hid_input_count: hid_data.dwCount, + raw_input: Box::from(data), + }, + } + } + _ => unreachable!(), + }; + } else { + let mut buf = vec![0u8; data_size as usize]; + + status = unsafe { + winuser::GetRawInputData( + handle, + RID_INPUT, + buf.as_mut_ptr() as *mut _, + &mut data_size, + header_size, + ) + } as INT; + + let rawinput_data = buf.as_ptr() as *const RAWINPUT; + + let device_handle = unsafe { (&*rawinput_data).header.hDevice }; + + data = match unsafe { *rawinput_data }.header.dwType { + winuser::RIM_TYPEMOUSE => { + let raw_mouse = unsafe { (&*rawinput_data).data.mouse().clone() }; + RawInputData::Mouse { + device_handle, + raw_mouse, + } + } + winuser::RIM_TYPEKEYBOARD => { + let raw_keyboard = unsafe { (&*rawinput_data).data.keyboard().clone() }; + RawInputData::Keyboard { + device_handle, + raw_keyboard, + } + } + winuser::RIM_TYPEHID => { + let hid_data: winuser::RAWHID = unsafe { (&*rawinput_data).data.hid().clone() }; + + let hid_data_index = { + let hid_data_start = + unsafe { &((&*rawinput_data).data.hid().bRawData) } as *const _; + hid_data_start as usize - buf.as_ptr() as usize + }; + + buf.drain(..hid_data_index); + + RawInputData::Hid { + device_handle, + raw_hid: RawHidData { + hid_input_size: hid_data.dwSizeHid, + hid_input_count: hid_data.dwCount, + raw_input: buf.into_boxed_slice(), + }, + } + } + _ => unreachable!(), + }; + + assert_ne!(-1, status); + } + + if status == 0 { return None; } @@ -200,7 +405,7 @@ fn button_flags_to_element_state( } } -pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option; 3] { +pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option; 5] { [ button_flags_to_element_state( button_flags, @@ -217,5 +422,264 @@ pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option winuser::RI_MOUSE_RIGHT_BUTTON_DOWN, winuser::RI_MOUSE_RIGHT_BUTTON_UP, ), + button_flags_to_element_state( + button_flags, + winuser::RI_MOUSE_BUTTON_4_DOWN, + winuser::RI_MOUSE_BUTTON_4_UP, + ), + button_flags_to_element_state( + button_flags, + winuser::RI_MOUSE_BUTTON_5_DOWN, + winuser::RI_MOUSE_BUTTON_5_UP, + ), ] } + +pub struct Axis { + caps: HIDP_VALUE_CAPS, + value: f64, + prev_value: f64, + axis: Option, +} + +impl fmt::Debug for Axis { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[derive(Debug)] + struct Axis { + value: f64, + prev_value: f64, + axis: Option, + } + + let axis_proxy = Axis { + value: self.value, + prev_value: self.prev_value, + axis: self.axis, + }; + + axis_proxy.fmt(f) + } +} + +#[derive(Debug)] +pub struct RawGamepad { + handle: HANDLE, + pre_parsed_data: Vec, + button_count: usize, + pub button_state: Vec, + pub prev_button_state: Vec, + axis_count: usize, + pub axis_state: Vec, +} + +// Reference: https://chromium.googlesource.com/chromium/chromium/+/trunk/content/browser/gamepad/raw_input_data_fetcher_win.cc +impl RawGamepad { + pub fn new(handle: HANDLE) -> Option { + let pre_parsed_data = get_raw_input_pre_parse_info(handle)?; + let data_ptr = pre_parsed_data.as_ptr() as PHIDP_PREPARSED_DATA; + let mut caps = unsafe { mem::zeroed() }; + let status = unsafe { HidP_GetCaps(data_ptr, &mut caps) }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + let mut button_caps_len = caps.NumberInputButtonCaps; + let mut button_caps = Vec::with_capacity(button_caps_len as _); + let status = unsafe { + HidP_GetButtonCaps( + HidP_Input, + button_caps.as_mut_ptr(), + &mut button_caps_len, + data_ptr, + ) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + unsafe { button_caps.set_len(button_caps_len as _) }; + let mut button_count = 0; + for button_cap in button_caps { + let range = unsafe { button_cap.u.Range() }; + button_count = max(button_count, range.UsageMax); + } + let button_state = vec![false; button_count as usize]; + let mut axis_caps_len = caps.NumberInputValueCaps; + let mut axis_caps = Vec::with_capacity(axis_caps_len as _); + let status = unsafe { + HidP_GetValueCaps( + HidP_Input, + axis_caps.as_mut_ptr(), + &mut axis_caps_len, + data_ptr, + ) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + unsafe { axis_caps.set_len(axis_caps_len as _) }; + let mut axis_state = Vec::with_capacity(axis_caps_len as _); + let mut axis_count = 0; + for (axis_index, axis_cap) in axis_caps.drain(0..).enumerate() { + axis_state.push(Axis { + caps: axis_cap, + value: 0.0, + prev_value: 0.0, + axis: None, + }); + axis_count = max(axis_count, axis_index + 1); + } + Some(RawGamepad { + handle, + pre_parsed_data, + button_count: button_count as usize, + button_state: button_state.clone(), + prev_button_state: button_state, + axis_count, + axis_state, + }) + } + + fn pre_parsed_data_ptr(&mut self) -> PHIDP_PREPARSED_DATA { + self.pre_parsed_data.as_mut_ptr() as PHIDP_PREPARSED_DATA + } + + fn update_button_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + let pre_parsed_data_ptr = self.pre_parsed_data_ptr(); + self.prev_button_state = + mem::replace(&mut self.button_state, vec![false; self.button_count]); + let mut usages_len = 0; + // This is the officially documented way to get the required length, but it nonetheless returns + // `HIDP_STATUS_BUFFER_TOO_SMALL`... + unsafe { + HidP_GetUsagesEx( + HidP_Input, + 0, + ptr::null_mut(), + &mut usages_len, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + let mut usages = Vec::with_capacity(usages_len as _); + let status = unsafe { + HidP_GetUsagesEx( + HidP_Input, + 0, + usages.as_mut_ptr(), + &mut usages_len, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + unsafe { usages.set_len(usages_len as _) }; + for usage in usages { + if usage.UsagePage != 0xFF << 8 { + let button_index = (usage.Usage - 1) as usize; + self.button_state[button_index] = true; + } + } + Some(()) + } + + fn update_axis_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + let pre_parsed_data_ptr = self.pre_parsed_data_ptr(); + for axis in &mut self.axis_state { + let (status, axis_value) = if axis.caps.LogicalMin < 0 { + let mut scaled_axis_value = 0; + let status = unsafe { + HidP_GetScaledUsageValue( + HidP_Input, + axis.caps.UsagePage, + 0, + axis.caps.u.Range().UsageMin, + &mut scaled_axis_value, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + (status, scaled_axis_value as f64) + } else { + let mut axis_value = 0; + let status = unsafe { + HidP_GetUsageValue( + HidP_Input, + axis.caps.UsagePage, + 0, + axis.caps.u.Range().UsageMin, + &mut axis_value, + pre_parsed_data_ptr, + raw_input_report.as_mut_ptr() as PCHAR, + raw_input_report.len() as _, + ) + }; + (status, axis_value as f64) + }; + if status != HIDP_STATUS_SUCCESS { + return None; + } + axis.prev_value = axis.value; + axis.value = util::normalize_symmetric( + axis_value, + axis.caps.LogicalMin as f64, + axis.caps.LogicalMax as f64, + ); + } + Some(()) + } + + pub unsafe fn update_state(&mut self, raw_input_report: &mut [u8]) -> Option<()> { + self.update_button_state(raw_input_report)?; + self.update_axis_state(raw_input_report)?; + Some(()) + } + + pub fn get_changed_buttons(&self) -> impl '_ + Iterator { + self.button_state + .iter() + .zip(self.prev_button_state.iter()) + .enumerate() + .filter(|&(_, (button, prev_button))| button != prev_button) + .map(|(index, (button, _))| { + let state = if *button { + ElementState::Pressed + } else { + ElementState::Released + }; + GamepadEvent::Button { + button_id: index as _, + button: None, + state, + } + }) + } + + pub fn get_changed_axes(&self) -> impl '_ + Iterator { + self.axis_state + .iter() + .enumerate() + .filter(|&(_, axis)| axis.value != axis.prev_value) + .map(|(index, axis)| GamepadEvent::Axis { + axis_id: index as _, + axis: axis.axis, + value: axis.value, + stick: false, + }) + } + + pub fn get_gamepad_events(&self) -> Vec { + self.get_changed_axes() + .chain(self.get_changed_buttons()) + .collect() + } + + // pub fn rumble(&mut self, _left_speed: u16, _right_speed: u16) { + // // Even though I can't read German, this is still the most useful resource I found: + // // https://zfx.info/viewtopic.php?t=3574&f=7 + // // I'm not optimistic about it being possible to implement this. + // } +} diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 5faaae3e68..4a761d2fd2 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -1,6 +1,5 @@ use std::{ io, mem, - ops::BitAnd, os::raw::c_void, ptr, slice, sync::atomic::{AtomicBool, Ordering}, @@ -22,12 +21,7 @@ use winapi::{ }, }; -pub fn has_flag(bitset: T, flag: T) -> bool -where - T: Copy + PartialEq + BitAnd, -{ - bitset & flag == flag -} +pub use crate::util::*; pub fn wchar_to_string(wchar: &[wchar_t]) -> String { String::from_utf16_lossy(wchar).to_string() diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 0eb7616094..c65d9f70da 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -89,12 +89,12 @@ impl Window { ); } - let file_drop_runner = event_loop.runner_shared.clone(); + let shared_data = event_loop.shared_data.clone(); let file_drop_handler = FileDropHandler::new( win.window.0, Box::new(move |event| { if let Ok(e) = event.map_nonuser_event() { - file_drop_runner.send_event(e) + shared_data.runner_shared.send_event(e) } }), ); @@ -112,7 +112,7 @@ impl Window { let subclass_input = event_loop::SubclassInput { window_state: win.window_state.clone(), - event_loop_runner: event_loop.runner_shared.clone(), + shared_data: event_loop.shared_data.clone(), file_drop_handler, subclass_removed: Cell::new(false), recurse_depth: Cell::new(0), diff --git a/src/platform_impl/windows/xinput.rs b/src/platform_impl/windows/xinput.rs new file mode 100644 index 0000000000..f0b3c6be9f --- /dev/null +++ b/src/platform_impl/windows/xinput.rs @@ -0,0 +1,330 @@ +use std::sync::{Arc, Weak}; +use std::{io, mem}; + +use rusty_xinput::*; +use winapi::shared::minwindef::{DWORD, WORD}; +use winapi::um::xinput::*; + +use crate::{ + event::{ + device::{BatteryLevel, GamepadAxis, GamepadButton, GamepadEvent, RumbleError, Side}, + ElementState, + }, + platform_impl::platform::util, +}; + +lazy_static! { + static ref XINPUT_HANDLE: Option = XInputHandle::load_default().ok(); +} + +static BUTTONS: &[(WORD, u32, GamepadButton)] = &[ + (XINPUT_GAMEPAD_DPAD_UP, 12, GamepadButton::DPadUp), + (XINPUT_GAMEPAD_DPAD_DOWN, 13, GamepadButton::DPadDown), + (XINPUT_GAMEPAD_DPAD_LEFT, 14, GamepadButton::DPadLeft), + (XINPUT_GAMEPAD_DPAD_RIGHT, 15, GamepadButton::DPadRight), + (XINPUT_GAMEPAD_START, 9, GamepadButton::Start), + (XINPUT_GAMEPAD_BACK, 8, GamepadButton::Select), + (XINPUT_GAMEPAD_LEFT_THUMB, 10, GamepadButton::LeftStick), + (XINPUT_GAMEPAD_RIGHT_THUMB, 11, GamepadButton::RightStick), + (XINPUT_GAMEPAD_LEFT_SHOULDER, 4, GamepadButton::LeftShoulder), + ( + XINPUT_GAMEPAD_RIGHT_SHOULDER, + 5, + GamepadButton::RightShoulder, + ), + (XINPUT_GAMEPAD_A, 0, GamepadButton::South), + (XINPUT_GAMEPAD_B, 1, GamepadButton::East), + (XINPUT_GAMEPAD_X, 2, GamepadButton::West), + (XINPUT_GAMEPAD_Y, 3, GamepadButton::North), +]; + +pub fn id_from_name(name: &str) -> Option { + // A device name looks like \\?\HID#VID_046D&PID_C21D&IG_00#8&6daf3b6&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} + // The IG_00 substring indicates that this is an XInput gamepad, and that the ID is 00 + let pat = "IG_0"; + name.find(pat) + .and_then(|i| name[i + pat.len()..].chars().next()) + .and_then(|c| match c { + '0' => Some(0), + '1' => Some(1), + '2' => Some(2), + '3' => Some(3), + _ => None, + }) +} + +#[derive(Debug)] +pub struct XInputGamepad { + shared: Arc, + prev_state: Option, + state: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct XInputGamepadShared { + port: DWORD, +} + +impl XInputGamepad { + pub fn new(port: DWORD) -> Option { + XINPUT_HANDLE.as_ref().map(|_| XInputGamepad { + shared: Arc::new(XInputGamepadShared { port }), + prev_state: None, + state: None, + }) + } + + pub fn update_state(&mut self) -> Option<()> { + let state = XINPUT_HANDLE + .as_ref() + .and_then(|h| h.get_state(self.shared.port).ok()); + if state.is_some() { + self.prev_state = mem::replace(&mut self.state, state); + Some(()) + } else { + None + } + } + + fn check_trigger_digital( + events: &mut Vec, + value: bool, + prev_value: Option, + side: Side, + ) { + const LEFT_TRIGGER_ID: u32 = /*BUTTONS.len() as _*/ 16; + const RIGHT_TRIGGER_ID: u32 = LEFT_TRIGGER_ID + 1; + if Some(value) != prev_value { + let state = if value { + ElementState::Pressed + } else { + ElementState::Released + }; + let (button_id, button) = match side { + Side::Left => (LEFT_TRIGGER_ID, Some(GamepadButton::LeftTrigger)), + Side::Right => (RIGHT_TRIGGER_ID, Some(GamepadButton::RightTrigger)), + }; + events.push(GamepadEvent::Button { + button_id, + button, + state, + }); + } + } + + pub fn get_changed_buttons(&self, events: &mut Vec) { + let (buttons, left_trigger, right_trigger) = match self.state.as_ref() { + Some(state) => ( + state.raw.Gamepad.wButtons, + state.left_trigger_bool(), + state.right_trigger_bool(), + ), + None => return, + }; + let (prev_buttons, prev_left, prev_right) = self + .prev_state + .as_ref() + .map(|state| { + ( + state.raw.Gamepad.wButtons, + Some(state.left_trigger_bool()), + Some(state.right_trigger_bool()), + ) + }) + .unwrap_or_else(|| (0, None, None)); + /* + A = buttons + B = prev_buttons + C = changed + P = pressed + R = released + A B C C A P C B R + (0 0) 0 (0 0) 0 (0 0) 0 + (0 1) 1 (1 1) 1 (1 0) 0 + (1 0) 1 (1 0) 0 (1 1) 1 + (1 1) 0 (0 1) 0 (0 1) 0 + */ + let changed = buttons ^ prev_buttons; + let pressed = changed & buttons; + let released = changed & prev_buttons; + for &(flag, button_id, button) in BUTTONS { + let button = Some(button); + if util::has_flag(pressed, flag) { + events.push(GamepadEvent::Button { + button_id, + button, + state: ElementState::Pressed, + }); + } else if util::has_flag(released, flag) { + events.push(GamepadEvent::Button { + button_id, + button, + state: ElementState::Released, + }); + } + } + Self::check_trigger_digital(events, left_trigger, prev_left, Side::Left); + Self::check_trigger_digital(events, right_trigger, prev_right, Side::Right); + } + + fn check_trigger( + events: &mut Vec, + value: u8, + prev_value: Option, + side: Side, + ) { + const LEFT_TRIGGER_ID: u32 = 4; + const RIGHT_TRIGGER_ID: u32 = LEFT_TRIGGER_ID + 1; + if Some(value) != prev_value { + let (axis_id, axis) = match side { + Side::Left => (LEFT_TRIGGER_ID, Some(GamepadAxis::LeftTrigger)), + Side::Right => (RIGHT_TRIGGER_ID, Some(GamepadAxis::RightTrigger)), + }; + events.push(GamepadEvent::Axis { + axis_id, + axis, + value: value as f64 / u8::max_value() as f64, + stick: false, + }); + } + } + + fn check_stick( + events: &mut Vec, + value: (i16, i16), + prev_value: Option<(i16, i16)>, + stick: Side, + ) { + let (id, axis) = match stick { + Side::Left => ((0, 1), (GamepadAxis::LeftStickX, GamepadAxis::LeftStickY)), + Side::Right => ((2, 3), (GamepadAxis::RightStickX, GamepadAxis::RightStickY)), + }; + let prev_x = prev_value.map(|prev| prev.0); + let prev_y = prev_value.map(|prev| prev.1); + + let value_f64 = |value_int: i16| match value_int.signum() { + 0 => 0.0, + 1 => value_int as f64 / i16::max_value() as f64, + -1 => value_int as f64 / (i16::min_value() as f64).abs(), + _ => unreachable!(), + }; + + let value_f64 = (value_f64(value.0), value_f64(value.1)); + if prev_x != Some(value.0) { + events.push(GamepadEvent::Axis { + axis_id: id.0, + axis: Some(axis.0), + value: value_f64.0, + stick: true, + }); + } + if prev_y != Some(value.1) { + events.push(GamepadEvent::Axis { + axis_id: id.1, + axis: Some(axis.1), + value: value_f64.1, + stick: true, + }); + } + if prev_x != Some(value.0) || prev_y != Some(value.1) { + events.push(GamepadEvent::Stick { + x_id: id.0, + y_id: id.1, + x_value: value_f64.0, + y_value: value_f64.1, + side: stick, + }) + } + } + + pub fn get_changed_axes(&self, events: &mut Vec) { + let state = match self.state { + Some(ref state) => state, + None => return, + }; + let left_stick = state.left_stick_raw(); + let right_stick = state.right_stick_raw(); + let left_trigger = state.left_trigger(); + let right_trigger = state.right_trigger(); + + let prev_state = self.prev_state.as_ref(); + let prev_left_stick = prev_state.map(|state| state.left_stick_raw()); + let prev_right_stick = prev_state.map(|state| state.right_stick_raw()); + let prev_left_trigger = prev_state.map(|state| state.left_trigger()); + let prev_right_trigger = prev_state.map(|state| state.right_trigger()); + + Self::check_stick(events, left_stick, prev_left_stick, Side::Left); + Self::check_stick(events, right_stick, prev_right_stick, Side::Right); + Self::check_trigger(events, left_trigger, prev_left_trigger, Side::Left); + Self::check_trigger(events, right_trigger, prev_right_trigger, Side::Right); + } + + pub fn get_gamepad_events(&self) -> Vec { + let mut events = Vec::new(); + self.get_changed_axes(&mut events); + self.get_changed_buttons(&mut events); + events + } + + pub fn shared_data(&self) -> Weak { + Arc::downgrade(&self.shared) + } +} + +impl Drop for XInputGamepad { + fn drop(&mut self) { + // For some reason, if you don't attempt to retrieve the xinput gamepad state at least once + // after the gamepad was disconnected, all future attempts to read from a given port (even + // if a controller was plugged back into said port) will fail! I don't know why that happens, + // but this fixes it, so 🤷. + XINPUT_HANDLE + .as_ref() + .and_then(|h| h.get_state(self.shared.port).ok()); + } +} + +impl XInputGamepadShared { + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), RumbleError> { + let left_speed = (left_speed.max(0.0).min(1.0) * u16::max_value() as f64) as u16; + let right_speed = (right_speed.max(0.0).min(1.0) * u16::max_value() as f64) as u16; + + let result = XINPUT_HANDLE + .as_ref() + .unwrap() + .set_state(self.port, left_speed, right_speed); + result.map_err(|e| match e { + XInputUsageError::XInputNotLoaded | XInputUsageError::InvalidControllerID => panic!( + "unexpected xinput error {:?}; this is a bug and should be reported", + e + ), + XInputUsageError::DeviceNotConnected => RumbleError::DeviceNotConnected, + XInputUsageError::UnknownError(code) => { + RumbleError::OsError(io::Error::from_raw_os_error(code as i32)) + } + }) + } + + pub fn port(&self) -> u8 { + self.port as _ + } + + pub fn battery_level(&self) -> Option { + use rusty_xinput::BatteryLevel as XBatteryLevel; + + let battery_info = XINPUT_HANDLE + .as_ref() + .unwrap() + .get_gamepad_battery_information(self.port) + .ok()?; + match battery_info.battery_type { + BatteryType::ALKALINE | BatteryType::NIMH => match battery_info.battery_level { + XBatteryLevel::EMPTY => Some(BatteryLevel::Empty), + XBatteryLevel::LOW => Some(BatteryLevel::Low), + XBatteryLevel::MEDIUM => Some(BatteryLevel::Medium), + XBatteryLevel::FULL => Some(BatteryLevel::Full), + _ => None, + }, + _ => None, + } + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000000..18e8c1941b --- /dev/null +++ b/src/util.rs @@ -0,0 +1,29 @@ +use std::ops::BitAnd; + +pub fn has_flag(bitset: T, flag: T) -> bool +where + T: Copy + PartialEq + BitAnd, +{ + bitset & flag == flag +} + +pub fn clamp(value: f64, min: f64, max: f64) -> f64 { + if value > max { + max + } else if value < min { + min + } else { + value + } +} + +pub fn normalize_asymmetric(value: f64, min: f64, max: f64) -> f64 { + let range = max - min; + let translated = value - min; + let scaled = translated / range; + clamp(scaled, 0.0, 1.0) +} + +pub fn normalize_symmetric(value: f64, min: f64, max: f64) -> f64 { + (2.0 * normalize_asymmetric(value, min, max)) - 1.0 +} diff --git a/tests/send_objects.rs b/tests/send_objects.rs index 252b271a4b..7221009813 100644 --- a/tests/send_objects.rs +++ b/tests/send_objects.rs @@ -22,6 +22,8 @@ fn window_send() { fn ids_send() { // ensures that the various `..Id` types implement `Send` needs_send::(); - needs_send::(); + needs_send::(); + needs_send::(); + needs_send::(); needs_send::(); } From 05125029c69e002c4e1a59629d5c4e2fd2ae4bf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20R=C3=83=C2=B8yset?= Date: Tue, 19 Jan 2021 17:41:02 +0100 Subject: [PATCH 232/239] On Windows, fix deadlock caused by mouse capture (#1830) The issue was caused by calling SetCapture on a window which already had the capture. The WM_CAPTURECHANGED handler assumed that it would only run if the capture was lost, but that wasn't the case. This made the handler to try to lock the window state mutex while it was already locked. The issue was introduced in #1797 (932cbe4). --- src/platform_impl/windows/event_loop.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 2b518e9ed6..022ce236aa 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1394,8 +1394,13 @@ unsafe fn public_window_callback_inner( } winuser::WM_CAPTURECHANGED => { - // window lost mouse capture - subclass_input.window_state.lock().mouse.capture_count = 0; + // lparam here is a handle to the window which is gaining mouse capture. + // If it is the same as our window, then we're essentially retaining the capture. This + // can happen if `SetCapture` is called on our window when it already has the mouse + // capture. + if lparam != window as isize { + subclass_input.window_state.lock().mouse.capture_count = 0; + } 0 } From 3f1e09ec0e5ecb3c80eff9e1e07e24d549502275 Mon Sep 17 00:00:00 2001 From: Simas Toleikis Date: Wed, 27 Jan 2021 20:01:17 +0200 Subject: [PATCH 233/239] Add Window::is_maximized method (#1804) --- CHANGELOG.md | 1 + examples/fullscreen.rs | 5 ++--- examples/window_debug.rs | 5 ++--- src/platform_impl/android/mod.rs | 4 ++++ src/platform_impl/ios/window.rs | 5 +++++ src/platform_impl/linux/mod.rs | 6 ++++++ src/platform_impl/macos/window.rs | 5 +++++ src/platform_impl/web/window.rs | 6 ++++++ src/platform_impl/windows/window.rs | 6 ++++++ src/platform_impl/windows/window_state.rs | 2 +- src/window.rs | 11 +++++++++++ 11 files changed, 49 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 143be66dfd..e59e278237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- Added `is_maximized` method to `Window`. - On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor. - On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600). - On Windows, fix bug causing mouse capture to not be released. diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 4ed157f042..83fbde30db 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -23,7 +23,6 @@ fn main() { _ => panic!("Please enter a valid number"), }); - let mut is_maximized = false; let mut decorations = true; let window = WindowBuilder::new() @@ -59,8 +58,8 @@ fn main() { println!("window.fullscreen {:?}", window.fullscreen()); } (VirtualKeyCode::M, ElementState::Pressed) => { - is_maximized = !is_maximized; - window.set_maximized(is_maximized); + let is_maximized = window.is_maximized(); + window.set_maximized(!is_maximized); } (VirtualKeyCode::D, ElementState::Pressed) => { decorations = !decorations; diff --git a/examples/window_debug.rs b/examples/window_debug.rs index dd4007f324..577ad5cd73 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -28,7 +28,6 @@ fn main() { eprintln!(" (X) Toggle maximized"); let mut minimized = false; - let mut maximized = false; let mut visible = true; event_loop.run(move |event, _, control_flow| { @@ -109,8 +108,8 @@ fn main() { window.set_visible(visible); } VirtualKeyCode::X => { - maximized = !maximized; - window.set_maximized(maximized); + let is_maximized = window.is_maximized(); + window.set_maximized(!is_maximized); } _ => (), }, diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index add163a05f..d98236f552 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -496,6 +496,10 @@ impl Window { pub fn set_maximized(&self, _maximized: bool) {} + pub fn is_maximized(&self) -> bool { + false + } + pub fn set_fullscreen(&self, _monitor: Option) { warn!("Cannot set fullscreen on Android"); } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 90c1fc2da7..7a3665a2b9 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -190,6 +190,11 @@ impl Inner { warn!("`Window::set_maximized` is ignored on iOS") } + pub fn is_maximized(&self) -> bool { + warn!("`Window::is_maximized` is ignored on iOS"); + false + } + pub fn set_fullscreen(&self, monitor: Option) { unsafe { let uiscreen = match monitor { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 5dea52c078..e845b83be3 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -373,6 +373,12 @@ impl Window { x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized)) } + #[inline] + pub fn is_maximized(&self) -> bool { + // TODO: Not implemented + false + } + #[inline] pub fn set_minimized(&self, minimized: bool) { x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized)) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 7e69b53dca..94a2f541cf 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -730,6 +730,11 @@ impl UnownedWindow { shared_state_lock.fullscreen.clone() } + #[inline] + pub fn is_maximized(&self) -> bool { + self.is_zoomed() + } + #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { trace!("Locked shared state in `set_fullscreen`"); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 463705bfea..63e3948128 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -232,6 +232,12 @@ impl Window { // Intentionally a no-op, as canvases cannot be 'maximized' } + #[inline] + pub fn is_maximized(&self) -> bool { + // Canvas cannot be 'maximized' + false + } + #[inline] pub fn fullscreen(&self) -> Option { if self.canvas.borrow().is_fullscreen() { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 0eb7616094..575e4da760 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -385,6 +385,12 @@ impl Window { }); } + #[inline] + pub fn is_maximized(&self) -> bool { + let window_state = self.window_state.lock(); + window_state.window_flags.contains(WindowFlags::MAXIMIZED) + } + #[inline] pub fn fullscreen(&self) -> Option { let window_state = self.window_state.lock(); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 70c5330aed..6a6f0255ec 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -34,7 +34,7 @@ pub struct WindowState { pub current_theme: Theme, pub preferred_theme: Option, pub high_surrogate: Option, - window_flags: WindowFlags, + pub window_flags: WindowFlags, } #[derive(Clone)] diff --git a/src/window.rs b/src/window.rs index 9ed2de06a9..052f5746e0 100644 --- a/src/window.rs +++ b/src/window.rs @@ -597,6 +597,17 @@ impl Window { self.window.set_maximized(maximized) } + /// Gets the window's current maximized state. + /// + /// ## Platform-specific + /// + /// - **Wayland / X11:** Not implemented. + /// - **iOS / Android / Web:** Unsupported. + #[inline] + pub fn is_maximized(&self) -> bool { + self.window.is_maximized() + } + /// Sets the window to fullscreen or back. /// /// ## Platform-specific From f79c01b0cf564739d494f3e605da713fd2944a52 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 28 Jan 2021 18:51:49 +0100 Subject: [PATCH 234/239] Fix HINSTANCE returned by raw_window_handle on 64 bit Windows (#1841) --- src/platform_impl/windows/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 575e4da760..4684f32a28 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -278,7 +278,7 @@ impl Window { #[inline] pub fn hinstance(&self) -> HINSTANCE { - unsafe { winuser::GetWindowLongW(self.hwnd(), winuser::GWL_HINSTANCE) as *mut _ } + unsafe { winuser::GetWindowLongPtrW(self.hwnd(), winuser::GWLP_HINSTANCE) as *mut _ } } #[inline] From bd99eb1347e460add973f65cca22f9d5de410c95 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Sat, 30 Jan 2021 19:43:26 +0100 Subject: [PATCH 235/239] Android: Bump ndk/ndk-glue to 0.3 and use constants for event ident (#1847) Following the changes in [1] this bumps ndk and ndk-glue to 0.3 and uses the new constants. The minor version has been bumped to prevent applications from running an older winit (without #1826) with a newer ndk/ndk-glue that does not pass this `ident` through the `data` pointer anymore. [1]: https://github.com/rust-windowing/android-ndk-rs/pull/112 --- CHANGELOG.md | 1 + Cargo.toml | 4 ++-- src/platform_impl/android/mod.rs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e59e278237..028730ea1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - On Windows, fix bug causing mouse capture to not be released. - On Windows, fix fullscreen not preserving minimized/maximized state. - On Android, unimplemented events are marked as unhandled on the native event loop. +- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`. # 0.24.0 (2020-12-09) diff --git a/Cargo.toml b/Cargo.toml index 1c776cd049..64b1bee10e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,9 +37,9 @@ image = "0.23.12" simple_logger = "1.9" [target.'cfg(target_os = "android")'.dependencies] -ndk = "0.2.0" +ndk = "0.3" ndk-sys = "0.2.0" -ndk-glue = "0.2.0" +ndk-glue = "0.3" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] objc = "0.2.7" diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index d98236f552..a6d197b644 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -31,8 +31,8 @@ enum EventSource { fn poll(poll: Poll) -> Option { match poll { Poll::Event { ident, .. } => match ident { - 0 => Some(EventSource::Callback), - 1 => Some(EventSource::InputQueue), + ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => Some(EventSource::Callback), + ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => Some(EventSource::InputQueue), _ => unreachable!(), }, Poll::Timeout => None, From b1d353180b512acfd534c7f3acc6484a2643d671 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 4 Feb 2021 22:26:33 +0100 Subject: [PATCH 236/239] Add ability to assign a menu when creating a window on Windows (#1842) --- CHANGELOG.md | 1 + FEATURES.md | 1 + src/platform/windows.rs | 18 +++++++++++++++++- src/platform_impl/windows/mod.rs | 4 +++- src/platform_impl/windows/window.rs | 6 +++++- 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 028730ea1f..979fba4fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - On Windows, fix bug causing mouse capture to not be released. - On Windows, fix fullscreen not preserving minimized/maximized state. - On Android, unimplemented events are marked as unhandled on the native event loop. +- On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time. - On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`. # 0.24.0 (2020-12-09) diff --git a/FEATURES.md b/FEATURES.md index dcd91ed3ea..4ebb64291a 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -116,6 +116,7 @@ If your PR makes notable changes to Winit's features, please update this section ### Windows * Setting the taskbar icon * Setting the parent window +* Setting a menu bar * `WS_EX_NOREDIRECTIONBITMAP` support * Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 05839d89c1..dd58d758cd 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -5,7 +5,7 @@ use std::path::Path; use libc; use winapi::shared::minwindef::WORD; -use winapi::shared::windef::HWND; +use winapi::shared::windef::{HMENU, HWND}; use crate::{ dpi::PhysicalSize, @@ -112,6 +112,16 @@ pub trait WindowBuilderExtWindows { /// Sets a parent to the window to be created. fn with_parent_window(self, parent: HWND) -> WindowBuilder; + /// Sets a menu on the window to be created. + /// + /// Parent and menu are mutually exclusive; a child window cannot have a menu! + /// + /// The menu must have been manually created beforehand with [`winapi::um::winuser::CreateMenu`] or similar. + /// + /// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how the menus look. + /// If you use this, it is recommended that you combine it with `with_theme(Some(Theme::Light))` to avoid a jarring effect. + fn with_menu(self, menu: HMENU) -> WindowBuilder; + /// This sets `ICON_BIG`. A good ceiling here is 256x256. fn with_taskbar_icon(self, taskbar_icon: Option) -> WindowBuilder; @@ -137,6 +147,12 @@ impl WindowBuilderExtWindows for WindowBuilder { self } + #[inline] + fn with_menu(mut self, menu: HMENU) -> WindowBuilder { + self.platform_specific.menu = Some(menu); + self + } + #[inline] fn with_taskbar_icon(mut self, taskbar_icon: Option) -> WindowBuilder { self.platform_specific.taskbar_icon = taskbar_icon; diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 5b3f4e1174..d748e6f01f 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -1,6 +1,6 @@ #![cfg(target_os = "windows")] -use winapi::{self, shared::windef::HWND}; +use winapi::{self, shared::windef::HMENU, shared::windef::HWND}; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, @@ -18,6 +18,7 @@ use crate::window::Theme; #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub parent: Option, + pub menu: Option, pub taskbar_icon: Option, pub no_redirection_bitmap: bool, pub drag_and_drop: bool, @@ -28,6 +29,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { fn default() -> Self { Self { parent: None, + menu: None, taskbar_icon: None, no_redirection_bitmap: false, drag_and_drop: true, diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 4684f32a28..54389b3980 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -711,6 +711,10 @@ unsafe fn init( window_flags.set(WindowFlags::CHILD, pl_attribs.parent.is_some()); window_flags.set(WindowFlags::ON_TASKBAR, true); + if pl_attribs.parent.is_some() && pl_attribs.menu.is_some() { + warn!("Setting a menu on windows that have a parent is unsupported"); + } + // creating the real window this time, by using the functions in `extra_functions` let real_window = { let (style, ex_style) = window_flags.to_window_styles(); @@ -724,7 +728,7 @@ unsafe fn init( winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT, pl_attribs.parent.unwrap_or(ptr::null_mut()), - ptr::null_mut(), + pl_attribs.menu.unwrap_or(ptr::null_mut()), libloaderapi::GetModuleHandleW(ptr::null()), ptr::null_mut(), ); From b9307a99676354a77e0d93838777207f2626c74a Mon Sep 17 00:00:00 2001 From: Imberflur <2002109+Imberflur@users.noreply.github.com> Date: Fri, 5 Feb 2021 02:58:55 -0500 Subject: [PATCH 237/239] Change linking of CGDisplayCreateUUIDFromDisplayID on macos (#1626) * Link CGDisplayCreateUUIDFromDisplayID through ColorSync instead of CoreGraphics * Conditionally link through ColorSync only if WINIT_LINK_COLORSYNC is set to true * Document new macos env var in README --- README.md | 10 ++++++++++ build.rs | 10 ++++++++++ src/platform_impl/macos/ffi.rs | 13 ++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 build.rs diff --git a/README.md b/README.md index e387ec0d04..8ba6518d4a 100644 --- a/README.md +++ b/README.md @@ -110,3 +110,13 @@ fn main() { ``` And run the application with `cargo apk run --example request_redraw_threaded` + +#### MacOS + +To ensure compatibility with older MacOS systems, winit links to +CGDisplayCreateUUIDFromDisplayID through the CoreGraphics framework. +However, under certain setups this function is only available to be linked +through the newer ColorSync framework. So, winit provides the +`WINIT_LINK_COLORSYNC` environment variable which can be set to `1` or `true` +while compiling to enable linking via ColorSync. + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..a092e63631 --- /dev/null +++ b/build.rs @@ -0,0 +1,10 @@ +fn main() { + // If building for macos and WINIT_LINK_COLORSYNC is set to true + // use CGDisplayCreateUUIDFromDisplayID from ColorSync instead of CoreGraphics + if std::env::var("CARGO_CFG_TARGET_OS").map_or(false, |os| os == "macos") + && std::env::var("WINIT_LINK_COLORSYNC") + .map_or(false, |v| v == "1" || v.eq_ignore_ascii_case("true")) + { + println!("cargo:rustc-cfg=use_colorsync_cgdisplaycreateuuidfromdisplayid"); + } +} diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index aec0fc974e..1bcdafbaa4 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -161,6 +161,18 @@ pub const IO8BitOverlayPixels: &str = "O8"; pub type CGWindowLevel = i32; pub type CGDisplayModeRef = *mut libc::c_void; +#[cfg_attr( + not(use_colorsync_cgdisplaycreateuuidfromdisplayid), + link(name = "CoreGraphics", kind = "framework") +)] +#[cfg_attr( + use_colorsync_cgdisplaycreateuuidfromdisplayid, + link(name = "ColorSync", kind = "framework") +)] +extern "C" { + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; +} + #[link(name = "CoreGraphics", kind = "framework")] extern "C" { pub fn CGRestorePermanentDisplayConfiguration(); @@ -189,7 +201,6 @@ extern "C" { synchronous: Boolean, ) -> CGError; pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError; - pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; pub fn CGShieldingWindowLevel() -> CGWindowLevel; pub fn CGDisplaySetDisplayMode( display: CGDirectDisplayID, From b1be34c6a040c643d3bd15193850ae2484743e39 Mon Sep 17 00:00:00 2001 From: Ssaely <32581789+Ssaely@users.noreply.github.com> Date: Sat, 6 Feb 2021 15:10:36 -0500 Subject: [PATCH 238/239] fix cursor blinking when clicking decorations bar on Windows (#1852) --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 979fba4fbc..577ef59af5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - Added `is_maximized` method to `Window`. +- On Windows, fix bug where clicking the decoration bar would make the cursor blink. - On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor. - On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600). - On Windows, fix bug causing mouse capture to not be released. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 022ce236aa..5b49cbe809 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -845,7 +845,7 @@ unsafe fn public_window_callback_inner( } winuser::WM_NCLBUTTONDOWN => { if wparam == winuser::HTCAPTION as _ { - winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0); + winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, lparam); } commctrl::DefSubclassProc(window, msg, wparam, lparam) } From 5239acfcb2e30020c6d4bfcd71ca92713ebc9044 Mon Sep 17 00:00:00 2001 From: Marnix Kuijs Date: Tue, 9 Feb 2021 16:27:48 +0100 Subject: [PATCH 239/239] Merge web --- Cargo.toml | 17 +- examples/web/gamepad/stdweb/Cargo.toml | 11 ++ examples/web/gamepad/stdweb/src/main.rs | 80 +++++++++ examples/web/gamepad/websys/.gitignore | 7 + examples/web/gamepad/websys/Cargo.toml | 20 +++ .../web/gamepad/websys/files/gamepad.html | 26 +++ examples/web/gamepad/websys/src/lib.rs | 85 +++++++++ examples/web/gamepad/websys/src/utils.rs | 10 ++ src/platform_impl/web/device.rs | 8 - .../web/device/gamepad/constants.rs | 37 ++++ .../web/device/gamepad/manager.rs | 158 +++++++++++++++++ .../web/device/gamepad/mapping.rs | 23 +++ src/platform_impl/web/device/gamepad/mod.rs | 99 +++++++++++ src/platform_impl/web/device/gamepad/utils.rs | 50 ++++++ src/platform_impl/web/device/mod.rs | 161 ++++++++++++++++++ src/platform_impl/web/event_loop/global.rs | 81 +++++++++ src/platform_impl/web/event_loop/mod.rs | 17 ++ src/platform_impl/web/event_loop/runner.rs | 18 ++ .../web/event_loop/window_target.rs | 116 ++++++------- src/platform_impl/web/mod.rs | 3 +- src/platform_impl/web/stdweb/canvas.rs | 51 +++--- src/platform_impl/web/stdweb/gamepad.rs | 82 +++++++++ src/platform_impl/web/stdweb/mod.rs | 10 +- .../web/stdweb/{event.rs => utils.rs} | 46 +++-- src/platform_impl/web/stdweb/windows.rs | 77 +++++++++ src/platform_impl/web/web_sys/canvas.rs | 32 ++-- .../web/web_sys/canvas/mouse_handler.rs | 37 ++-- .../web/web_sys/canvas/pointer_handler.rs | 31 ++-- src/platform_impl/web/web_sys/gamepad.rs | 75 ++++++++ src/platform_impl/web/web_sys/mod.rs | 22 ++- .../web/web_sys/{event.rs => utils.rs} | 60 +++++-- src/platform_impl/web/web_sys/window.rs | 88 ++++++++++ src/platform_impl/web/window.rs | 1 + 33 files changed, 1458 insertions(+), 181 deletions(-) create mode 100644 examples/web/gamepad/stdweb/Cargo.toml create mode 100644 examples/web/gamepad/stdweb/src/main.rs create mode 100644 examples/web/gamepad/websys/.gitignore create mode 100644 examples/web/gamepad/websys/Cargo.toml create mode 100644 examples/web/gamepad/websys/files/gamepad.html create mode 100644 examples/web/gamepad/websys/src/lib.rs create mode 100644 examples/web/gamepad/websys/src/utils.rs delete mode 100644 src/platform_impl/web/device.rs create mode 100644 src/platform_impl/web/device/gamepad/constants.rs create mode 100644 src/platform_impl/web/device/gamepad/manager.rs create mode 100644 src/platform_impl/web/device/gamepad/mapping.rs create mode 100644 src/platform_impl/web/device/gamepad/mod.rs create mode 100644 src/platform_impl/web/device/gamepad/utils.rs create mode 100644 src/platform_impl/web/device/mod.rs create mode 100644 src/platform_impl/web/event_loop/global.rs create mode 100644 src/platform_impl/web/stdweb/gamepad.rs rename src/platform_impl/web/stdweb/{event.rs => utils.rs} (88%) create mode 100644 src/platform_impl/web/stdweb/windows.rs create mode 100644 src/platform_impl/web/web_sys/gamepad.rs rename src/platform_impl/web/web_sys/{event.rs => utils.rs} (84%) create mode 100644 src/platform_impl/web/web_sys/window.rs diff --git a/Cargo.toml b/Cargo.toml index 4e078b6aeb..58a54369d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,9 +117,24 @@ features = [ 'MediaQueryListEvent', 'MouseEvent', 'Node', + 'Navigator', 'PointerEvent', 'Window', - 'WheelEvent' + 'WheelEvent', + 'Gamepad', + 'GamepadAxisMoveEvent', + 'GamepadAxisMoveEventInit', + 'GamepadButton', + 'GamepadButtonEvent', + 'GamepadButtonEventInit', + 'GamepadEvent', + 'GamepadEventInit', + 'GamepadHand', + 'GamepadHapticActuator', + 'GamepadHapticActuatorType', + 'GamepadMappingType', + 'GamepadPose', + 'GamepadServiceTest' ] [target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen] diff --git a/examples/web/gamepad/stdweb/Cargo.toml b/examples/web/gamepad/stdweb/Cargo.toml new file mode 100644 index 0000000000..3d931fdca4 --- /dev/null +++ b/examples/web/gamepad/stdweb/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "stdweb-gamepad" +version = "0.1.0" +authors = ["furiouzz "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +winit = { path = "../../../../", features = [ "stdweb" ] } +stdweb = "0.4.20" diff --git a/examples/web/gamepad/stdweb/src/main.rs b/examples/web/gamepad/stdweb/src/main.rs new file mode 100644 index 0000000000..7b545bc18d --- /dev/null +++ b/examples/web/gamepad/stdweb/src/main.rs @@ -0,0 +1,80 @@ +use winit::{ + event::{device::GamepadEvent, Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +use stdweb::js; + +/** + * Build example (from examples/web/gamepad/stdweb): + * cargo web build + * Run example (from examples/web/gamepad/stdweb): + * cargo web start + * Development (from project root): + * npx nodemon --watch src --watch examples/web/gamepad/stdweb/src -e rs --exec 'cargo web check' + */ + +pub fn main() { + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_title("Gamepad tests") + .build(&event_loop) + .unwrap(); + + let deadzone = 0.12; + + event_loop.run(move |event, _, control_flow| match event { + Event::GamepadEvent(gamepad_handle, event) => match event { + GamepadEvent::Axis { + axis_id, + axis, + value, + stick, + } if value > deadzone => { + let string = format!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick); + js! { console.log( @{string} ); } + } + + GamepadEvent::Stick { + x_id, + y_id, + x_value, + y_value, + side, + } if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => { + let string = format!( + "Stick {:#?} {:#?} {:#?} {:#?} {:#?}", + x_id, y_id, x_value, y_value, side + ); + js! { console.log( @{string} ); } + } + + GamepadEvent::Button { + button_id, + button, + state, + } => { + let string = format!("Button {:#?} {:#?} {:#?}", button_id, button, state); + js! { console.log( @{string} ); } + } + + GamepadEvent::Added => { + let string = format!("[{:?}] {:#?}", gamepad_handle, event); + js! { console.log( @{string} ); } + } + GamepadEvent::Removed => { + let string = format!("[{:?}] {:#?}", gamepad_handle, event); + js! { console.log( @{string} ); } + } + + _ => {} + }, + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + }); +} diff --git a/examples/web/gamepad/websys/.gitignore b/examples/web/gamepad/websys/.gitignore new file mode 100644 index 0000000000..5968859b78 --- /dev/null +++ b/examples/web/gamepad/websys/.gitignore @@ -0,0 +1,7 @@ +/target +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log +.DS_Store diff --git a/examples/web/gamepad/websys/Cargo.toml b/examples/web/gamepad/websys/Cargo.toml new file mode 100644 index 0000000000..f4ad9c90d1 --- /dev/null +++ b/examples/web/gamepad/websys/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "websys-gamepad" +version = "0.0.1" +authors = ["The winit contributors", "Pierre Krieger "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +winit = { path = "../../../../", features = [ "web-sys" ] } +wasm-bindgen = "0.2.45" +wasm-bindgen-test = "0.3.8" +web-sys = { version = "0.3.22", features = [ "console" ] } + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = "0.1.6" diff --git a/examples/web/gamepad/websys/files/gamepad.html b/examples/web/gamepad/websys/files/gamepad.html new file mode 100644 index 0000000000..acb70ea349 --- /dev/null +++ b/examples/web/gamepad/websys/files/gamepad.html @@ -0,0 +1,26 @@ + + + + + + + + Gamepad + + + + + + + + + diff --git a/examples/web/gamepad/websys/src/lib.rs b/examples/web/gamepad/websys/src/lib.rs new file mode 100644 index 0000000000..94a5b42fcb --- /dev/null +++ b/examples/web/gamepad/websys/src/lib.rs @@ -0,0 +1,85 @@ +mod utils; + +use wasm_bindgen::prelude::*; +use winit::{ + event::{device::GamepadEvent, Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +/** + * Build example (from examples/gamepad/websys): + * wasm-pack build --target web + * Run web server (from examples/gamepad/websys): + * npx http-server + * Open your browser at http://localhost:8000/files/${EXAMPLE}.html + * Development (from project root): + * npx nodemon --watch src --watch examples/web/gamepad/websys/src -e rs --exec 'cd examples/web/gamepad/websys && wasm-pack build --target web' + */ + +macro_rules! console_log { + ($($t:tt)*) => (web_sys::console::log_1(&format_args!($($t)*).to_string().into())) +} + +#[wasm_bindgen(start)] +pub fn example_gamepad() { + utils::set_panic_hook(); // needed for error stack trace + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_title("Gamepad tests") + .build(&event_loop) + .unwrap(); + + let deadzone = 0.12; + + event_loop.run(move |event, _, control_flow| match event { + Event::GamepadEvent(gamepad_handle, event) => match event { + GamepadEvent::Axis { + axis_id, + axis, + value, + stick, + } if value > deadzone => { + console_log!("Axis {:#?} {:#?} {:#?} {:#?}", axis_id, axis, value, stick) + } + + GamepadEvent::Stick { + x_id, + y_id, + x_value, + y_value, + side, + } if (x_value.powi(2) + y_value.powi(2)).sqrt() > deadzone => { + console_log!( + "Stick {:#?} {:#?} {:#?} {:#?} {:#?}", + x_id, + y_id, + x_value, + y_value, + side + ) + } + + GamepadEvent::Button { + button_id, + button, + state, + } => { + console_log!("Button {:#?} {:#?} {:#?}", button_id, button, state) + } + + GamepadEvent::Added => { + console_log!("[{:?}] {:#?}", gamepad_handle, event) + } + GamepadEvent::Removed => console_log!("[{:?}] {:#?}", gamepad_handle, event), + + _ => {} + }, + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + _ => (), + }); +} diff --git a/examples/web/gamepad/websys/src/utils.rs b/examples/web/gamepad/websys/src/utils.rs new file mode 100644 index 0000000000..9aae24c65f --- /dev/null +++ b/examples/web/gamepad/websys/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + // #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} diff --git a/src/platform_impl/web/device.rs b/src/platform_impl/web/device.rs deleted file mode 100644 index a2f00b69c4..0000000000 --- a/src/platform_impl/web/device.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Id(pub i32); - -impl Id { - pub unsafe fn dummy() -> Self { - Id(0) - } -} diff --git a/src/platform_impl/web/device/gamepad/constants.rs b/src/platform_impl/web/device/gamepad/constants.rs new file mode 100644 index 0000000000..c2c589adc1 --- /dev/null +++ b/src/platform_impl/web/device/gamepad/constants.rs @@ -0,0 +1,37 @@ +use crate::event::device::{GamepadAxis, GamepadButton}; + +pub(crate) static BUTTONS: [GamepadButton; 16] = [ + GamepadButton::South, + GamepadButton::East, + GamepadButton::West, + GamepadButton::North, + GamepadButton::LeftTrigger, + GamepadButton::RightTrigger, + GamepadButton::LeftShoulder, + GamepadButton::RightShoulder, + GamepadButton::Select, + GamepadButton::Start, + GamepadButton::LeftStick, + GamepadButton::RightStick, + GamepadButton::DPadUp, + GamepadButton::DPadDown, + GamepadButton::DPadLeft, + GamepadButton::DPadRight, +]; + +pub(crate) static AXES: [GamepadAxis; 6] = [ + GamepadAxis::LeftStickX, + GamepadAxis::LeftStickY, + GamepadAxis::RightStickX, + GamepadAxis::RightStickY, + GamepadAxis::LeftTrigger, + GamepadAxis::RightTrigger, +]; + +pub(crate) fn button_code(index: usize) -> Option { + BUTTONS.get(index).map(|ev| ev.clone()) +} + +pub(crate) fn axis_code(index: usize) -> Option { + AXES.get(index).map(|ev| ev.clone()) +} diff --git a/src/platform_impl/web/device/gamepad/manager.rs b/src/platform_impl/web/device/gamepad/manager.rs new file mode 100644 index 0000000000..aeaba59fa4 --- /dev/null +++ b/src/platform_impl/web/device/gamepad/manager.rs @@ -0,0 +1,158 @@ +use super::utils; +use crate::event::device; +use crate::platform_impl::platform::{backend, device::gamepad, event_loop::global, GamepadHandle}; +use std::collections::VecDeque; + +pub struct Manager { + pub(crate) gamepads: Vec, + pub(crate) events: VecDeque<(backend::gamepad::Gamepad, device::GamepadEvent)>, + pub(crate) global_window: Option, +} + +impl Manager { + pub fn new() -> Self { + Self { + gamepads: Vec::new(), + events: VecDeque::new(), + global_window: None, + } + } + + // Register global window to fetch gamepads. + // Due to Chrome issue, I prefer to use its gamepad list + pub fn set_global_window(&mut self, global_window: global::Shared) { + self.global_window.replace(global_window); + } + + // Get an updated raw gamepad and generate a new mapping + pub fn collect_gamepads(&self) -> Option> { + self.global_window.as_ref().map(|w| w.get_gamepads()) + } + + // Collect gamepad events (buttons/axes/sticks) + // dispatch to handler and update gamepads + pub fn collect_events(&mut self, mut handler: F) + where + F: 'static + FnMut((device::GamepadHandle, device::GamepadEvent)), + { + let opt_new_gamepads = self.collect_gamepads(); + if opt_new_gamepads.is_none() { + return; + } + + let new_gamepads = opt_new_gamepads.unwrap(); + let old_gamepads = &self.gamepads; + + let mut old_index = 0; + let mut new_index = 0; + + // Collect events + loop { + match (old_gamepads.get(old_index), new_gamepads.get(new_index)) { + (Some(old), Some(new)) if old.index() == new.index() => { + // Button events + let buttons = old.mapping.buttons().zip(new.mapping.buttons()).enumerate(); + for (btn_index, (old_button, new_button)) in buttons { + match (old_button, new_button) { + (false, true) => self + .events + .push_back((new.clone(), utils::gamepad_button(btn_index, true))), + (true, false) => self + .events + .push_back((new.clone(), utils::gamepad_button(btn_index, false))), + _ => (), + } + } + + // Axis events + let axes = old.mapping.axes().zip(new.mapping.axes()).enumerate(); + for (axis_index, (old_axis, new_axis)) in axes { + if old_axis != new_axis { + self.events + .push_back((new.clone(), utils::gamepad_axis(axis_index, new_axis))) + } + } + + // Stick events + let mut old_axes = old.mapping.axes(); + let mut new_axes = new.mapping.axes(); + + let old_left = (old_axes.next(), old_axes.next()); + let new_left = (new_axes.next(), new_axes.next()); + if old_left != new_left { + if let (Some(x), Some(y)) = (new_left.0, new_left.1) { + self.events.push_back(( + new.clone(), + utils::gamepad_stick(0, 1, x, y, device::Side::Left), + )); + } + } + + let old_right = (old_axes.next(), old_axes.next()); + let new_right = (new_axes.next(), new_axes.next()); + if old_right != new_right { + if let (Some(x), Some(y)) = (new_right.0, new_right.1) { + self.events.push_back(( + new.clone(), + utils::gamepad_stick(2, 3, x, y, device::Side::Right), + )); + } + } + + // Increment indices + old_index += 1; + new_index += 1; + } + + // Connect + (None, Some(new)) => { + self.events + .push_back((new.clone(), device::GamepadEvent::Added)); + new_index += 1; + } + + // Connect + (Some(old), Some(new)) if old.index > new.index => { + self.events + .push_back((new.clone(), device::GamepadEvent::Added)); + new_index += 1; + } + + // Disconnect + (Some(old), Some(_new)) => { + self.events + .push_back((old.clone(), device::GamepadEvent::Removed)); + old_index += 1; + } + + // Disconnect + (Some(old), None) => { + self.events + .push_back((old.clone(), device::GamepadEvent::Removed)); + old_index += 1; + } + + // Break loop + (None, None) => break, + } + } + + // Dispatch events and drain events vec + loop { + if let Some((gamepad, event)) = self.events.pop_front() { + handler(( + device::GamepadHandle(GamepadHandle { + id: gamepad.index, + gamepad: gamepad::Shared::Raw(gamepad), + }), + event, + )); + } else { + break; + } + } + + // Update gamepads + self.gamepads = new_gamepads; + } +} diff --git a/src/platform_impl/web/device/gamepad/mapping.rs b/src/platform_impl/web/device/gamepad/mapping.rs new file mode 100644 index 0000000000..c424b73789 --- /dev/null +++ b/src/platform_impl/web/device/gamepad/mapping.rs @@ -0,0 +1,23 @@ +#[derive(Debug, Clone)] +pub enum Mapping { + Standard { buttons: [bool; 16], axes: [f64; 6] }, + NoMapping { buttons: Vec, axes: Vec }, +} + +impl Mapping { + pub(crate) fn buttons<'a>(&'a self) -> impl Iterator + 'a { + match self { + Mapping::Standard { buttons, .. } => buttons.iter(), + Mapping::NoMapping { buttons, .. } => buttons.iter(), + } + .cloned() + } + + pub(crate) fn axes<'a>(&'a self) -> impl Iterator + 'a { + match self { + Mapping::Standard { axes, .. } => axes.iter(), + Mapping::NoMapping { axes, .. } => axes.iter(), + } + .cloned() + } +} diff --git a/src/platform_impl/web/device/gamepad/mod.rs b/src/platform_impl/web/device/gamepad/mod.rs new file mode 100644 index 0000000000..7ed8e7abce --- /dev/null +++ b/src/platform_impl/web/device/gamepad/mod.rs @@ -0,0 +1,99 @@ +mod manager; +mod mapping; +mod utils; + +pub mod constants; +pub use manager::Manager; +pub use mapping::Mapping; + +use crate::event::device::{BatteryLevel, RumbleError}; +use crate::platform_impl::platform::backend; +use std::fmt; + +pub enum Shared { + Raw(backend::gamepad::Gamepad), + Dummy, +} + +impl Shared { + // An integer that is auto-incremented to be unique for each device + // currently connected to the system. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index + pub fn id(&self) -> i32 { + match self { + Shared::Raw(g) => g.index() as i32, + Shared::Dummy => -1, + } + } + + // A string containing some information about the controller. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id + pub fn info(&self) -> String { + match self { + Shared::Raw(g) => g.id(), + Shared::Dummy => String::new(), + } + } + + // A boolean indicating whether the gamepad is still connected to the system. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected + pub fn connected(&self) -> bool { + match self { + Shared::Raw(g) => g.connected(), + Shared::Dummy => false, + } + } + + // [EXPERIMENTAL] An array containing GamepadHapticActuator objects, + // each of which represents haptic feedback hardware available on the controller. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/hapticActuators + pub fn rumble(&self, left_speed: f64, _right_speed: f64) -> Result<(), RumbleError> { + match self { + Shared::Dummy => Ok(()), + Shared::Raw(g) => { + g.vibrate(left_speed, 1000f64); + Ok(()) + } + } + } + + pub fn is_dummy(&self) -> bool { + match self { + Shared::Dummy => true, + _ => false, + } + } + + pub fn port(&self) -> Option { + None + } + + pub fn battery_level(&self) -> Option { + None + } +} + +impl Clone for Shared { + fn clone(&self) -> Self { + match self { + Shared::Raw(g) => Shared::Raw(g.clone()), + Shared::Dummy => Shared::Dummy, + } + } +} + +impl Default for Shared { + fn default() -> Self { + Shared::Dummy + } +} + +impl fmt::Debug for Shared { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + if self.is_dummy() { + write!(f, "Gamepad (Dummy)") + } else { + write!(f, "Gamepad ({}#{})", self.id(), self.info()) + } + } +} diff --git a/src/platform_impl/web/device/gamepad/utils.rs b/src/platform_impl/web/device/gamepad/utils.rs new file mode 100644 index 0000000000..3bc8cb973d --- /dev/null +++ b/src/platform_impl/web/device/gamepad/utils.rs @@ -0,0 +1,50 @@ +use super::constants; +use crate::event::{device, ElementState}; + +pub fn gamepad_button(code: usize, pressed: bool) -> device::GamepadEvent { + let button_id = code as u32; + let button = constants::button_code(code); + + let state = if pressed { + ElementState::Pressed + } else { + ElementState::Released + }; + + device::GamepadEvent::Button { + button_id, + button, + state, + } +} + +pub fn gamepad_axis(code: usize, value: f64) -> device::GamepadEvent { + let axis_id = code as u32; + let axis = constants::axis_code(code); + + device::GamepadEvent::Axis { + axis_id, + axis, + value, + stick: true, + } +} + +pub fn gamepad_stick( + x_code: usize, + y_code: usize, + x_value: f64, + y_value: f64, + side: device::Side, +) -> device::GamepadEvent { + let x_id = x_code as u32; + let y_id = y_code as u32; + + device::GamepadEvent::Stick { + x_id, + y_id, + x_value, + y_value, + side, + } +} diff --git a/src/platform_impl/web/device/mod.rs b/src/platform_impl/web/device/mod.rs new file mode 100644 index 0000000000..eb4108bade --- /dev/null +++ b/src/platform_impl/web/device/mod.rs @@ -0,0 +1,161 @@ +pub mod gamepad; + +use super::event_loop::EventLoop; +use crate::event::device; + +use std::{ + cmp::{Eq, Ordering, PartialEq, PartialOrd}, + hash::{Hash, Hasher}, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct MouseId(pub i32); + +unsafe impl Send for MouseId {} +unsafe impl Sync for MouseId {} + +impl MouseId { + pub unsafe fn dummy() -> Self { + Self(0) + } + + pub fn is_connected(&self) -> bool { + false + } + + pub fn enumerate<'a, T>( + event_loop: &'a EventLoop, + ) -> impl 'a + Iterator { + event_loop.mice() + } +} + +impl From for device::MouseId { + fn from(platform_id: MouseId) -> Self { + Self(platform_id) + } +} +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct KeyboardId(pub i32); + +unsafe impl Send for KeyboardId {} +unsafe impl Sync for KeyboardId {} + +impl KeyboardId { + pub unsafe fn dummy() -> Self { + Self(0) + } + + pub fn is_connected(&self) -> bool { + false + } + + pub fn enumerate<'a, T>( + event_loop: &'a EventLoop, + ) -> impl 'a + Iterator { + event_loop.keyboards() + } +} + +impl From for device::KeyboardId { + fn from(platform_id: KeyboardId) -> Self { + Self(platform_id) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct HidId(pub i32); + +unsafe impl Send for HidId {} +unsafe impl Sync for HidId {} + +impl HidId { + pub unsafe fn dummy() -> Self { + Self(0) + } + + pub fn is_connected(&self) -> bool { + false + } + + pub fn enumerate<'a, T>( + event_loop: &'a EventLoop, + ) -> impl 'a + Iterator { + event_loop.hids() + } +} + +impl From for device::HidId { + fn from(platform_id: HidId) -> Self { + Self(platform_id) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct GamepadHandle { + pub(crate) id: i32, + pub(crate) gamepad: gamepad::Shared, +} + +unsafe impl Send for GamepadHandle {} +unsafe impl Sync for GamepadHandle {} + +impl GamepadHandle { + pub unsafe fn dummy() -> Self { + Self { + id: -1, + gamepad: gamepad::Shared::default(), + } + } + + pub fn is_connected(&self) -> bool { + self.gamepad.connected() + } + + pub fn enumerate<'a, T>( + event_loop: &'a EventLoop, + ) -> impl 'a + Iterator { + event_loop.gamepads() + } + + pub fn rumble(&self, left_speed: f64, right_speed: f64) -> Result<(), device::RumbleError> { + self.gamepad.rumble(left_speed, right_speed) + } + + pub fn port(&self) -> Option { + self.gamepad.port() + } + + pub fn battery_level(&self) -> Option { + self.gamepad.battery_level() + } +} + +impl Eq for GamepadHandle {} + +impl PartialEq for GamepadHandle { + #[inline(always)] + fn eq(&self, othr: &Self) -> bool { + self.id == othr.id + } +} + +impl Ord for GamepadHandle { + #[inline(always)] + fn cmp(&self, othr: &Self) -> Ordering { + self.id.cmp(&othr.id) + } +} +impl PartialOrd for GamepadHandle { + #[inline(always)] + fn partial_cmp(&self, othr: &Self) -> Option { + self.id.partial_cmp(&othr.id) + } +} + +impl Hash for GamepadHandle { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.id.hash(state) + } +} diff --git a/src/platform_impl/web/event_loop/global.rs b/src/platform_impl/web/event_loop/global.rs new file mode 100644 index 0000000000..8ec797009a --- /dev/null +++ b/src/platform_impl/web/event_loop/global.rs @@ -0,0 +1,81 @@ +use super::super::device::{gamepad, GamepadHandle}; +use super::backend; +use crate::event::device; +use std::{cell::RefCell, collections::HashSet, rc::Rc}; + +#[derive(Debug)] +pub struct Window { + raw: RefCell>, + gamepads: Rc>>, +} + +#[derive(Debug)] +pub struct Shared(Rc); + +impl Shared { + pub fn new() -> Self { + Self(Rc::new(Window { + raw: RefCell::new(None), + gamepads: Rc::new(RefCell::new(HashSet::new())), + })) + } + + // Request window object and listen global events + pub fn register_events(&self) -> Result<(), crate::error::OsError> { + if (*self.0.raw.borrow()).is_none() { + let shared = backend::window::Shared::create()?; + let mut window = shared.0.borrow_mut(); + + let shared_gamepads = self.0.gamepads.clone(); + window.on_gamepad_connected(move |gamepad: backend::gamepad::Gamepad| { + let mut gamepads = shared_gamepads.borrow_mut(); + let index = gamepad.index(); + if !gamepads.contains(&index) { + gamepads.insert(index); + } + }); + + let shared_gamepads = self.0.gamepads.clone(); + window.on_gamepad_disconnected(move |gamepad: backend::gamepad::Gamepad| { + let mut gamepads = shared_gamepads.borrow_mut(); + let index = gamepad.index(); + if gamepads.contains(&index) { + gamepads.remove(&index); + } + }); + + self.0.raw.replace(Some(shared.clone())); + } + + Ok(()) + } + + // Google Chrome create an array of [null, null, null, null]. + // To fix that issue, I create my own list of gamepads + // by listening "gamepadconnected" and "gamepaddisconnected" + pub fn get_gamepads(&self) -> Vec { + let gamepads = self.0.gamepads.borrow_mut(); + backend::get_gamepads() + .filter(|g| gamepads.contains(&g.index())) + .collect() + } + + // Return gamepads handles required for EventLoop::gamepads() + pub fn get_gamepad_handles(&self) -> Vec { + self.get_gamepads() + .iter() + .map(|gamepad| { + device::GamepadHandle(GamepadHandle { + id: gamepad.index, + gamepad: gamepad::Shared::Raw(gamepad.clone()), + }) + }) + .collect() + } +} + +impl Clone for Shared { + fn clone(&self) -> Self { + Shared(self.0.clone()) + } +} diff --git a/src/platform_impl/web/event_loop/mod.rs b/src/platform_impl/web/event_loop/mod.rs index 24be4d3dcc..ccdef374d6 100644 --- a/src/platform_impl/web/event_loop/mod.rs +++ b/src/platform_impl/web/event_loop/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod global; mod proxy; mod runner; mod state; @@ -55,4 +56,20 @@ impl EventLoop { pub fn window_target(&self) -> &root::EventLoopWindowTarget { &self.elw } + + pub fn mice(&self) -> impl '_ + Iterator { + std::iter::empty() + } + + pub fn keyboards(&self) -> impl '_ + Iterator { + std::iter::empty() + } + + pub fn hids(&self) -> impl '_ + Iterator { + std::iter::empty() + } + + pub fn gamepads(&self) -> impl '_ + Iterator { + self.elw.p.collect_gamepads().into_iter() + } } diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 4c51a8a36a..f98cfcf0bf 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -1,6 +1,7 @@ use super::{super::ScaleChangeArgs, backend, state::State}; use crate::event::{Event, StartCause}; use crate::event_loop as root; +use crate::platform_impl::platform::device::gamepad; use crate::window::WindowId; use instant::{Duration, Instant}; @@ -30,6 +31,7 @@ pub struct Execution { destroy_pending: RefCell>, scale_change_detector: RefCell>, unload_event_handle: RefCell>, + gamepad_manager: RefCell, } enum RunnerEnum { @@ -106,9 +108,17 @@ impl Shared { destroy_pending: RefCell::new(VecDeque::new()), scale_change_detector: RefCell::new(None), unload_event_handle: RefCell::new(None), + gamepad_manager: RefCell::new(gamepad::Manager::new()), })) } + pub fn set_global_window(&self, global_window: super::global::Shared) { + self.0 + .gamepad_manager + .borrow_mut() + .set_global_window(global_window); + } + pub fn add_canvas(&self, id: WindowId, canvas: &Rc>) { self.0 .all_canvases @@ -279,6 +289,14 @@ impl Shared { for window_id in redraw_events { self.handle_event(Event::RedrawRequested(window_id), &mut control); } + + // Collect all global events + let mut gamepad_manager = self.0.gamepad_manager.borrow_mut(); + let instance = self.clone(); + gamepad_manager.collect_events(move |(handle, event)| { + instance.handle_event(Event::GamepadEvent(handle, event), &mut control); + }); + self.handle_event(Event::RedrawEventsCleared, &mut control); self.apply_control_flow(control); diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 7818ac0d37..4e74ba4ac6 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,8 +1,9 @@ -use super::{super::monitor, backend, device, proxy::Proxy, runner, window}; +use super::{super::monitor, backend, global, proxy::Proxy, runner, window}; use crate::dpi::{PhysicalSize, Size}; -use crate::event::{DeviceId, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; +use crate::event::{device, ElementState, Event, KeyboardInput, TouchPhase, WindowEvent}; use crate::event_loop::ControlFlow; use crate::monitor::MonitorHandle as RootMH; +use crate::platform_impl::platform::device::{KeyboardId, MouseId}; use crate::window::{Theme, WindowId}; use std::cell::RefCell; use std::clone::Clone; @@ -11,12 +12,14 @@ use std::rc::Rc; pub struct WindowTarget { pub(crate) runner: runner::Shared, + pub(crate) global_window: global::Shared, } impl Clone for WindowTarget { fn clone(&self) -> Self { WindowTarget { runner: self.runner.clone(), + global_window: self.global_window.clone(), } } } @@ -25,6 +28,7 @@ impl WindowTarget { pub fn new() -> Self { WindowTarget { runner: runner::Shared::new(), + global_window: global::Shared::new(), } } @@ -33,6 +37,7 @@ impl WindowTarget { } pub fn run(&self, event_handler: Box, &mut ControlFlow)>) { + self.runner.set_global_window(self.global_window.clone()); self.runner.set_listener(event_handler); let runner = self.runner.clone(); self.runner.set_on_scale_change(move |arg| { @@ -44,6 +49,14 @@ impl WindowTarget { window::Id(self.runner.generate_id()) } + pub fn collect_gamepads(&self) -> Vec { + self.global_window.get_gamepad_handles() + } + + pub fn register_global_events(&self) -> Result<(), crate::error::OsError> { + self.global_window.register_events() + } + pub fn register(&self, canvas: &Rc>, id: window::Id) { self.runner.add_canvas(WindowId(id), canvas); let mut canvas = canvas.borrow_mut(); @@ -67,38 +80,28 @@ impl WindowTarget { let runner = self.runner.clone(); canvas.on_keyboard_press(move |scancode, virtual_keycode, modifiers| { - #[allow(deprecated)] - runner.send_event(Event::WindowEvent { - window_id: WindowId(id), - event: WindowEvent::KeyboardInput { - device_id: DeviceId(unsafe { device::Id::dummy() }), - input: KeyboardInput { - scancode, - state: ElementState::Pressed, - virtual_keycode, - modifiers, - }, - is_synthetic: false, - }, - }); + runner.send_event(Event::KeyboardEvent( + device::KeyboardId(unsafe { KeyboardId::dummy() }), + device::KeyboardEvent::Input(KeyboardInput { + scancode, + state: ElementState::Pressed, + virtual_keycode, + modifiers, + }), + )); }); let runner = self.runner.clone(); canvas.on_keyboard_release(move |scancode, virtual_keycode, modifiers| { - #[allow(deprecated)] - runner.send_event(Event::WindowEvent { - window_id: WindowId(id), - event: WindowEvent::KeyboardInput { - device_id: DeviceId(unsafe { device::Id::dummy() }), - input: KeyboardInput { - scancode, - state: ElementState::Released, - virtual_keycode, - modifiers, - }, - is_synthetic: false, - }, - }); + runner.send_event(Event::KeyboardEvent( + device::KeyboardId(unsafe { KeyboardId::dummy() }), + device::KeyboardEvent::Input(KeyboardInput { + scancode, + state: ElementState::Released, + virtual_keycode, + modifiers, + }), + )); }); let runner = self.runner.clone(); @@ -110,31 +113,26 @@ impl WindowTarget { }); let runner = self.runner.clone(); - canvas.on_cursor_leave(move |pointer_id| { + canvas.on_cursor_leave(move || { runner.send_event(Event::WindowEvent { window_id: WindowId(id), - event: WindowEvent::CursorLeft { - device_id: DeviceId(device::Id(pointer_id)), - }, + event: WindowEvent::CursorLeft, }); }); let runner = self.runner.clone(); - canvas.on_cursor_enter(move |pointer_id| { + canvas.on_cursor_enter(move || { runner.send_event(Event::WindowEvent { window_id: WindowId(id), - event: WindowEvent::CursorEntered { - device_id: DeviceId(device::Id(pointer_id)), - }, + event: WindowEvent::CursorEntered, }); }); let runner = self.runner.clone(); - canvas.on_cursor_move(move |pointer_id, position, modifiers| { + canvas.on_cursor_move(move |position, modifiers| { runner.send_event(Event::WindowEvent { window_id: WindowId(id), event: WindowEvent::CursorMoved { - device_id: DeviceId(device::Id(pointer_id)), position, modifiers, }, @@ -150,47 +148,37 @@ impl WindowTarget { std::iter::once(Event::WindowEvent { window_id: WindowId(id), event: WindowEvent::CursorMoved { - device_id: DeviceId(device::Id(pointer_id)), position, modifiers, }, }) - .chain(std::iter::once(Event::WindowEvent { - window_id: WindowId(id), - event: WindowEvent::MouseInput { - device_id: DeviceId(device::Id(pointer_id)), + .chain(std::iter::once(Event::MouseEvent( + device::MouseId(MouseId(pointer_id)), + device::MouseEvent::Button { state: ElementState::Pressed, button, - modifiers, }, - })), + ))), ); }); let runner = self.runner.clone(); - canvas.on_mouse_release(move |pointer_id, button, modifiers| { - runner.send_event(Event::WindowEvent { - window_id: WindowId(id), - event: WindowEvent::MouseInput { - device_id: DeviceId(device::Id(pointer_id)), + canvas.on_mouse_release(move |pointer_id, button| { + runner.send_event(Event::MouseEvent( + device::MouseId(MouseId(pointer_id)), + device::MouseEvent::Button { state: ElementState::Released, button, - modifiers, }, - }); + )); }); let runner = self.runner.clone(); - canvas.on_mouse_wheel(move |pointer_id, delta, modifiers| { - runner.send_event(Event::WindowEvent { - window_id: WindowId(id), - event: WindowEvent::MouseWheel { - device_id: DeviceId(device::Id(pointer_id)), - delta, - phase: TouchPhase::Moved, - modifiers, - }, - }); + canvas.on_mouse_wheel(move |pointer_id, delta| { + runner.send_event(Event::MouseEvent( + device::MouseId(MouseId(pointer_id)), + device::MouseEvent::Wheel(delta.0, delta.1), + )); }); let runner = self.runner.clone(); diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index 1eb479173c..f56890a023 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -37,7 +37,8 @@ mod backend; #[cfg(not(any(feature = "web-sys", feature = "stdweb")))] compile_error!("Please select a feature to build for web: `web-sys`, `stdweb`"); -pub use self::device::Id as DeviceId; +pub(crate) use self::device::*; + pub use self::error::OsError; pub use self::event_loop::{ EventLoop, Proxy as EventLoopProxy, WindowTarget as EventLoopWindowTarget, diff --git a/src/platform_impl/web/stdweb/canvas.rs b/src/platform_impl/web/stdweb/canvas.rs index c68ec911aa..8aeba9e5f6 100644 --- a/src/platform_impl/web/stdweb/canvas.rs +++ b/src/platform_impl/web/stdweb/canvas.rs @@ -1,7 +1,7 @@ -use super::event; +use super::utils; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; -use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; +use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode}; use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; use std::cell::RefCell; @@ -124,9 +124,9 @@ impl Canvas { self.on_keyboard_release = Some(self.add_user_event(move |event: KeyUpEvent| { event.prevent_default(); handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), + utils::scan_code(&event), + utils::virtual_key_code(&event), + utils::keyboard_modifiers(&event), ); })); } @@ -148,9 +148,9 @@ impl Canvas { event.prevent_default(); } handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), + utils::scan_code(&event), + utils::virtual_key_code(&event), + utils::keyboard_modifiers(&event), ); })); } @@ -167,38 +167,34 @@ impl Canvas { self.on_received_character = Some(self.add_user_event(move |event: KeyPressEvent| { // Supress further handling to stop keys like the space key from scrolling the page. event.prevent_default(); - handler(event::codepoint(&event)); + handler(utils::codepoint(&event)); })); } pub fn on_cursor_leave(&mut self, mut handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(), { - self.on_cursor_leave = Some(self.add_event(move |event: PointerOutEvent| { - handler(event.pointer_id()); + self.on_cursor_leave = Some(self.add_event(move |_event: PointerOutEvent| { + handler(); })); } pub fn on_cursor_enter(&mut self, mut handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(), { - self.on_cursor_enter = Some(self.add_event(move |event: PointerOverEvent| { - handler(event.pointer_id()); + self.on_cursor_enter = Some(self.add_event(move |_event: PointerOverEvent| { + handler(); })); } pub fn on_mouse_release(&mut self, mut handler: F) where - F: 'static + FnMut(i32, MouseButton, ModifiersState), + F: 'static + FnMut(i32, MouseButton), { self.on_mouse_release = Some(self.add_user_event(move |event: PointerUpEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); + handler(event.pointer_id(), utils::mouse_button(&event)); })); } @@ -222,27 +218,26 @@ impl Canvas { pub fn on_cursor_move(&mut self, mut handler: F) where - F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + F: 'static + FnMut(PhysicalPosition, ModifiersState), { // todo self.on_cursor_move = Some(self.add_event(move |event: PointerMoveEvent| { handler( event.pointer_id(), - event::mouse_position(&event).to_physical(super::scale_factor()), - event::mouse_modifiers(&event), + utils::mouse_position(&event).to_physical(super::scale_factor()), + utils::mouse_modifiers(&event), ); })); } pub fn on_mouse_wheel(&mut self, mut handler: F) where - F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), + F: 'static + FnMut(i32, (f64, f64)), { self.on_mouse_wheel = Some(self.add_event(move |event: MouseWheelEvent| { event.prevent_default(); - if let Some(delta) = event::mouse_scroll_delta(&event) { - handler(0, delta, event::mouse_modifiers(&event)); - } + let delta = utils::mouse_scroll_delta(&event); + handler(0, delta); })); } diff --git a/src/platform_impl/web/stdweb/gamepad.rs b/src/platform_impl/web/stdweb/gamepad.rs new file mode 100644 index 0000000000..f54d0c1528 --- /dev/null +++ b/src/platform_impl/web/stdweb/gamepad.rs @@ -0,0 +1,82 @@ +use super::utils; +use crate::platform_impl::platform::device; +use std::cmp::PartialEq; +use stdweb::js; + +#[derive(Debug)] +pub struct Gamepad { + pub(crate) index: i32, + pub(crate) raw: stdweb::web::Gamepad, + pub(crate) mapping: device::gamepad::Mapping, +} + +impl Gamepad { + pub fn new(raw: stdweb::web::Gamepad) -> Self { + let mapping = utils::create_mapping(&raw); + + Self { + index: raw.index(), + raw, + mapping, + } + } + + // An integer that is auto-incremented to be unique for each device + // currently connected to the system. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index + pub fn index(&self) -> i32 { + self.raw.index() + } + + // A string containing some information about the controller. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id + pub fn id(&self) -> String { + self.raw.id() + } + + // A boolean indicating whether the gamepad is still connected to the system. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected + pub fn connected(&self) -> bool { + self.raw.connected() + } + + // EXPERIMENTAL + #[allow(dead_code)] + pub fn vibrate(&self, value: f64, duration: f64) { + let index = self.index; + js! { + const gamepads = navigator.getGamepads(); + let gamepad = null; + for (let i = 0; i < gamepads.length; i++) { + if (gamepads[i] && gamepads[i].index == @{index}) { + gamepad = gamepads[i]; + break + } + } + if (!gamepad || !gamepad.hapticActuators) return; + for (let i = 0; i < gamepad.hapticActuators.length; i++) { + const actuator = gamepad.hapticActuators[i]; + if (actuator && actuator.type === "vibration") { + actuator.pulse(@{value}, @{duration}); + } + } + } + } +} + +impl Clone for Gamepad { + fn clone(&self) -> Self { + Self { + index: self.index, + raw: self.raw.clone(), + mapping: self.mapping.clone(), + } + } +} + +impl PartialEq for Gamepad { + #[inline(always)] + fn eq(&self, othr: &Self) -> bool { + self.raw.index() == othr.raw.index() + } +} diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index e7a6122791..5e4d67043b 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -1,9 +1,11 @@ #![deprecated(since = "0.23.0", note = "Please migrate to web-sys over stdweb")] mod canvas; -mod event; +pub mod gamepad; mod scaling; mod timeout; +mod utils; +pub mod window; pub use self::canvas::Canvas; pub use self::scaling::ScaleChangeDetector; @@ -91,4 +93,10 @@ pub fn is_fullscreen(canvas: &CanvasElement) -> bool { } } +pub fn get_gamepads() -> impl Iterator { + stdweb::web::Gamepad::get_all() + .into_iter() + .filter_map(|gamepad| gamepad.map(|gamepad| gamepad::Gamepad::new(gamepad))) +} + pub type RawCanvasType = CanvasElement; diff --git a/src/platform_impl/web/stdweb/event.rs b/src/platform_impl/web/stdweb/utils.rs similarity index 88% rename from src/platform_impl/web/stdweb/event.rs rename to src/platform_impl/web/stdweb/utils.rs index d04298af3d..913f697995 100644 --- a/src/platform_impl/web/stdweb/event.rs +++ b/src/platform_impl/web/stdweb/utils.rs @@ -1,7 +1,10 @@ use crate::dpi::LogicalPosition; -use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; +use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode}; -use stdweb::web::event::{IKeyboardEvent, IMouseEvent, MouseWheelDeltaMode, MouseWheelEvent}; +use stdweb::web::{ + event::{IKeyboardEvent, IMouseEvent, MouseWheelEvent}, + Gamepad, GamepadMappingType, +}; use stdweb::{js, unstable::TryInto, JsSerialize}; pub fn mouse_button(event: &impl IMouseEvent) -> MouseButton { @@ -30,18 +33,11 @@ pub fn mouse_position(event: &impl IMouseEvent) -> LogicalPosition { } } -pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> Option { +pub fn mouse_scroll_delta(event: &MouseWheelEvent) -> (f64, f64) { let x = event.delta_x(); let y = -event.delta_y(); - match event.delta_mode() { - MouseWheelDeltaMode::Line => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), - MouseWheelDeltaMode::Pixel => { - let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor()); - Some(MouseScrollDelta::PixelDelta(delta)) - } - MouseWheelDeltaMode::Page => None, - } + (x, y); } pub fn scan_code(event: &T) -> ScanCode { @@ -230,3 +226,31 @@ pub fn codepoint(event: &impl IKeyboardEvent) -> char { // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key event.key().chars().next().unwrap() } + +pub fn create_mapping(raw: &Gamepad) -> gamepad::Mapping { + match raw.mapping() { + GamepadMappingType::Standard => { + let mut buttons = [false; 16]; + let mut axes = [0.0; 6]; + + for (index, button) in raw.buttons().into_iter().enumerate().take(buttons.len()) { + buttons[index] = button.pressed(); + } + + for (index, axis) in raw.axes().into_iter().enumerate().take(axes.len()) { + axes[index] = axis; + } + + gamepad::Mapping::Standard { buttons, axes } + } + _ => { + let buttons = raw + .buttons() + .into_iter() + .map(|button| button.pressed()) + .collect(); + let axes = raw.axes(); + gamepad::Mapping::NoMapping { buttons, axes } + } + } +} diff --git a/src/platform_impl/web/stdweb/windows.rs b/src/platform_impl/web/stdweb/windows.rs new file mode 100644 index 0000000000..6fc5c5a8d5 --- /dev/null +++ b/src/platform_impl/web/stdweb/windows.rs @@ -0,0 +1,77 @@ +use super::gamepad; +use crate::error::OsError as RootOE; +use std::{cell::RefCell, rc::Rc}; +use stdweb::web; +use stdweb::web::{event::IGamepadEvent, IEventTarget}; + +#[derive(Debug)] +pub struct Shared(pub Rc>); + +#[derive(Debug)] +pub struct Window { + raw: web::Window, + on_gamepad_connected: Option, + on_gamepad_disconnected: Option, +} + +impl Shared { + pub fn create() -> Result { + let global = Window::create()?; + Ok(Shared(Rc::new(RefCell::new(global)))) + } +} + +impl Clone for Shared { + fn clone(&self) -> Self { + Shared(self.0.clone()) + } +} + +impl Window { + pub fn create() -> Result { + let raw = stdweb::web::window(); + + Ok(Window { + raw, + on_gamepad_connected: None, + on_gamepad_disconnected: None, + }) + } + + pub fn on_gamepad_connected(&mut self, mut handler: F) + where + F: 'static + FnMut(gamepad::Gamepad), + { + self.on_gamepad_connected = Some(self.add_event( + move |event: stdweb::web::event::GamepadConnectedEvent| { + let gamepad = event.gamepad(); + handler(gamepad::Gamepad::new(gamepad)); + }, + )); + } + + pub fn on_gamepad_disconnected(&mut self, mut handler: F) + where + F: 'static + FnMut(gamepad::Gamepad), + { + self.on_gamepad_connected = Some(self.add_event( + move |event: stdweb::web::event::GamepadDisconnectedEvent| { + let gamepad = event.gamepad(); + handler(gamepad::Gamepad::new(gamepad)); + }, + )); + } + + fn add_event(&self, mut handler: F) -> web::EventListenerHandle + where + E: web::event::ConcreteEvent, + F: 'static + FnMut(E), + { + self.raw.add_event_listener(move |event: E| { + event.stop_propagation(); + event.cancel_bubble(); + + handler(event); + }) + } +} diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index ae2fe0e186..94b1d8307a 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -1,9 +1,9 @@ -use super::event; use super::event_handle::EventListenerHandle; use super::media_query_handle::MediaQueryListHandle; +use super::utils; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; -use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; +use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode}; use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; use std::cell::RefCell; @@ -142,9 +142,9 @@ impl Canvas { move |event: KeyboardEvent| { event.prevent_default(); handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), + utils::scan_code(&event), + utils::virtual_key_code(&event), + utils::keyboard_modifiers(&event), ); }, )); @@ -169,9 +169,9 @@ impl Canvas { event.prevent_default(); } handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), + utils::scan_code(&event), + utils::virtual_key_code(&event), + utils::keyboard_modifiers(&event), ); }, )); @@ -191,14 +191,14 @@ impl Canvas { move |event: KeyboardEvent| { // Supress further handling to stop keys like the space key from scrolling the page. event.prevent_default(); - handler(event::codepoint(&event)); + handler(utils::codepoint(&event)); }, )); } pub fn on_cursor_leave(&mut self, handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(), { match &mut self.mouse_state { MouseState::HasPointerEvent(h) => h.on_cursor_leave(&self.common, handler), @@ -208,7 +208,7 @@ impl Canvas { pub fn on_cursor_enter(&mut self, handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(), { match &mut self.mouse_state { MouseState::HasPointerEvent(h) => h.on_cursor_enter(&self.common, handler), @@ -218,7 +218,7 @@ impl Canvas { pub fn on_mouse_release(&mut self, handler: F) where - F: 'static + FnMut(i32, MouseButton, ModifiersState), + F: 'static + FnMut(i32, MouseButton), { match &mut self.mouse_state { MouseState::HasPointerEvent(h) => h.on_mouse_release(&self.common, handler), @@ -238,7 +238,7 @@ impl Canvas { pub fn on_cursor_move(&mut self, handler: F) where - F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + F: 'static + FnMut(PhysicalPosition, ModifiersState), { match &mut self.mouse_state { MouseState::HasPointerEvent(h) => h.on_cursor_move(&self.common, handler), @@ -248,12 +248,12 @@ impl Canvas { pub fn on_mouse_wheel(&mut self, mut handler: F) where - F: 'static + FnMut(i32, MouseScrollDelta, ModifiersState), + F: 'static + FnMut(i32, (f64, f64)), { self.on_mouse_wheel = Some(self.common.add_event("wheel", move |event: WheelEvent| { event.prevent_default(); - if let Some(delta) = event::mouse_scroll_delta(&event) { - handler(0, delta, event::mouse_modifiers(&event)); + if let delta = utils::mouse_scroll_delta(&event) { + handler(0, delta); } })); } diff --git a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs index fcfb88b991..847275eb7d 100644 --- a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs +++ b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs @@ -1,4 +1,4 @@ -use super::event; +use super::utils; use super::EventListenerHandle; use crate::dpi::PhysicalPosition; use crate::event::{ModifiersState, MouseButton}; @@ -14,7 +14,7 @@ pub(super) struct MouseHandler { on_mouse_move: Option>, on_mouse_press: Option>, on_mouse_release: Option>, - on_mouse_leave_handler: Rc>>>, + on_mouse_leave_handler: Rc>>>, mouse_capture_state: Rc>, } @@ -39,7 +39,7 @@ impl MouseHandler { } pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(), { *self.on_mouse_leave_handler.borrow_mut() = Some(Box::new(handler)); let on_mouse_leave_handler = self.on_mouse_leave_handler.clone(); @@ -50,7 +50,7 @@ impl MouseHandler { // released, therefore we don't send cursor leave events. if *mouse_capture_state.borrow() != MouseCaptureState::Captured { if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { - handler(0); + handler(); } } })); @@ -58,21 +58,21 @@ impl MouseHandler { pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(), { let mouse_capture_state = self.mouse_capture_state.clone(); self.on_mouse_enter = Some(canvas_common.add_event("mouseover", move |_: MouseEvent| { // We don't send cursor leave events when the mouse is being // captured, therefore we do the same with cursor enter events. if *mouse_capture_state.borrow() != MouseCaptureState::Captured { - handler(0); + handler(); } })); } pub fn on_mouse_release(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32, MouseButton, ModifiersState), + F: 'static + FnMut(i32, MouseButton), { let on_mouse_leave_handler = self.on_mouse_leave_handler.clone(); let mouse_capture_state = self.mouse_capture_state.clone(); @@ -96,11 +96,7 @@ impl MouseHandler { MouseCaptureState::Captured => {} } event.stop_propagation(); - handler( - 0, - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); + handler(0, utils::mouse_button(&event)); if event .target() .map_or(false, |target| target != EventTarget::from(canvas)) @@ -109,7 +105,7 @@ impl MouseHandler { // cursor is being captured, we instead send it after // the capture has been released. if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { - handler(0); + handler(); } } if event.buttons() == 0 { @@ -150,9 +146,9 @@ impl MouseHandler { event.stop_propagation(); handler( 0, - event::mouse_position(&event).to_physical(super::super::scale_factor()), - event::mouse_button(&event), - event::mouse_modifiers(&event), + utils::mouse_position(&event).to_physical(super::super::scale_factor()), + utils::mouse_button(&event), + utils::mouse_modifiers(&event), ); }, )); @@ -160,7 +156,7 @@ impl MouseHandler { pub fn on_cursor_move(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + F: 'static + FnMut(PhysicalPosition, ModifiersState), { let mouse_capture_state = self.mouse_capture_state.clone(); let canvas = canvas_common.raw.clone(); @@ -184,16 +180,15 @@ impl MouseHandler { event.stop_propagation(); } let mouse_pos = if is_over_canvas { - event::mouse_position(&event) + utils::mouse_position(&event) } else { // Since the mouse is not on the canvas, we cannot // use `offsetX`/`offsetY`. - event::mouse_position_by_client(&event, &canvas) + utils::mouse_position_by_client(&event, &canvas) }; handler( - 0, mouse_pos.to_physical(super::super::scale_factor()), - event::mouse_modifiers(&event), + utils::mouse_modifiers(&event), ); } } diff --git a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs index 4764e03c7d..a31b552b7e 100644 --- a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs +++ b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs @@ -1,4 +1,4 @@ -use super::event; +use super::utils; use super::EventListenerHandle; use crate::dpi::PhysicalPosition; use crate::event::{ModifiersState, MouseButton}; @@ -26,40 +26,36 @@ impl PointerHandler { pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(), { self.on_cursor_leave = Some(canvas_common.add_event( "pointerout", move |event: PointerEvent| { - handler(event.pointer_id()); + handler(); }, )); } pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(), { self.on_cursor_enter = Some(canvas_common.add_event( "pointerover", move |event: PointerEvent| { - handler(event.pointer_id()); + handler(); }, )); } pub fn on_mouse_release(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32, MouseButton, ModifiersState), + F: 'static + FnMut(i32, MouseButton), { self.on_pointer_release = Some(canvas_common.add_user_event( "pointerup", move |event: PointerEvent| { - handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); + handler(event.pointer_id(), utils::mouse_button(&event)); }, )); } @@ -74,9 +70,9 @@ impl PointerHandler { move |event: PointerEvent| { handler( event.pointer_id(), - event::mouse_position(&event).to_physical(super::super::scale_factor()), - event::mouse_button(&event), - event::mouse_modifiers(&event), + utils::mouse_position(&event).to_physical(super::super::scale_factor()), + utils::mouse_button(&event), + utils::mouse_modifiers(&event), ); canvas .set_pointer_capture(event.pointer_id()) @@ -87,15 +83,14 @@ impl PointerHandler { pub fn on_cursor_move(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32, PhysicalPosition, ModifiersState), + F: 'static + FnMut(PhysicalPosition, ModifiersState), { self.on_cursor_move = Some(canvas_common.add_event( "pointermove", move |event: PointerEvent| { handler( - event.pointer_id(), - event::mouse_position(&event).to_physical(super::super::scale_factor()), - event::mouse_modifiers(&event), + utils::mouse_position(&event).to_physical(super::super::scale_factor()), + utils::mouse_modifiers(&event), ); }, )); diff --git a/src/platform_impl/web/web_sys/gamepad.rs b/src/platform_impl/web/web_sys/gamepad.rs new file mode 100644 index 0000000000..922bd645e0 --- /dev/null +++ b/src/platform_impl/web/web_sys/gamepad.rs @@ -0,0 +1,75 @@ +use super::utils; +use crate::platform_impl::platform::device; +use std::cmp::PartialEq; + +#[derive(Debug)] +pub struct Gamepad { + pub(crate) index: i32, + pub(crate) raw: web_sys::Gamepad, + pub(crate) mapping: device::gamepad::Mapping, +} + +impl Gamepad { + pub fn new(raw: web_sys::Gamepad) -> Self { + let mapping = utils::create_mapping(&raw); + + Self { + index: raw.index() as i32, + raw, + mapping, + } + } + + // An integer that is auto-incremented to be unique for each device + // currently connected to the system. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/index + pub fn index(&self) -> i32 { + self.raw.index() as i32 + } + + // A string containing some information about the controller. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id + pub fn id(&self) -> String { + self.raw.id() + } + + // A boolean indicating whether the gamepad is still connected to the system. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/connected + pub fn connected(&self) -> bool { + self.raw.connected() + } + + // An array containing GamepadHapticActuator objects, + // each of which represents haptic feedback hardware available on the controller. + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/hapticActuators + pub fn vibrate(&self, value: f64, duration: f64) { + for actuator in self.raw.haptic_actuators().values() { + actuator.ok().and_then(|a| { + let actuator: web_sys::GamepadHapticActuator = a.into(); + match actuator.type_() { + web_sys::GamepadHapticActuatorType::Vibration => { + actuator.pulse(value, duration).ok() + } + _ => None, + } + }); + } + } +} + +impl Clone for Gamepad { + fn clone(&self) -> Self { + Self { + index: self.index, + raw: self.raw.clone(), + mapping: self.mapping.clone(), + } + } +} + +impl PartialEq for Gamepad { + #[inline(always)] + fn eq(&self, othr: &Self) -> bool { + self.raw.index() == othr.raw.index() + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 8057f7756a..9b55642935 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -1,9 +1,11 @@ mod canvas; -mod event; mod event_handle; +pub mod gamepad; mod media_query_handle; mod scaling; mod timeout; +mod utils; +pub mod window; pub use self::canvas::Canvas; pub use self::scaling::ScaleChangeDetector; @@ -115,3 +117,21 @@ pub fn is_fullscreen(canvas: &HtmlCanvasElement) -> bool { } pub type RawCanvasType = HtmlCanvasElement; + +pub fn get_gamepads() -> impl Iterator { + let mut gamepads: Vec = Vec::new(); + let web_gamepads = web_sys::window() + .unwrap() + .navigator() + .get_gamepads() + .ok() + .unwrap(); + for index in 0..web_gamepads.length() { + let jsvalue = web_gamepads.get(index); + if !jsvalue.is_null() { + let gamepad: web_sys::Gamepad = jsvalue.into(); + gamepads.push(gamepad::Gamepad::new(gamepad)); + } + } + gamepads.into_iter() +} diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/utils.rs similarity index 84% rename from src/platform_impl/web/web_sys/event.rs rename to src/platform_impl/web/web_sys/utils.rs index 3d2b77b127..e39c3aed30 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/utils.rs @@ -1,8 +1,12 @@ use crate::dpi::LogicalPosition; -use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; +use crate::event::{ModifiersState, MouseButton, ScanCode, VirtualKeyCode}; +use crate::platform_impl::platform; use std::convert::TryInto; -use web_sys::{HtmlCanvasElement, KeyboardEvent, MouseEvent, WheelEvent}; +use web_sys::{ + Gamepad, GamepadButton, GamepadMappingType, HtmlCanvasElement, KeyboardEvent, MouseEvent, + WheelEvent, +}; pub fn mouse_button(event: &MouseEvent) -> MouseButton { match event.button() { @@ -40,18 +44,11 @@ pub fn mouse_position_by_client( } } -pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { +pub fn mouse_scroll_delta(event: &WheelEvent) -> (f64, f64) { let x = event.delta_x(); let y = -event.delta_y(); - match event.delta_mode() { - WheelEvent::DOM_DELTA_LINE => Some(MouseScrollDelta::LineDelta(x as f32, y as f32)), - WheelEvent::DOM_DELTA_PIXEL => { - let delta = LogicalPosition::new(x, y).to_physical(super::scale_factor()); - Some(MouseScrollDelta::PixelDelta(delta)) - } - _ => None, - } + (x, y) } pub fn scan_code(event: &KeyboardEvent) -> ScanCode { @@ -239,3 +236,44 @@ pub fn codepoint(event: &KeyboardEvent) -> char { // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key event.key().chars().next().unwrap() } + +pub fn create_mapping(raw: &Gamepad) -> platform::device::gamepad::Mapping { + match raw.mapping() { + GamepadMappingType::Standard => { + let mut buttons = [false; 16]; + let mut axes = [0.0; 6]; + + let gbuttons = raw.buttons(); + for index in 0..buttons.len() { + let button: GamepadButton = gbuttons.get(index as u32).into(); + buttons[index] = button.pressed(); + } + + let gaxes = raw.axes(); + for index in 0..axes.len() { + let axe: f64 = gaxes.get(index as u32).as_f64().unwrap_or(0.0); + axes[index] = axe; + } + + platform::device::gamepad::Mapping::Standard { buttons, axes } + } + _ => { + let mut buttons: Vec = Vec::new(); + let mut axes: Vec = Vec::new(); + + let gbuttons = raw.buttons(); + for index in 0..gbuttons.length() { + let button: GamepadButton = gbuttons.get(index as u32).into(); + buttons.push(button.pressed()); + } + + let gaxes = raw.axes(); + for index in 0..gaxes.length() { + let axe: f64 = gaxes.get(index as u32).as_f64().unwrap_or(0.0); + axes.push(axe); + } + + platform::device::gamepad::Mapping::NoMapping { buttons, axes } + } + } +} diff --git a/src/platform_impl/web/web_sys/window.rs b/src/platform_impl/web/web_sys/window.rs new file mode 100644 index 0000000000..9bb4cee450 --- /dev/null +++ b/src/platform_impl/web/web_sys/window.rs @@ -0,0 +1,88 @@ +use super::gamepad; +use crate::error::OsError as RootOE; +use crate::platform_impl::OsError; +use std::{cell::RefCell, rc::Rc}; +use wasm_bindgen::{closure::Closure, JsCast}; +use web_sys::GamepadEvent; + +#[derive(Debug)] +pub struct Shared(pub Rc>); + +#[derive(Debug)] +pub struct Window { + raw: web_sys::Window, + on_gamepad_connected: Option>, + on_gamepad_disconnected: Option>, +} + +impl Shared { + pub fn create() -> Result { + let global = Window::create()?; + Ok(Shared(Rc::new(RefCell::new(global)))) + } +} + +impl Clone for Shared { + fn clone(&self) -> Self { + Shared(self.0.clone()) + } +} + +impl Window { + pub fn create() -> Result { + let raw = + web_sys::window().ok_or(os_error!(OsError("Failed to obtain window".to_owned())))?; + + Ok(Window { + raw, + on_gamepad_connected: None, + on_gamepad_disconnected: None, + }) + } + + pub fn on_gamepad_connected(&mut self, mut handler: F) + where + F: 'static + FnMut(gamepad::Gamepad), + { + self.on_gamepad_connected = Some(self.add_event( + "gamepadconnected", + move |event: GamepadEvent| { + let gamepad = event + .gamepad() + .expect("[gamepadconnected] expected gamepad"); + handler(gamepad::Gamepad::new(gamepad)); + }, + )) + } + + pub fn on_gamepad_disconnected(&mut self, mut handler: F) + where + F: 'static + FnMut(gamepad::Gamepad), + { + self.on_gamepad_disconnected = Some(self.add_event( + "gamepaddisconnected", + move |event: GamepadEvent| { + let gamepad = event + .gamepad() + .expect("[gamepaddisconnected] expected gamepad"); + handler(gamepad::Gamepad::new(gamepad)); + }, + )) + } + + fn add_event(&self, event_name: &str, mut handler: F) -> Closure + where + E: 'static + AsRef + wasm_bindgen::convert::FromWasmAbi, + F: 'static + FnMut(E), + { + let closure = Closure::wrap(Box::new(move |event: E| { + handler(event); + }) as Box); + + self.raw + .add_event_listener_with_callback(event_name, &closure.as_ref().unchecked_ref()) + .expect("Failed to add event listener with callback"); + + closure + } +} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 463705bfea..449500c9e9 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -40,6 +40,7 @@ impl Window { let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); + target.register_global_events()?; target.register(&mut canvas, id); let runner = target.runner.clone();