From 93c36ccf7851b008e062bde6ab0bb39612f6e1a7 Mon Sep 17 00:00:00 2001 From: Kalmar Robert Date: Thu, 27 Jun 2019 11:34:38 +0200 Subject: [PATCH 01/61] Handle WM_POINTER* events in favor of WM_TOUCH Fixes #975. --- src/platform_impl/windows/event_loop.rs | 58 +++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index b53d3add..d5d1e024 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1457,6 +1457,64 @@ unsafe extern "system" fn public_window_callback( 0 } + winuser::WM_POINTERDOWN | winuser::WM_POINTERUPDATE | winuser::WM_POINTERUP => { + let pointer_id = LOWORD(wparam as DWORD) as UINT; + let mut entries_count = 0 as UINT; + let mut pointers_count = 0 as UINT; + if winuser::GetPointerFrameInfoHistory( + pointer_id, + &mut entries_count as *mut _, + &mut pointers_count as *mut _, + std::ptr::null_mut(), + ) == 0 + { + return 0; + } + + let pointer_info_count = (entries_count * pointers_count) as usize; + let mut pointer_infos = Vec::with_capacity(pointer_info_count); + pointer_infos.set_len(pointer_info_count); + if winuser::GetPointerFrameInfoHistory( + pointer_id, + &mut entries_count as *mut _, + &mut pointers_count as *mut _, + pointer_infos.as_mut_ptr(), + ) == 0 + { + 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 + for pointer_info in pointer_infos.iter().rev() { + let x = pointer_info.ptPixelLocation.x as f64; + let y = pointer_info.ptPixelLocation.y as f64; + let location = LogicalPosition::from_physical((x, y), dpi_factor); + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::Touch(Touch { + phase: if pointer_info.pointerFlags & winuser::POINTER_FLAG_DOWN != 0 { + TouchPhase::Started + } else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UP != 0 { + TouchPhase::Ended + } else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UPDATE != 0 { + TouchPhase::Moved + } else { + continue; + }, + location, + id: pointer_info.pointerId as u64, + device_id: DEVICE_ID, + }), + }); + } + + winuser::SkipPointerFrameMessages(pointer_id); + 0 + } + winuser::WM_SETFOCUS => { use crate::event::WindowEvent::Focused; subclass_input.send_event(Event::WindowEvent { From 026b331ba54e1a49909242d72c5d78ed0d24b255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalm=C3=A1r=20R=C3=B3bert?= Date: Fri, 5 Jul 2019 18:37:25 +0200 Subject: [PATCH 02/61] Handle WM_POINTER* events in favor of WM_TOUCH Fixes #975 --- src/platform_impl/windows/dpi.rs | 30 +----- src/platform_impl/windows/event_loop.rs | 125 ++++++++++++++---------- src/platform_impl/windows/mod.rs | 3 +- src/platform_impl/windows/util.rs | 38 ++++++- 4 files changed, 114 insertions(+), 82 deletions(-) diff --git a/src/platform_impl/windows/dpi.rs b/src/platform_impl/windows/dpi.rs index 12186182..548c2608 100644 --- a/src/platform_impl/windows/dpi.rs +++ b/src/platform_impl/windows/dpi.rs @@ -9,13 +9,12 @@ use winapi::{ winerror::S_OK, }, um::{ - libloaderapi::{GetProcAddress, LoadLibraryA}, shellscalingapi::{ MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS, PROCESS_PER_MONITOR_DPI_AWARE, }, wingdi::{GetDeviceCaps, LOGPIXELSX}, - winnt::{HRESULT, LPCSTR}, + winnt::HRESULT, winuser::{self, MONITOR_DEFAULTTONEAREST}, }, }; @@ -35,33 +34,6 @@ type GetDpiForMonitor = unsafe extern "system" fn( ) -> HRESULT; type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL; -// Helper function to dynamically load function pointer. -// `library` and `function` must be zero-terminated. -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) => { - get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')) - .map(|f| unsafe { mem::transmute::<*const _, $func>(f) }) - }; -} - lazy_static! { static ref GET_DPI_FOR_WINDOW: Option = get_function!("user32.dll", GetDpiForWindow); diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index d5d1e024..0204747e 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1,3 +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. @@ -62,6 +63,22 @@ use crate::{ window::WindowId as RootWindowId, }; +type GetPointerFrameInfoHistory = unsafe extern "system" fn( + pointerId: UINT, + entriesCount: *mut UINT, + pointerCount: *mut UINT, + pointerInfo: *mut winuser::POINTER_INFO, +) -> BOOL; + +type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: UINT) -> BOOL; + +lazy_static! { + static ref GET_POINTER_FRAME_INFO_HISTORY: Option = + get_function!("user32.dll", GetPointerFrameInfoHistory); + static ref SKIP_POINTER_FRAME_MESSAGES: Option = + get_function!("user32.dll", SkipPointerFrameMessages); +} + pub(crate) struct SubclassInput { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, @@ -1458,60 +1475,66 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_POINTERDOWN | winuser::WM_POINTERUPDATE | winuser::WM_POINTERUP => { - let pointer_id = LOWORD(wparam as DWORD) as UINT; - let mut entries_count = 0 as UINT; - let mut pointers_count = 0 as UINT; - if winuser::GetPointerFrameInfoHistory( - pointer_id, - &mut entries_count as *mut _, - &mut pointers_count as *mut _, - std::ptr::null_mut(), - ) == 0 - { - return 0; - } + if let (Some(GetPointerFrameInfoHistory), Some(SkipPointerFrameMessages)) = ( + *GET_POINTER_FRAME_INFO_HISTORY, + *SKIP_POINTER_FRAME_MESSAGES, + ) { + let pointer_id = LOWORD(wparam as DWORD) as UINT; + let mut entries_count = 0 as UINT; + let mut pointers_count = 0 as UINT; + if GetPointerFrameInfoHistory( + pointer_id, + &mut entries_count as *mut _, + &mut pointers_count as *mut _, + std::ptr::null_mut(), + ) == 0 + { + return 0; + } - let pointer_info_count = (entries_count * pointers_count) as usize; - let mut pointer_infos = Vec::with_capacity(pointer_info_count); - pointer_infos.set_len(pointer_info_count); - if winuser::GetPointerFrameInfoHistory( - pointer_id, - &mut entries_count as *mut _, - &mut pointers_count as *mut _, - pointer_infos.as_mut_ptr(), - ) == 0 - { - return 0; - } + let pointer_info_count = (entries_count * pointers_count) as usize; + let mut pointer_infos = Vec::with_capacity(pointer_info_count); + pointer_infos.set_len(pointer_info_count); + if GetPointerFrameInfoHistory( + pointer_id, + &mut entries_count as *mut _, + &mut pointers_count as *mut _, + pointer_infos.as_mut_ptr(), + ) == 0 + { + 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 - for pointer_info in pointer_infos.iter().rev() { - let x = pointer_info.ptPixelLocation.x as f64; - let y = pointer_info.ptPixelLocation.y as f64; - let location = LogicalPosition::from_physical((x, y), dpi_factor); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::Touch(Touch { - phase: if pointer_info.pointerFlags & winuser::POINTER_FLAG_DOWN != 0 { - TouchPhase::Started - } else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UP != 0 { - TouchPhase::Ended - } else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UPDATE != 0 { - TouchPhase::Moved - } else { - continue; - }, - location, - id: pointer_info.pointerId as u64, - device_id: DEVICE_ID, - }), - }); - } + 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 + for pointer_info in pointer_infos.iter().rev() { + let x = pointer_info.ptPixelLocation.x as f64; + let y = pointer_info.ptPixelLocation.y as f64; + let location = LogicalPosition::from_physical((x, y), dpi_factor); + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::Touch(Touch { + phase: if pointer_info.pointerFlags & winuser::POINTER_FLAG_DOWN != 0 { + TouchPhase::Started + } else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UP != 0 { + TouchPhase::Ended + } else if pointer_info.pointerFlags & winuser::POINTER_FLAG_UPDATE != 0 + { + TouchPhase::Moved + } else { + continue; + }, + location, + id: pointer_info.pointerId as u64, + device_id: DEVICE_ID, + }), + }); + } - winuser::SkipPointerFrameMessages(pointer_id); + SkipPointerFrameMessages(pointer_id); + } 0 } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 58d86faf..6216fa29 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -67,6 +67,8 @@ impl WindowId { } } +#[macro_use] +mod util; mod dpi; mod drop_handler; mod event; @@ -74,6 +76,5 @@ mod event_loop; mod icon; mod monitor; mod raw_input; -mod util; mod window; mod window_state; diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 28513d30..4b82cefa 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -1,6 +1,7 @@ use std::{ io, mem, ops::BitAnd, + os::raw::c_void, ptr, slice, sync::atomic::{AtomicBool, Ordering}, }; @@ -12,9 +13,44 @@ use winapi::{ minwindef::{BOOL, DWORD}, windef::{HWND, POINT, RECT}, }, - um::{winbase::lstrlenW, winuser}, + um::{ + libloaderapi::{GetProcAddress, LoadLibraryA}, + winbase::lstrlenW, + winnt::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, From 1ea29b4de069e3f1f439449ad2c14c25ce177cea Mon Sep 17 00:00:00 2001 From: Riku Salminen Date: Tue, 9 Jul 2019 13:02:02 +0300 Subject: [PATCH 03/61] x11: NewEvents(StartCause::Init) callback at start Before starting the event loop, invoke callback with NewEvents(StartCause::Init). --- src/platform_impl/linux/x11/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index ba61e719..329d5f8e 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -249,6 +249,12 @@ impl EventLoop { let mut control_flow = ControlFlow::default(); let wt = get_xtarget(&self.target); + callback( + crate::event::Event::NewEvents(crate::event::StartCause::Init), + &self.target, + &mut control_flow, + ); + loop { // Empty the event buffer { From 17b831051748256b592ffeeda8b828861a4c87e1 Mon Sep 17 00:00:00 2001 From: Osspial Date: Wed, 10 Jul 2019 18:54:34 -0400 Subject: [PATCH 04/61] Update Windows Multitouch in FEATURES.md (#1039) It seems we were already implementing multitouch on Windows, and the question mark was inaccurate. --- FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FEATURES.md b/FEATURES.md index d71b00e4..a89c0689 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -174,7 +174,7 @@ Legend: |Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ | |Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | -|Multitouch |❓ |❌ |✔️ |✔️ |❓ |❌ |❌ | +|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |❌ |❌ | |Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | |Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❌ | From 7daf1468015586f7fedcf2a3790615570a6572f0 Mon Sep 17 00:00:00 2001 From: Murarth Date: Thu, 11 Jul 2019 09:34:32 -0700 Subject: [PATCH 05/61] Replace `std::mem::uninitialized` with `MaybeUninit` (#1027) * Replace `std::mem::uninitialized` with `MaybeUninit` * Avoid undefined behavior when using `MaybeUninit` * Restore unused `PointerState` fields as internally public * Zero-initialize some struct values in Xlib FFI calls * Reform usage of `MaybeUninit` in Xlib FFI * Prefer safe zero-initialization using `Default`, when possible * Zero-initialize integers and floats using `0` or `0.0` * Use `MaybeUninit::uninit` for large byte buffers and union types * Use `MaybeUninit::uninit` when the resulting value is ignored --- src/platform_impl/linux/mod.rs | 10 +- src/platform_impl/linux/x11/mod.rs | 50 ++++---- .../linux/x11/util/client_msg.rs | 35 ++++-- src/platform_impl/linux/x11/util/geometry.rs | 32 ++--- src/platform_impl/linux/x11/util/hint.rs | 4 +- src/platform_impl/linux/x11/util/input.rs | 112 +++++++++++------- src/platform_impl/linux/x11/util/mod.rs | 7 +- .../linux/x11/util/window_property.rs | 11 +- src/platform_impl/linux/x11/window.rs | 51 +++++--- 9 files changed, 186 insertions(+), 126 deletions(-) diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 73d9f643..258f4280 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -1,6 +1,6 @@ #![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, os::raw::*, sync::Arc}; +use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc}; use parking_lot::Mutex; use smithay_client_toolkit::reexports::client::ConnectError; @@ -410,14 +410,16 @@ unsafe extern "C" fn x_error_callback( ) -> c_int { let xconn_lock = X11_BACKEND.lock(); if let Ok(ref xconn) = *xconn_lock { - let mut buf: [c_char; 1024] = mem::uninitialized(); + // `assume_init` is safe here because the array consists of `MaybeUninit` values, + // which do not require initialization. + let mut buf: [MaybeUninit; 1024] = MaybeUninit::uninit().assume_init(); (xconn.xlib.XGetErrorText)( display, (*event).error_code as c_int, - buf.as_mut_ptr(), + buf.as_mut_ptr() as *mut c_char, buf.len() as c_int, ); - let description = CStr::from_ptr(buf.as_ptr()).to_string_lossy(); + let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy(); let error = XError { description: description.into_owned(), diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 329d5f8e..bbd962c5 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -20,7 +20,7 @@ use std::{ cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::CStr, - mem, + mem::{self, MaybeUninit}, ops::Deref, os::raw::*, rc::Rc, @@ -100,22 +100,21 @@ impl EventLoop { .expect("Failed to query XRandR extension"); let xi2ext = unsafe { - let mut result = XExtension { - opcode: mem::uninitialized(), - first_event_id: mem::uninitialized(), - first_error_id: mem::uninitialized(), - }; + let mut ext = XExtension::default(); + let res = (xconn.xlib.XQueryExtension)( xconn.display, b"XInputExtension\0".as_ptr() as *const c_char, - &mut result.opcode as *mut c_int, - &mut result.first_event_id as *mut c_int, - &mut result.first_error_id as *mut c_int, + &mut ext.opcode, + &mut ext.first_event_id, + &mut ext.first_error_id, ); + if res == ffi::False { panic!("X server missing XInput extension"); } - result + + ext }; unsafe { @@ -204,8 +203,9 @@ impl EventLoop { move |evt, &mut ()| { if evt.readiness.is_readable() { // process all pending events - let mut xev = unsafe { mem::uninitialized() }; - while unsafe { processor.poll_one_event(&mut xev) } { + 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); } } @@ -401,19 +401,19 @@ struct DeviceInfo<'a> { impl<'a> DeviceInfo<'a> { fn get(xconn: &'a XConnection, device: c_int) -> Option { unsafe { - let mut count = mem::uninitialized(); + let mut count = 0; let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count); - xconn.check_errors().ok().and_then(|_| { - if info.is_null() || count == 0 { - None - } else { - Some(DeviceInfo { - xconn, - info, - count: count as usize, - }) - } - }) + xconn.check_errors().ok()?; + + if info.is_null() || count == 0 { + None + } else { + Some(DeviceInfo { + xconn, + info, + count: count as usize, + }) + } } } } @@ -518,7 +518,7 @@ impl<'a> Drop for GenericEventCookie<'a> { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Default, Copy, Clone)] struct XExtension { opcode: c_int, first_event_id: c_int, diff --git a/src/platform_impl/linux/x11/util/client_msg.rs b/src/platform_impl/linux/x11/util/client_msg.rs index 4c72665a..3fafcdf9 100644 --- a/src/platform_impl/linux/x11/util/client_msg.rs +++ b/src/platform_impl/linux/x11/util/client_msg.rs @@ -30,13 +30,17 @@ impl XConnection { event_mask: Option, data: ClientMsgPayload, ) -> Flusher<'_> { - let mut event: ffi::XClientMessageEvent = unsafe { mem::uninitialized() }; - event.type_ = ffi::ClientMessage; - event.display = self.display; - event.window = window; - event.message_type = message_type; - event.format = c_long::FORMAT as c_int; - event.data = unsafe { mem::transmute(data) }; + let event = ffi::XClientMessageEvent { + type_: ffi::ClientMessage, + display: self.display, + window, + message_type, + format: c_long::FORMAT as c_int, + data: unsafe { mem::transmute(data) }, + // These fields are ignored by `XSendEvent` + serial: 0, + send_event: 0, + }; self.send_event(target_window, event_mask, event) } @@ -54,12 +58,17 @@ impl XConnection { 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 = unsafe { mem::uninitialized() }; - event.type_ = ffi::ClientMessage; - event.display = self.display; - event.window = window; - event.message_type = message_type; - event.format = format as c_int; + 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); diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs index e66606af..6b59d13a 100644 --- a/src/platform_impl/linux/x11/util/geometry.rs +++ b/src/platform_impl/linux/x11/util/geometry.rs @@ -41,14 +41,14 @@ impl AaRect { } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct TranslatedCoords { pub x_rel_root: c_int, pub y_rel_root: c_int, pub child: ffi::Window, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Geometry { pub root: ffi::Window, // If you want positions relative to the root window, use translate_coords. @@ -183,7 +183,8 @@ impl XConnection { window: ffi::Window, root: ffi::Window, ) -> Result { - let mut translated_coords: TranslatedCoords = unsafe { mem::uninitialized() }; + let mut coords = TranslatedCoords::default(); + unsafe { (self.xlib.XTranslateCoordinates)( self.display, @@ -191,18 +192,20 @@ impl XConnection { root, 0, 0, - &mut translated_coords.x_rel_root, - &mut translated_coords.y_rel_root, - &mut translated_coords.child, + &mut coords.x_rel_root, + &mut coords.y_rel_root, + &mut coords.child, ); } - //println!("XTranslateCoordinates coords:{:?}", translated_coords); - self.check_errors().map(|_| translated_coords) + + self.check_errors()?; + Ok(coords) } // This is adequate for inner_size pub fn get_geometry(&self, window: ffi::Window) -> Result { - let mut geometry: Geometry = unsafe { mem::uninitialized() }; + let mut geometry = Geometry::default(); + let _status = unsafe { (self.xlib.XGetGeometry)( self.display, @@ -216,8 +219,9 @@ impl XConnection { &mut geometry.depth, ) }; - //println!("XGetGeometry geo:{:?}", geometry); - self.check_errors().map(|_| geometry) + + self.check_errors()?; + Ok(geometry) } fn get_frame_extents(&self, window: ffi::Window) -> Option { @@ -264,10 +268,10 @@ impl XConnection { fn get_parent_window(&self, window: ffi::Window) -> Result { let parent = unsafe { - let mut root: ffi::Window = mem::uninitialized(); - let mut parent: ffi::Window = mem::uninitialized(); + let mut root = 0; + let mut parent = 0; let mut children: *mut ffi::Window = ptr::null_mut(); - let mut nchildren: c_uint = mem::uninitialized(); + let mut nchildren = 0; // What's filled into `parent` if `window` is the root window? let _status = (self.xlib.XQueryTree)( diff --git a/src/platform_impl/linux/x11/util/hint.rs b/src/platform_impl/linux/x11/util/hint.rs index a71d75e0..28086460 100644 --- a/src/platform_impl/linux/x11/util/hint.rs +++ b/src/platform_impl/linux/x11/util/hint.rs @@ -317,13 +317,13 @@ impl XConnection { pub fn get_normal_hints(&self, window: ffi::Window) -> Result, XError> { let size_hints = self.alloc_size_hints(); - let mut supplied_by_user: c_long = unsafe { mem::uninitialized() }; + let mut supplied_by_user = MaybeUninit::uninit(); unsafe { (self.xlib.XGetWMNormalHints)( self.display, window, size_hints.ptr, - &mut supplied_by_user, + supplied_by_user.as_mut_ptr(), ); } self.check_errors().map(|_| NormalHints { size_hints }) diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index 8f2e9833..1f02090d 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -1,4 +1,4 @@ -use std::str; +use std::{slice, str}; use super::*; use crate::event::ModifiersState; @@ -23,18 +23,19 @@ impl From for ModifiersState { } } +// NOTE: Some of these fields are not used, but may be of use in the future. pub struct PointerState<'a> { xconn: &'a XConnection, - root: ffi::Window, - child: ffi::Window, + pub root: ffi::Window, + pub child: ffi::Window, pub root_x: c_double, pub root_y: c_double, - win_x: c_double, - win_y: c_double, + pub win_x: c_double, + pub win_y: c_double, buttons: ffi::XIButtonState, modifiers: ffi::XIModifierState, - group: ffi::XIGroupState, - relative_to_window: bool, + pub group: ffi::XIGroupState, + pub relative_to_window: bool, } impl<'a> PointerState<'a> { @@ -93,29 +94,46 @@ impl XConnection { device_id: c_int, ) -> Result, XError> { unsafe { - let mut pointer_state: PointerState<'_> = mem::uninitialized(); - pointer_state.xconn = self; - pointer_state.relative_to_window = (self.xinput2.XIQueryPointer)( + let mut root = 0; + let mut child = 0; + let mut root_x = 0.0; + let mut root_y = 0.0; + let mut win_x = 0.0; + let mut win_y = 0.0; + let mut buttons = Default::default(); + let mut modifiers = Default::default(); + let mut group = Default::default(); + + let relative_to_window = (self.xinput2.XIQueryPointer)( self.display, device_id, window, - &mut pointer_state.root, - &mut pointer_state.child, - &mut pointer_state.root_x, - &mut pointer_state.root_y, - &mut pointer_state.win_x, - &mut pointer_state.win_y, - &mut pointer_state.buttons, - &mut pointer_state.modifiers, - &mut pointer_state.group, + &mut root, + &mut child, + &mut root_x, + &mut root_y, + &mut win_x, + &mut win_y, + &mut buttons, + &mut modifiers, + &mut group, ) == ffi::True; - if let Err(err) = self.check_errors() { - // Running the destrutor would be bad news for us... - mem::forget(pointer_state); - Err(err) - } else { - Ok(pointer_state) - } + + self.check_errors()?; + + Ok(PointerState { + xconn: self, + root, + child, + root_x, + root_y, + win_x, + win_y, + buttons, + modifiers, + group, + relative_to_window, + }) } } @@ -123,7 +141,8 @@ impl XConnection { &self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent, - buffer: &mut [u8], + buffer: *mut u8, + size: usize, ) -> (ffi::KeySym, ffi::Status, c_int) { let mut keysym: ffi::KeySym = 0; let mut status: ffi::Status = 0; @@ -131,8 +150,8 @@ impl XConnection { (self.xlib.Xutf8LookupString)( ic, key_event, - buffer.as_mut_ptr() as *mut c_char, - buffer.len() as c_int, + buffer as *mut c_char, + size as c_int, &mut keysym, &mut status, ) @@ -141,21 +160,28 @@ impl XConnection { } pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String { - let mut buffer: [u8; TEXT_BUFFER_SIZE] = unsafe { mem::uninitialized() }; - let (_, status, count) = self.lookup_utf8_inner(ic, key_event, &mut buffer); - // The buffer overflowed, so we'll make a new one on the heap. - if status == ffi::XBufferOverflow { - let mut buffer = Vec::with_capacity(count as usize); - unsafe { buffer.set_len(count as usize) }; - let (_, _, new_count) = self.lookup_utf8_inner(ic, key_event, &mut buffer); + // `assume_init` is safe here because the array consists of `MaybeUninit` values, + // which do not require initialization. + let mut buffer: [MaybeUninit; TEXT_BUFFER_SIZE] = + unsafe { MaybeUninit::uninit().assume_init() }; + // If the buffer overflows, we'll make a new one on the heap. + let mut vec; + + let (_, status, count) = + self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len()); + + let bytes = if status == ffi::XBufferOverflow { + vec = Vec::with_capacity(count as usize); + let (_, _, new_count) = + self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity()); debug_assert_eq!(count, new_count); - str::from_utf8(&buffer[..count as usize]) - .unwrap_or("") - .to_string() + + unsafe { vec.set_len(count as usize) }; + &vec[..count as usize] } else { - str::from_utf8(&buffer[..count as usize]) - .unwrap_or("") - .to_string() - } + unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) } + }; + + str::from_utf8(bytes).unwrap_or("").to_string() } } diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 920205be..58e0c332 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -18,7 +18,12 @@ pub use self::{ randr::*, window_property::*, wm::*, }; -use std::{mem, ops::BitAnd, os::raw::*, ptr}; +use std::{ + mem::{self, MaybeUninit}, + ops::BitAnd, + os::raw::*, + ptr, +}; use super::{ffi, XConnection, XError}; diff --git a/src/platform_impl/linux/x11/util/window_property.rs b/src/platform_impl/linux/x11/util/window_property.rs index d719ea70..0b9e7a71 100644 --- a/src/platform_impl/linux/x11/util/window_property.rs +++ b/src/platform_impl/linux/x11/util/window_property.rs @@ -43,13 +43,14 @@ impl XConnection { let mut offset = 0; let mut done = false; + let mut actual_type = 0; + let mut actual_format = 0; + let mut quantity_returned = 0; + let mut bytes_after = 0; + let mut buf: *mut c_uchar = ptr::null_mut(); + while !done { unsafe { - let mut actual_type: ffi::Atom = mem::uninitialized(); - let mut actual_format: c_int = mem::uninitialized(); - let mut quantity_returned: c_ulong = mem::uninitialized(); - let mut bytes_after: c_ulong = mem::uninitialized(); - let mut buf: *mut c_uchar = ptr::null_mut(); (self.xlib.XGetWindowProperty)( self.display, window, diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 2211c29b..33a16e02 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1,4 +1,14 @@ -use std::{cmp, collections::HashSet, env, ffi::CString, mem, os::raw::*, path::Path, sync::Arc}; +use std::{ + cmp, + collections::HashSet, + env, + ffi::CString, + mem::{self, MaybeUninit}, + os::raw::*, + path::Path, + ptr, slice, + sync::Arc, +}; use libc; use parking_lot::Mutex; @@ -410,11 +420,11 @@ impl UnownedWindow { unsafe { // XSetInputFocus generates an error if the window is not visible, so we wait // until we receive VisibilityNotify. - let mut event = mem::uninitialized(); + let mut event = MaybeUninit::uninit(); (xconn.xlib.XIfEvent)( // This will flush the request buffer IF it blocks. xconn.display, - &mut event as *mut ffi::XEvent, + event.as_mut_ptr(), Some(visibility_predicate), window.xwindow as _, ); @@ -449,19 +459,22 @@ impl UnownedWindow { 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") }; unsafe { - let (hostname, hostname_length) = { - // 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is - // the limit defined by OpenBSD. - const MAXHOSTNAMELEN: usize = 256; - let mut hostname: [c_char; MAXHOSTNAMELEN] = mem::uninitialized(); - let status = libc::gethostname(hostname.as_mut_ptr(), hostname.len()); - if status != 0 { - return None; - } - hostname[MAXHOSTNAMELEN - 1] = '\0' as c_char; // a little extra safety - let hostname_length = libc::strlen(hostname.as_ptr()); - (hostname, hostname_length as usize) - }; + // 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is + // the limit defined by OpenBSD. + const MAXHOSTNAMELEN: usize = 256; + // `assume_init` is safe here because the array consists of `MaybeUninit` values, + // which do not require initialization. + let mut buffer: [MaybeUninit; MAXHOSTNAMELEN] = + MaybeUninit::uninit().assume_init(); + let status = libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()); + if status != 0 { + return None; + } + ptr::write(buffer[MAXHOSTNAMELEN - 1].as_mut_ptr() as *mut u8, b'\0'); // a little extra safety + let hostname_length = libc::strlen(buffer.as_ptr() as *const c_char); + + let hostname = slice::from_raw_parts(buffer.as_ptr() as *const c_char, hostname_length); + self.xconn .change_property( self.xwindow, @@ -1134,13 +1147,13 @@ impl UnownedWindow { let cursor = unsafe { // We don't care about this color, since it only fills bytes // in the pixmap which are not 0 in the mask. - let dummy_color: ffi::XColor = mem::uninitialized(); + let mut dummy_color = MaybeUninit::uninit(); let cursor = (self.xconn.xlib.XCreatePixmapCursor)( self.xconn.display, pixmap, pixmap, - &dummy_color as *const _ as *mut _, - &dummy_color as *const _ as *mut _, + dummy_color.as_mut_ptr(), + dummy_color.as_mut_ptr(), 0, 0, ); From 44af4f4f5283ac75d1c390677fd256bd984a8de6 Mon Sep 17 00:00:00 2001 From: Felix Rabe Date: Sat, 13 Jul 2019 01:05:07 +0200 Subject: [PATCH 06/61] Minor doc changes (#1024) * Minor doc changes * More typos --- src/event_loop.rs | 7 ++++--- src/platform_impl/ios/app_state.rs | 6 +++--- src/platform_impl/ios/event_loop.rs | 2 +- src/platform_impl/macos/event_loop.rs | 2 +- src/platform_impl/macos/observer.rs | 6 +++--- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/event_loop.rs b/src/event_loop.rs index b8fd4ca4..241d4a9b 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -37,9 +37,10 @@ pub struct EventLoop { /// Target that associates windows with an `EventLoop`. /// -/// This type exists to allow you to create new windows while Winit executes your callback. -/// `EventLoop` will coerce into this type, so functions that take this as a parameter can also -/// take `&EventLoop`. +/// This type exists to allow you to create new windows while Winit executes +/// your callback. `EventLoop` will coerce into this type (`impl Deref for +/// EventLoop`), so functions that take this as a parameter can also take +/// `&EventLoop`. pub struct EventLoopWindowTarget { pub(crate) p: platform_impl::EventLoopWindowTarget, pub(crate) _marker: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 5b982b14..32dae8ed 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -577,9 +577,9 @@ impl EventLoopWaker { fn new(rl: CFRunLoopRef) -> EventLoopWaker { extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} unsafe { - // create a timer with a 1microsec interval (1ns does not work) to mimic polling. - // it is initially setup with a first fire time really far into the - // future, but that gets changed to fire immediatley in did_finish_launching + // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. + // It is initially setup with a first fire time really far into the + // future, but that gets changed to fire immediately in did_finish_launching let timer = CFRunLoopTimerCreate( ptr::null_mut(), std::f64::MAX, diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 469d11b1..adbdd7c3 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -163,7 +163,7 @@ impl Drop for EventLoopProxy { impl EventLoopProxy { fn new(sender: Sender) -> EventLoopProxy { unsafe { - // just wakeup the eventloop + // just wake up the eventloop extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} // adding a Source to the main CFRunLoop lets us wake it up and diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 2ad17e1d..1631daa8 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -120,7 +120,7 @@ unsafe impl Sync for Proxy {} impl Proxy { fn new(sender: mpsc::Sender) -> Self { unsafe { - // just wakeup the eventloop + // just wake up the eventloop extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} // adding a Source to the main CFRunLoop lets us wake it up and diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index ddd20a8f..db7a61d7 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -204,9 +204,9 @@ impl Default for EventLoopWaker { fn default() -> EventLoopWaker { extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} unsafe { - // create a timer with a 1µs interval (1ns does not work) to mimic polling. - // it is initially setup with a first fire time really far into the - // future, but that gets changed to fire immediatley in did_finish_launching + // Create a timer with a 0.1µs interval (1ns does not work) to mimic polling. + // It is initially setup with a first fire time really far into the + // future, but that gets changed to fire immediately in did_finish_launching let timer = CFRunLoopTimerCreate( ptr::null_mut(), std::f64::MAX, From e8e4d4ce6643e51b913c904ed092bc18288c08e9 Mon Sep 17 00:00:00 2001 From: Murarth Date: Tue, 16 Jul 2019 15:53:41 -0700 Subject: [PATCH 07/61] X11: Fix `request_redraw` deadlock while handling `RedrawRequested` (#1046) --- src/platform_impl/linux/x11/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index bbd962c5..45220f8b 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -278,8 +278,10 @@ impl EventLoop { } // Empty the redraw requests { - let mut guard = wt.pending_redraws.lock().unwrap(); - for wid in guard.drain() { + // Release the lock to prevent deadlock + let windows: Vec<_> = wt.pending_redraws.lock().unwrap().drain().collect(); + + for wid in windows { sticky_exit_callback( Event::WindowEvent { window_id: crate::window::WindowId(super::WindowId::X(wid)), From 856775815623684a78c7cad6f8f5c74c3a86a4bf Mon Sep 17 00:00:00 2001 From: dam4rus Date: Wed, 17 Jul 2019 18:25:35 +0200 Subject: [PATCH 08/61] Touch events emit screen coordinates instead of client coordinates on Windows (#1042) * Touch events emit screen coordinates instead of client coordinates on Windows Fixes #1002 * Don't lose precision of WM_TOUCH events when converting from screen space to client space * Updated CHANGELOG.md to reflect changes from issue: #1042 --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 26 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65555cf0..a4665ca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and `WindowEvent::HoveredFile`. - On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected. - On Windows, fix `with_maximized` not properly setting window size to entire window. - On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`. +- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. # 0.20.0 Alpha 1 (2019-06-21) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 0204747e..65380c0f 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1448,8 +1448,17 @@ unsafe extern "system" fn public_window_callback( { let dpi_factor = hwnd_scale_factor(window); for input in &inputs { - let x = (input.x as f64) / 100f64; - let y = (input.y as f64) / 100f64; + let mut location = POINT { + x: input.x / 100, + y: input.y / 100, + }; + + if winuser::ScreenToClient(window, &mut location as *mut _) == 0 { + continue; + } + + 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); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1510,8 +1519,17 @@ unsafe extern "system" fn public_window_callback( // The information retrieved appears in reverse chronological order, with the most recent entry in the first // row of the returned array for pointer_info in pointer_infos.iter().rev() { - let x = pointer_info.ptPixelLocation.x as f64; - let y = pointer_info.ptPixelLocation.y as f64; + let mut location = POINT { + x: pointer_info.ptPixelLocation.x, + y: pointer_info.ptPixelLocation.y, + }; + + if winuser::ScreenToClient(window, &mut location as *mut _) == 0 { + continue; + } + + let x = location.x as f64; + let y = location.y as f64; let location = LogicalPosition::from_physical((x, y), dpi_factor); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), From bd1ac6cb1ee9cf73eb0dbe0665dec815142766bc Mon Sep 17 00:00:00 2001 From: Murarth Date: Wed, 17 Jul 2019 11:09:02 -0700 Subject: [PATCH 09/61] X11: Fix events not being reported promptly (#1048) * X11: Fix events not being reported promptly * Add an entry to the changelog --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/mod.rs | 46 +++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4665ca5..e602b73c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and `WindowEvent::HoveredFile`. - On Windows, fix `with_maximized` not properly setting window size to entire window. - On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`. - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. +- On X11, fix delayed events after window redraw. # 0.20.0 Alpha 1 (2019-06-21) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 45220f8b..45f1e3d3 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -60,6 +60,7 @@ pub struct 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>>>, target: Rc>, @@ -171,7 +172,7 @@ impl EventLoop { // Handle X11 events let pending_events: Rc>> = Default::default(); - let mut processor = EventProcessor { + let processor = EventProcessor { target: target.clone(), dnd, devices: Default::default(), @@ -189,6 +190,9 @@ impl EventLoop { 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); @@ -197,17 +201,11 @@ impl EventLoop { .handle() .insert_source(x11_events, { let pending_events = pending_events.clone(); - let mut callback = move |event| { - pending_events.borrow_mut().push_back(event); - }; move |evt, &mut ()| { if evt.readiness.is_readable() { - // 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); - } + let mut processor = processor.borrow_mut(); + let mut pending_events = pending_events.borrow_mut(); + drain_events(&mut processor, &mut pending_events); } } }) @@ -220,6 +218,7 @@ impl EventLoop { _user_source, user_sender, pending_user_events, + event_processor, target, }; @@ -362,6 +361,10 @@ impl EventLoop { } } } + + // If the user callback had any interaction with the X server, + // it may have received and buffered some user input events. + self.drain_events(); } callback( @@ -378,6 +381,29 @@ impl EventLoop { 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(); + + drain_events(&mut processor, &mut pending_events); + } +} + +fn drain_events( + processor: &mut EventProcessor, + pending_events: &mut VecDeque>, +) { + let mut callback = |event| { + pending_events.push_back(event); + }; + + // 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); + } } fn get_xtarget(rt: &RootELW) -> &EventLoopWindowTarget { From 39e668ffb0a1437305508aaa155a57f354b5aa24 Mon Sep 17 00:00:00 2001 From: Murarth Date: Mon, 22 Jul 2019 18:26:52 -0700 Subject: [PATCH 10/61] Fix CHANGELOG.md (#1061) --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e602b73c..eeea01e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. +- On X11, fix delayed events after window redraw. + # 0.20.0 Alpha 2 (2019-07-09) - On X11, non-resizable windows now have maximize explicitly disabled. @@ -18,8 +21,6 @@ and `WindowEvent::HoveredFile`. - On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected. - On Windows, fix `with_maximized` not properly setting window size to entire window. - On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`. -- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. -- On X11, fix delayed events after window redraw. # 0.20.0 Alpha 1 (2019-06-21) From b547531499982abb21af07914be39a6a21c68caf Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 23 Jul 2019 19:38:45 +0200 Subject: [PATCH 11/61] Update the percent-encoding crate to 2.0 (#1066) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1e22ed44..e205b9a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", " calloop = "0.4.2" smithay-client-toolkit = "0.6" x11-dl = "2.18.3" -percent-encoding = "1.0" +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.8" From 5a206de6203fd1602113101bc0c4618fdbc93d07 Mon Sep 17 00:00:00 2001 From: Tilman Schmidt Date: Tue, 23 Jul 2019 21:44:07 +0100 Subject: [PATCH 12/61] macOS: Drop the closure on exit. (Fixes #1058) (#1063) --- CHANGELOG.md | 2 +- src/platform_impl/macos/app_state.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eeea01e9..c91fbe43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Unreleased - +- On macOS, drop the run closure on exit. - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. - On X11, fix delayed events after window redraw. diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index cccbb098..fb020d72 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -196,6 +196,7 @@ impl AppState { HANDLER.set_in_callback(true); HANDLER.handle_nonuser_event(Event::LoopDestroyed); HANDLER.set_in_callback(false); + HANDLER.callback.lock().unwrap().take(); } pub fn launched() { From a28b60578d55d2331753552775c1cae61a97d48e Mon Sep 17 00:00:00 2001 From: Austin Lasher Date: Thu, 25 Jul 2019 14:56:24 -0400 Subject: [PATCH 13/61] Fix run_return example build error on non-desktop platforms (#1067) --- examples/window_run_return.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index d2516c7a..374703e1 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -1,11 +1,21 @@ -use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - platform::desktop::EventLoopExtDesktop, - window::WindowBuilder, -}; - +// Limit this example to only compatible platforms. +#[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] fn main() { + use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + platform::desktop::EventLoopExtDesktop, + window::WindowBuilder, + }; + let mut event_loop = EventLoop::new(); let window = WindowBuilder::new() @@ -39,3 +49,8 @@ fn main() { println!("Okay we're done now for real."); } + +#[cfg(any(target_os = "ios", target_os = "android", target_os = "emscripten"))] +fn main() { + println!("This platform doesn't support run_return."); +} From 454d4190b74f7eaa10276ecd973db31233a7ff02 Mon Sep 17 00:00:00 2001 From: dam4rus Date: Fri, 26 Jul 2019 09:12:06 +0200 Subject: [PATCH 14/61] Use himetric values in WM_POINTER events (#1053) * Use himetric location for WM_POINTER events * Ran rustfmt --- src/platform_impl/windows/event_loop.rs | 51 ++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 65380c0f..1d964270 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -39,7 +39,7 @@ use winapi::{ }, um::{ commctrl, libloaderapi, ole2, processthreadsapi, winbase, - winnt::{LONG, LPCSTR, SHORT}, + winnt::{HANDLE, LONG, LPCSTR, SHORT}, winuser, }, }; @@ -71,12 +71,19 @@ type GetPointerFrameInfoHistory = unsafe extern "system" fn( ) -> BOOL; type SkipPointerFrameMessages = unsafe extern "system" fn(pointerId: UINT) -> BOOL; +type GetPointerDeviceRects = unsafe extern "system" fn( + device: HANDLE, + pointerDeviceRect: *mut RECT, + displayRect: *mut RECT, +) -> BOOL; lazy_static! { static ref GET_POINTER_FRAME_INFO_HISTORY: Option = get_function!("user32.dll", GetPointerFrameInfoHistory); static ref SKIP_POINTER_FRAME_MESSAGES: Option = get_function!("user32.dll", SkipPointerFrameMessages); + static ref GET_POINTER_DEVICE_RECTS: Option = + get_function!("user32.dll", GetPointerDeviceRects); } pub(crate) struct SubclassInput { @@ -1484,9 +1491,14 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_POINTERDOWN | winuser::WM_POINTERUPDATE | winuser::WM_POINTERUP => { - if let (Some(GetPointerFrameInfoHistory), Some(SkipPointerFrameMessages)) = ( + if let ( + Some(GetPointerFrameInfoHistory), + Some(SkipPointerFrameMessages), + Some(GetPointerDeviceRects), + ) = ( *GET_POINTER_FRAME_INFO_HISTORY, *SKIP_POINTER_FRAME_MESSAGES, + *GET_POINTER_DEVICE_RECTS, ) { let pointer_id = LOWORD(wparam as DWORD) as UINT; let mut entries_count = 0 as UINT; @@ -1519,17 +1531,44 @@ unsafe extern "system" fn public_window_callback( // The information retrieved appears in reverse chronological order, with the most recent entry in the first // row of the returned array for pointer_info in pointer_infos.iter().rev() { + let mut device_rect: RECT = mem::uninitialized(); + let mut display_rect: RECT = mem::uninitialized(); + + if (GetPointerDeviceRects( + pointer_info.sourceDevice, + &mut device_rect as *mut _, + &mut display_rect as *mut _, + )) == 0 + { + continue; + } + + // For the most precise himetric to pixel conversion we calculate the ratio between the resolution + // of the display device (pixel) and the touch device (himetric). + let himetric_to_pixel_ratio_x = (display_rect.right - display_rect.left) as f64 + / (device_rect.right - device_rect.left) as f64; + let himetric_to_pixel_ratio_y = (display_rect.bottom - display_rect.top) as f64 + / (device_rect.bottom - device_rect.top) as f64; + + // ptHimetricLocation's origin is 0,0 even on multi-monitor setups. + // On multi-monitor setups we need to translate the himetric location to the rect of the + // display device it's attached to. + let x = display_rect.left as f64 + + pointer_info.ptHimetricLocation.x as f64 * himetric_to_pixel_ratio_x; + let y = display_rect.top as f64 + + pointer_info.ptHimetricLocation.y as f64 * himetric_to_pixel_ratio_y; + let mut location = POINT { - x: pointer_info.ptPixelLocation.x, - y: pointer_info.ptPixelLocation.y, + x: x.floor() as i32, + y: y.floor() as i32, }; if winuser::ScreenToClient(window, &mut location as *mut _) == 0 { continue; } - let x = location.x as f64; - let y = location.y as f64; + 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); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), From 4ae990036335284d5700e1f10c52194f9e8fbc27 Mon Sep 17 00:00:00 2001 From: Felix Rabe Date: Fri, 26 Jul 2019 09:14:48 +0200 Subject: [PATCH 15/61] PULL_REQUEST_TEMPLATE.md: Add entry on warnings (#1017) --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d6f55915..ecbd5d4b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,5 @@ - [ ] Tested on all platforms changed +- [ ] Compilation warnings were addressed - [ ] `cargo fmt` has been run on this branch - [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users - [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior From 03f9e8fce01a284b7834c0efc8be52afe5525ebf Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Sun, 28 Jul 2019 03:09:31 -0600 Subject: [PATCH 16/61] Always use stable rustfmt for CI. (#1074) Signed-off-by: Hal Gentz --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7e6bc833..17a0e4af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,10 +45,11 @@ matrix: install: - rustup self update - rustup target add $TARGET; true - - rustup component add rustfmt + - rustup toolchain install stable + - rustup component add rustfmt --toolchain stable script: - - cargo fmt --all -- --check + - cargo +stable fmt --all -- --check - cargo build --target $TARGET --verbose - cargo build --target $TARGET --features serde --verbose # Running iOS apps on OSX requires the simulator so we skip that for now From f4e9bf51db21fba8386ec9868fef27569ffe171e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?t=E5=98=8E?= Date: Mon, 29 Jul 2019 16:07:36 +0800 Subject: [PATCH 17/61] add macos with_disallow_hidpi (#1073) * add macos with_disallow_hidpi add CHANGELOG * Always use stable rustfmt for CI. (#1074) Signed-off-by: Hal Gentz * add macos with_disallow_hidpi add CHANGELOG --- CHANGELOG.md | 1 + src/platform/macos.rs | 7 +++++++ src/platform_impl/macos/window.rs | 19 +++++++++++++------ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c91fbe43..a0eece88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On macOS, drop the run closure on exit. - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. - On X11, fix delayed events after window redraw. +- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index f08c774b..8101f0f7 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -129,6 +129,7 @@ pub trait WindowBuilderExtMacOS { 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_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder; } impl WindowBuilderExtMacOS for WindowBuilder { @@ -182,6 +183,12 @@ impl WindowBuilderExtMacOS for WindowBuilder { self.platform_specific.resize_increments = Some(increments.into()); self } + + #[inline] + fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder { + self.platform_specific.disallow_hidpi = disallow_hidpi; + 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 4f6bf255..a99e7f6c 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -66,6 +66,7 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub titlebar_buttons_hidden: bool, pub fullsize_content_view: bool, pub resize_increments: Option, + pub disallow_hidpi: bool, } fn create_app(activation_policy: ActivationPolicy) -> Option { @@ -86,10 +87,15 @@ fn create_app(activation_policy: ActivationPolicy) -> Option { } } -unsafe fn create_view(ns_window: id) -> Option<(IdRef, Weak>)> { +unsafe fn create_view( + ns_window: id, + pl_attribs: &PlatformSpecificWindowBuilderAttributes, +) -> Option<(IdRef, Weak>)> { let (ns_view, cursor) = new_view(ns_window); ns_view.non_nil().map(|ns_view| { - ns_view.setWantsBestResolutionOpenGLSurface_(YES); + if !pl_attribs.disallow_hidpi { + ns_view.setWantsBestResolutionOpenGLSurface_(YES); + } // On Mojave, views automatically become layer-backed shortly after being added to // a window. Changing the layer-backedness of a view breaks the association between @@ -301,10 +307,11 @@ impl UnownedWindow { os_error!(OsError::CreationError("Couldn't create `NSWindow`")) })?; - let (ns_view, cursor) = unsafe { create_view(*ns_window) }.ok_or_else(|| { - unsafe { pool.drain() }; - os_error!(OsError::CreationError("Couldn't create `NSView`")) - })?; + let (ns_view, cursor) = + unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| { + unsafe { pool.drain() }; + os_error!(OsError::CreationError("Couldn't create `NSView`")) + })?; let input_context = unsafe { util::create_input_context(*ns_view) }; From e5ba79db048800cecbb2ed6242097b1eedd0dc35 Mon Sep 17 00:00:00 2001 From: dam4rus Date: Mon, 29 Jul 2019 11:18:23 +0200 Subject: [PATCH 18/61] Process WM_SYSCOMMAND to forbid screen savers in fullscreen mode (#1065) * Process WM_SYSCOMMAND to forbid screen savers in fullscreen mode Fixes #1047 * Update CHANGELOG.md and documentation to reflect changes from issue #1065 * Updated documentation of window.Window.set_fullscreen to match the documentation of window.WindowBuilder.with_fullscreen. --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 11 +++++++++++ src/window.rs | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0eece88..ea99659b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. - On X11, fix delayed events after window redraw. - On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface. +- On Windows, screen saver won't start if the window is in fullscreen mode. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 1d964270..c84716f7 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1020,6 +1020,17 @@ unsafe extern "system" fn public_window_callback( // other unwanted default hotkeys as well. winuser::WM_SYSCHAR => 0, + winuser::WM_SYSCOMMAND => { + if wparam == winuser::SC_SCREENSAVE { + let window_state = subclass_input.window_state.lock(); + if window_state.fullscreen.is_some() { + return 0; + } + } + + winuser::DefWindowProcW(window, msg, wparam, lparam) + } + winuser::WM_MOUSEMOVE => { use crate::event::WindowEvent::{CursorEntered, CursorMoved}; let mouse_was_outside_window = { diff --git a/src/window.rs b/src/window.rs index dfde6713..e095e38d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -224,6 +224,10 @@ impl WindowBuilder { /// Sets the window fullscreen state. None means a normal window, Some(MonitorHandle) /// means a fullscreen window on that specific monitor + /// + /// ## Platform-specific + /// + /// - **Windows:** Screen saver is disabled in fullscreen mode. #[inline] pub fn with_fullscreen(mut self, monitor: Option) -> WindowBuilder { self.window.fullscreen = monitor; @@ -534,6 +538,7 @@ impl Window { /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. + /// - **Windows:** Screen saver is disabled in fullscreen mode. #[inline] pub fn set_fullscreen(&self, monitor: Option) { self.window.set_fullscreen(monitor) From 131e67ddc172b07e9fe65814ad5ad9a0277e5a4f Mon Sep 17 00:00:00 2001 From: Brian Kabiro Date: Mon, 29 Jul 2019 17:58:16 +0300 Subject: [PATCH 19/61] Rename `new_user_event` method to `with_user_event` (#1057) (#1068) Finishes #1057 --- CHANGELOG.md | 1 + examples/proxy.rs | 2 +- src/event_loop.rs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea99659b..e7e3d8fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - On X11, fix delayed events after window redraw. - On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface. - On Windows, screen saver won't start if the window is in fullscreen mode. +- Change all occurrences of the `new_user_event` method to `with_user_event`. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/examples/proxy.rs b/examples/proxy.rs index 06198ecd..d078f230 100644 --- a/examples/proxy.rs +++ b/examples/proxy.rs @@ -5,7 +5,7 @@ use winit::{ }; fn main() { - let event_loop: EventLoop = EventLoop::new_user_event(); + let event_loop: EventLoop = EventLoop::with_user_event(); let _window = WindowBuilder::new() .with_title("A fantastic window!") diff --git a/src/event_loop.rs b/src/event_loop.rs index 241d4a9b..a7a1134c 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -100,7 +100,7 @@ impl EventLoop<()> { /// /// - **iOS:** Can only be called on the main thread. pub fn new() -> EventLoop<()> { - EventLoop::<()>::new_user_event() + EventLoop::<()>::with_user_event() } } @@ -115,7 +115,7 @@ impl EventLoop { /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. - pub fn new_user_event() -> EventLoop { + pub fn with_user_event() -> EventLoop { EventLoop { event_loop: platform_impl::EventLoop::new(), _marker: ::std::marker::PhantomData, From 5bc3cf18d98aef0a5f0a51c0cf655d37af833730 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Date: Mon, 29 Jul 2019 21:16:14 +0300 Subject: [PATCH 20/61] Add exclusive fullscreen mode (#925) * Add exclusive fullscreen mode * Add `WindowExtMacOS::set_fullscreen_presentation_options` * Capture display for exclusive fullscreen on macOS * Fix applying video mode on macOS after a fullscreen cycle * Fix compilation on iOS * Set monitor appropriately for fullscreen on macOS * Fix exclusive to borderless fullscreen transitions on macOS * Fix borderless to exclusive fullscreen transition on macOS * Sort video modes on Windows * Fix fullscreen issues on Windows * Fix video mode changes during exclusive fullscreen on Windows * Add video mode sorting for macOS and iOS * Fix monitor `ns_screen` returning `None` after video mode change * Fix "multithreaded" example on macOS * Restore video mode upon closing an exclusive fullscreen window * Fix "multithreaded" example closing multiple windows at once * Fix compilation on Linux * Update FEATURES.md * Don't care about logical monitor groups on X11 * Add exclusive fullscreen for X11 * Update FEATURES.md * Fix transitions between exclusive and borderless fullscreen on X11 * Update CHANGELOG.md * Document that Wayland doesn't support exclusive fullscreen * Replace core-graphics display mode bindings on macOS * Use `panic!()` instead of `unreachable!()` in "fullscreen" example * Fix fullscreen "always on top" flag on Windows * Track current monitor for fullscreen in "multithreaded" example * Fix exclusive fullscreen sometimes not positioning window properly * Format * More formatting and fix CI issues * Fix formatting * Fix changelog formatting --- CHANGELOG.md | 6 + FEATURES.md | 4 + examples/fullscreen.rs | 103 +++---- examples/multithreaded.rs | 50 +++- examples/video_modes.rs | 2 +- src/lib.rs | 1 - src/monitor.rs | 62 +++- src/platform_impl/ios/mod.rs | 2 +- src/platform_impl/ios/monitor.rs | 52 +++- src/platform_impl/ios/view.rs | 25 +- src/platform_impl/ios/window.rs | 22 +- src/platform_impl/linux/mod.rs | 56 +++- src/platform_impl/linux/wayland/event_loop.rs | 84 +++++- src/platform_impl/linux/wayland/mod.rs | 3 +- src/platform_impl/linux/wayland/window.rs | 56 ++-- src/platform_impl/linux/x11/mod.rs | 2 +- src/platform_impl/linux/x11/monitor.rs | 185 ++++++------ src/platform_impl/linux/x11/util/randr.rs | 117 +++++--- src/platform_impl/linux/x11/window.rs | 125 ++++++-- src/platform_impl/macos/ffi.rs | 99 +++++++ src/platform_impl/macos/mod.rs | 2 +- src/platform_impl/macos/monitor.rs | 179 ++++++++++-- src/platform_impl/macos/observer.rs | 15 +- src/platform_impl/macos/util/async.rs | 5 +- src/platform_impl/macos/window.rs | 269 +++++++++++++----- src/platform_impl/macos/window_delegate.rs | 48 +++- src/platform_impl/windows/mod.rs | 2 +- src/platform_impl/windows/monitor.rs | 136 +++++---- src/platform_impl/windows/window.rs | 214 ++++++++++---- src/platform_impl/windows/window_state.rs | 82 ++---- src/window.rs | 49 +++- 31 files changed, 1452 insertions(+), 605 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e3d8fe..b8ec9346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ # Unreleased + - On macOS, drop the run closure on exit. - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. - On X11, fix delayed events after window redraw. - On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface. - On Windows, screen saver won't start if the window is in fullscreen mode. - Change all occurrences of the `new_user_event` method to `with_user_event`. +- On macOS, the dock and the menu bar are now hidden in fullscreen mode. +- `Window::set_fullscreen` now takes `Option` where `Fullscreen` + consists of `Fullscreen::Exclusive(VideoMode)` and + `Fullscreen::Borderless(MonitorHandle)` variants. + - Adds support for exclusive fullscreen mode. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/FEATURES.md b/FEATURES.md index a89c0689..73994604 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -84,6 +84,9 @@ If your PR makes notable changes to Winit's features, please update this section - **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. +- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor + for fullscreen windows, and if applicable, captures the monitor for exclusive + use by this application. - **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content. - **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent windows can be disabled in favor of popup windows. This feature also guarantees that popup windows @@ -157,6 +160,7 @@ Legend: |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | +|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |❌ |❌ | |HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 27df2276..d4b83bb0 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,56 +1,35 @@ -use std::io::{self, Write}; -use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - monitor::MonitorHandle, - window::WindowBuilder, -}; +use std::io::{stdin, stdout, Write}; +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() { let event_loop = EventLoop::new(); - #[cfg(target_os = "macos")] - let mut macos_use_simple_fullscreen = false; + print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: "); + stdout().flush().unwrap(); - let monitor = { - // On macOS there are two fullscreen modes "native" and "simple" - #[cfg(target_os = "macos")] - { - print!("Please choose the fullscreen mode: (1) native, (2) simple: "); - io::stdout().flush().unwrap(); + let mut num = String::new(); + stdin().read_line(&mut num).unwrap(); + let num = num.trim().parse().ok().expect("Please enter a number"); - let mut num = String::new(); - io::stdin().read_line(&mut num).unwrap(); - let num = num.trim().parse().ok().expect("Please enter a number"); - match num { - 2 => macos_use_simple_fullscreen = true, - _ => {} - } + 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)), + _ => panic!("Please enter a valid number"), + }); - // Prompt for monitor when using native fullscreen - if !macos_use_simple_fullscreen { - Some(prompt_for_monitor(&event_loop)) - } else { - None - } - } - - #[cfg(not(target_os = "macos"))] - Some(prompt_for_monitor(&event_loop)) - }; - - let mut is_fullscreen = monitor.is_some(); let mut is_maximized = false; let mut decorations = true; let window = WindowBuilder::new() .with_title("Hello world!") - .with_fullscreen(monitor) + .with_fullscreen(fullscreen.clone()) .build(&event_loop) .unwrap(); event_loop.run(move |event, _, control_flow| { - println!("{:?}", event); *control_flow = ControlFlow::Wait; match event { @@ -67,35 +46,14 @@ fn main() { } => match (virtual_code, state) { (VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit, (VirtualKeyCode::F, ElementState::Pressed) => { - #[cfg(target_os = "macos")] - { - if macos_use_simple_fullscreen { - use winit::platform::macos::WindowExtMacOS; - if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) { - is_fullscreen = !is_fullscreen; - } - return; - } - } - - is_fullscreen = !is_fullscreen; - if !is_fullscreen { + if window.fullscreen().is_some() { window.set_fullscreen(None); } else { - window.set_fullscreen(Some(window.current_monitor())); + window.set_fullscreen(fullscreen.clone()); } } (VirtualKeyCode::S, ElementState::Pressed) => { println!("window.fullscreen {:?}", window.fullscreen()); - - #[cfg(target_os = "macos")] - { - use winit::platform::macos::WindowExtMacOS; - println!( - "window.simple_fullscreen {:?}", - WindowExtMacOS::simple_fullscreen(&window) - ); - } } (VirtualKeyCode::M, ElementState::Pressed) => { is_maximized = !is_maximized; @@ -121,10 +79,10 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { } print!("Please write the number of the monitor to use: "); - io::stdout().flush().unwrap(); + stdout().flush().unwrap(); let mut num = String::new(); - io::stdin().read_line(&mut num).unwrap(); + stdin().read_line(&mut num).unwrap(); let num = num.trim().parse().ok().expect("Please enter a number"); let monitor = event_loop .available_monitors() @@ -135,3 +93,24 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle { monitor } + +fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode { + for (i, video_mode) in monitor.video_modes().enumerate() { + println!("Video mode #{}: {}", i, video_mode); + } + + print!("Please write the number of the video mode to use: "); + stdout().flush().unwrap(); + + let mut num = String::new(); + stdin().read_line(&mut num).unwrap(); + let num = num.trim().parse().ok().expect("Please enter a number"); + let video_mode = monitor + .video_modes() + .nth(num) + .expect("Please enter a valid ID"); + + println!("Using {}", video_mode); + + video_mode +} diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 39f7af3d..4f5dbc49 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -4,7 +4,7 @@ use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; use winit::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::{CursorIcon, WindowBuilder}, + window::{CursorIcon, Fullscreen, WindowBuilder}, }; const WINDOW_COUNT: usize = 3; @@ -19,11 +19,34 @@ fn main() { .with_inner_size(WINDOW_SIZE.into()) .build(&event_loop) .unwrap(); + + let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect(); + let mut video_mode_id = 0usize; + let (tx, rx) = mpsc::channel(); window_senders.insert(window.id(), tx); thread::spawn(move || { while let Ok(event) = rx.recv() { match event { + WindowEvent::Moved { .. } => { + // We need to update our chosen video mode if the window + // 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_mode_id = video_mode_id.min(video_modes.len()); + let video_mode = video_modes.iter().nth(video_mode_id); + + // Different monitors may support different video modes, + // and the index we chose previously may now point to a + // completely different video mode, so notify the user + if video_mode != previous_video_mode.as_ref() { + println!( + "Window moved to another monitor, picked video mode: {}", + video_modes.iter().nth(video_mode_id).unwrap() + ); + } + } WindowEvent::KeyboardInput { input: KeyboardInput { @@ -44,9 +67,26 @@ fn main() { false => CursorIcon::Default, }), D => window.set_decorations(!state), - F => window.set_fullscreen(match state { - true => Some(window.current_monitor()), - false => None, + // Cycle through video modes + Right | Left => { + video_mode_id = match key { + Left => video_mode_id.saturating_sub(1), + Right => (video_modes.len() - 1).min(video_mode_id + 1), + _ => unreachable!(), + }; + println!( + "Picking video mode: {}", + video_modes.iter().nth(video_mode_id).unwrap() + ); + } + F => window.set_fullscreen(match (state, modifiers.alt) { + (true, false) => { + Some(Fullscreen::Borderless(window.current_monitor())) + } + (true, true) => Some(Fullscreen::Exclusive( + video_modes.iter().nth(video_mode_id).unwrap().clone(), + )), + (false, _) => None, }), G => window.set_cursor_grab(state).unwrap(), H => window.set_cursor_visible(!state), @@ -56,6 +96,7 @@ fn main() { println!("-> inner_position : {:?}", window.inner_position()); println!("-> outer_size : {:?}", window.outer_size()); println!("-> inner_size : {:?}", window.inner_size()); + println!("-> fullscreen : {:?}", window.fullscreen()); } L => window.set_min_inner_size(match state { true => Some(WINDOW_SIZE.into()), @@ -108,6 +149,7 @@ fn main() { | WindowEvent::KeyboardInput { input: KeyboardInput { + state: ElementState::Released, virtual_keycode: Some(VirtualKeyCode::Escape), .. }, diff --git a/examples/video_modes.rs b/examples/video_modes.rs index f8c6aa08..f923fa92 100644 --- a/examples/video_modes.rs +++ b/examples/video_modes.rs @@ -7,6 +7,6 @@ fn main() { println!("Listing available video modes:"); for mode in monitor.video_modes() { - println!("{:?}", mode); + println!("{}", mode); } } diff --git a/src/lib.rs b/src/lib.rs index 1290bcd1..a4a65f54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,6 @@ extern crate log; #[macro_use] extern crate serde; #[macro_use] -#[cfg(target_os = "windows")] extern crate derivative; #[macro_use] #[cfg(target_os = "windows")] diff --git a/src/monitor.rs b/src/monitor.rs index f27ef9d7..8e085a58 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -52,17 +52,41 @@ impl Iterator for AvailableMonitorsIter { /// - [`MonitorHandle::video_modes`][monitor_get]. /// /// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Derivative)] +#[derivative(Clone, Debug = "transparent", PartialEq, Eq, Hash)] pub struct VideoMode { - pub(crate) size: (u32, u32), - pub(crate) bit_depth: u16, - pub(crate) refresh_rate: u16, + pub(crate) video_mode: platform_impl::VideoMode, +} + +impl PartialOrd for VideoMode { + fn partial_cmp(&self, other: &VideoMode) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VideoMode { + fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering { + // TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32` + // to `u32` there + let size: (u32, u32) = self.size().into(); + let other_size: (u32, u32) = other.size().into(); + self.monitor().cmp(&other.monitor()).then( + size.cmp(&other_size) + .then( + self.refresh_rate() + .cmp(&other.refresh_rate()) + .then(self.bit_depth().cmp(&other.bit_depth())), + ) + .reverse(), + ) + } } impl VideoMode { /// Returns the resolution of this video mode. + #[inline] pub fn size(&self) -> PhysicalSize { - self.size.into() + self.video_mode.size() } /// Returns the bit depth of this video mode, as in how many bits you have @@ -73,15 +97,37 @@ impl VideoMode { /// /// - **Wayland:** Always returns 32. /// - **iOS:** Always returns 32. + #[inline] pub fn bit_depth(&self) -> u16 { - self.bit_depth + self.video_mode.bit_depth() } /// Returns the refresh rate of this video mode. **Note**: the returned /// refresh rate is an integer approximation, and you shouldn't rely on this /// value to be exact. + #[inline] pub fn refresh_rate(&self) -> u16 { - self.refresh_rate + self.video_mode.refresh_rate() + } + + /// Returns the monitor that this video mode is valid for. Each monitor has + /// a separate set of valid video modes. + #[inline] + pub fn monitor(&self) -> MonitorHandle { + self.video_mode.monitor() + } +} + +impl std::fmt::Display for VideoMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}x{} @ {} Hz ({} bpp)", + self.size().width, + self.size().height, + self.refresh_rate(), + self.bit_depth() + ) } } @@ -90,7 +136,7 @@ impl VideoMode { /// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. /// /// [`Window`]: ../window/struct.Window.html -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct MonitorHandle { pub(crate) inner: platform_impl::MonitorHandle, } diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index dfb659f9..3141dea4 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -79,7 +79,7 @@ use std::fmt; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, }; diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index c75b7ef6..90257077 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -1,18 +1,44 @@ use std::{ - collections::{HashSet, VecDeque}, + collections::{BTreeSet, VecDeque}, fmt, ops::{Deref, DerefMut}, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - monitor::VideoMode, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + platform_impl::platform::ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger}, }; -use crate::platform_impl::platform::ffi::{ - id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger, -}; +#[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 { + pub fn size(&self) -> PhysicalSize { + self.size.into() + } + + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> RootMonitorHandle { + RootMonitorHandle { + inner: self.monitor.clone(), + } + } +} + +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Inner { uiscreen: id, } @@ -25,6 +51,7 @@ impl Drop for Inner { } } +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct MonitorHandle { inner: Inner, } @@ -140,21 +167,24 @@ impl Inner { } } - pub fn video_modes(&self) -> impl Iterator { + pub fn video_modes(&self) -> impl Iterator { let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] }; let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] }; let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] }; - let mut modes = HashSet::with_capacity(available_mode_count); + let mut modes = BTreeSet::new(); for i in 0..available_mode_count { let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] }; let size: CGSize = unsafe { msg_send![mode, size] }; - modes.insert(VideoMode { - size: (size.width as u32, size.height as u32), - bit_depth: 32, - refresh_rate: refresh_rate as u16, + modes.insert(RootVideoMode { + video_mode: VideoMode { + size: (size.width as u32, size.height as u32), + bit_depth: 32, + refresh_rate: refresh_rate as u16, + monitor: MonitorHandle::retained_new(self.uiscreen), + }, }); } diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index b174b75a..95047814 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -8,15 +8,14 @@ use objc::{ use crate::{ event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent}, platform::ios::MonitorHandleExtIOS, - window::{WindowAttributes, WindowId as RootWindowId}, -}; - -use crate::platform_impl::platform::{ - app_state::AppState, - event_loop, - ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase}, - window::PlatformSpecificWindowBuilderAttributes, - DeviceId, + platform_impl::platform::{ + app_state::AppState, + event_loop, + ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase}, + window::PlatformSpecificWindowBuilderAttributes, + DeviceId, + }, + window::{Fullscreen, WindowAttributes, WindowId as RootWindowId}, }; // requires main thread @@ -366,8 +365,12 @@ pub unsafe fn create_window( if let Some(hidpi_factor) = platform_attributes.hidpi_factor { let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat]; } - if let &Some(ref monitor) = &window_attributes.fullscreen { - let () = msg_send![window, setScreen:monitor.ui_screen()]; + match window_attributes.fullscreen { + Some(Fullscreen::Exclusive(_)) => unimplemented!(), + Some(Fullscreen::Borderless(ref monitor)) => { + msg_send![window, setScreen:monitor.ui_screen()] + } + None => (), } window diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index ea4f022f..05b68ea4 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -17,7 +17,7 @@ use crate::{ ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask}, monitor, view, EventLoopWindowTarget, MonitorHandle, }, - window::{CursorIcon, WindowAttributes}, + window::{CursorIcon, Fullscreen, WindowAttributes}, }; pub struct Inner { @@ -157,10 +157,11 @@ impl Inner { warn!("`Window::set_maximized` is ignored on iOS") } - pub fn set_fullscreen(&self, monitor: Option) { + pub fn set_fullscreen(&self, monitor: Option) { unsafe { match monitor { - Some(monitor) => { + Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO + Some(Fullscreen::Borderless(monitor)) => { let uiscreen = monitor.ui_screen() as id; let current: id = msg_send![self.window, screen]; let bounds: CGRect = msg_send![uiscreen, bounds]; @@ -176,7 +177,7 @@ impl Inner { } } - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { unsafe { let monitor = self.current_monitor(); let uiscreen = monitor.inner.ui_screen(); @@ -189,7 +190,7 @@ impl Inner { && screen_space_bounds.size.width == screen_bounds.size.width && screen_space_bounds.size.height == screen_bounds.size.height { - Some(monitor) + Some(Fullscreen::Borderless(monitor)) } else { None } @@ -293,11 +294,12 @@ impl Window { // TODO: transparency, visible unsafe { - let screen = window_attributes - .fullscreen - .as_ref() - .map(|screen| screen.ui_screen() as _) - .unwrap_or_else(|| monitor::main_uiscreen().ui_screen()); + let screen = match window_attributes.fullscreen { + Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO: do we set the frame to video mode bounds instead of screen bounds? + Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id, + None => monitor::main_uiscreen().ui_screen(), + }; + let screen_bounds: CGRect = msg_send![screen, bounds]; let frame = match window_attributes.inner_size { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 258f4280..bd2656ed 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -13,8 +13,8 @@ use crate::{ event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, icon::Icon, - monitor::{MonitorHandle as RootMonitorHandle, VideoMode}, - window::{CursorIcon, WindowAttributes}, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + window::{CursorIcon, Fullscreen, WindowAttributes}, }; mod dlopen; @@ -92,7 +92,7 @@ impl DeviceId { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum MonitorHandle { X(x11::MonitorHandle), Wayland(wayland::MonitorHandle), @@ -140,7 +140,7 @@ impl MonitorHandle { } #[inline] - pub fn video_modes(&self) -> Box> { + pub fn video_modes(&self) -> Box> { match self { MonitorHandle::X(m) => Box::new(m.video_modes()), MonitorHandle::Wayland(m) => Box::new(m.video_modes()), @@ -148,6 +148,46 @@ impl MonitorHandle { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum VideoMode { + X(x11::VideoMode), + 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(), + } + } + + #[inline] + pub fn bit_depth(&self) -> u16 { + match self { + &VideoMode::X(ref m) => m.bit_depth(), + &VideoMode::Wayland(ref 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(), + } + } + + #[inline] + pub fn monitor(&self) -> RootMonitorHandle { + match self { + &VideoMode::X(ref m) => m.monitor(), + &VideoMode::Wayland(ref m) => m.monitor(), + } + } +} + impl Window { #[inline] pub fn new( @@ -310,17 +350,15 @@ impl Window { } #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { match self { &Window::X(ref w) => w.fullscreen(), - &Window::Wayland(ref w) => w.fullscreen().map(|monitor_id| RootMonitorHandle { - inner: MonitorHandle::Wayland(monitor_id), - }), + &Window::Wayland(ref w) => w.fullscreen(), } } #[inline] - pub fn set_fullscreen(&self, monitor: Option) { + 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), diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 7c3b21a0..2cdfb168 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -16,8 +16,11 @@ use crate::{ dpi::{PhysicalPosition, PhysicalSize}, event::ModifiersState, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, - monitor::VideoMode, - platform_impl::platform::sticky_exit_callback, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + platform_impl::platform::{ + sticky_exit_callback, MonitorHandle as PlatformMonitorHandle, + VideoMode as PlatformVideoMode, + }, }; use super::{window::WindowStore, DeviceId, WindowId}; @@ -603,17 +606,67 @@ impl Drop for SeatData { * 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 Clone for MonitorHandle { - fn clone(&self) -> MonitorHandle { - MonitorHandle { - proxy: self.proxy.clone(), - mgr: self.mgr.clone(), - } +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); } } @@ -680,15 +733,20 @@ impl MonitorHandle { } #[inline] - pub fn video_modes(&self) -> impl Iterator { + 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(|x| 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, + .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(), + }), }) } } diff --git a/src/platform_impl/linux/wayland/mod.rs b/src/platform_impl/linux/wayland/mod.rs index aa96e276..09cd66d1 100644 --- a/src/platform_impl/linux/wayland/mod.rs +++ b/src/platform_impl/linux/wayland/mod.rs @@ -3,7 +3,8 @@ pub use self::{ event_loop::{ - EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, WindowEventsSink, + EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode, + WindowEventsSink, }, window::Window, }; diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 773af760..b4a04aa7 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -8,10 +8,11 @@ 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, PlatformSpecificWindowBuilderAttributes as PlAttributes, }, - window::{CursorIcon, WindowAttributes}, + window::{CursorIcon, Fullscreen, WindowAttributes}, }; use smithay_client_toolkit::{ @@ -25,7 +26,6 @@ use smithay_client_toolkit::{ }; use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId}; -use crate::platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor}; pub struct Window { surface: wl_surface::WlSurface, @@ -108,13 +108,19 @@ impl Window { } // Check for fullscreen requirements - if let Some(RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(ref monitor_id), - }) = attributes.fullscreen - { - frame.set_fullscreen(Some(&monitor_id.proxy)); - } else if attributes.maximized { - frame.set_maximized(); + match attributes.fullscreen { + 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)), + Some(Fullscreen::Borderless(_)) => unreachable!(), + None => { + if attributes.maximized { + frame.set_maximized(); + } + } } frame.set_resizable(attributes.resizable); @@ -252,25 +258,31 @@ impl Window { } } - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { if *(self.fullscreen.lock().unwrap()) { - Some(self.current_monitor()) + Some(Fullscreen::Borderless(RootMonitorHandle { + inner: PlatformMonitorHandle::Wayland(self.current_monitor()), + })) } else { None } } - pub fn set_fullscreen(&self, monitor: Option) { - if let Some(RootMonitorHandle { - inner: PlatformMonitorHandle::Wayland(ref monitor_id), - }) = monitor - { - self.frame - .lock() - .unwrap() - .set_fullscreen(Some(&monitor_id.proxy)); - } else { - self.frame.lock().unwrap().unset_fullscreen(); + pub fn set_fullscreen(&self, fullscreen: Option) { + match fullscreen { + 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(_)) => 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 45f1e3d3..345ebeeb 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -11,7 +11,7 @@ mod window; mod xdisplay; pub use self::{ - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::UnownedWindow, xdisplay::{XConnection, XError, XNotSupported}, }; diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 72026257..33a694e0 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -4,47 +4,66 @@ use parking_lot::Mutex; use super::{ ffi::{ - RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, Window, - XRRScreenResources, + RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask, + RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources, }, util, XConnection, XError, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - monitor::VideoMode, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode}, }; -// Used to test XRandR < 1.5 code path. This should always be committed as false. -const FORCE_RANDR_COMPAT: bool = false; -// Also used for testing. This should always be committed as false. +// Used for testing. This should always be committed as false. const DISABLE_MONITOR_LIST_CACHING: bool = false; lazy_static! { - static ref XRANDR_VERSION: Mutex> = Mutex::default(); static ref MONITORS: Mutex>> = Mutex::default(); } -fn version_is_at_least(major: c_int, minor: c_int) -> bool { - if let Some((avail_major, avail_minor)) = *XRANDR_VERSION.lock() { - if avail_major == major { - avail_minor >= minor - } else { - avail_major > major - } - } else { - unreachable!(); - } -} - pub fn invalidate_cached_monitor_list() -> Option> { // We update this lazily. (*MONITORS.lock()).take() } +#[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) native_mode: RRMode, + pub(crate) monitor: Option, +} + +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::X(self.monitor.clone().unwrap()), + } + } +} + #[derive(Debug, Clone)] pub struct MonitorHandle { /// The actual id - id: u32, + pub(crate) id: RRCrtc, /// The name of the monitor pub(crate) name: String, /// The size of the monitor @@ -61,16 +80,43 @@ pub struct MonitorHandle { video_modes: Vec, } +impl PartialEq for MonitorHandle { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +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.id.cmp(&other.id) + } +} + +impl std::hash::Hash for MonitorHandle { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + impl MonitorHandle { - fn from_repr( + fn new( xconn: &XConnection, resources: *mut XRRScreenResources, - id: u32, - repr: util::MonitorRepr, + id: RRCrtc, + crtc: *mut XRRCrtcInfo, primary: bool, ) -> Option { - let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, &repr)? }; - let (dimensions, position) = unsafe { (repr.size(), repr.position()) }; + let (name, hidpi_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, @@ -107,8 +153,14 @@ impl MonitorHandle { } #[inline] - pub fn video_modes(&self) -> impl Iterator { - self.video_modes.clone().into_iter() + pub fn video_modes(&self) -> impl Iterator { + let monitor = self.clone(); + self.video_modes.clone().into_iter().map(move |mut x| { + x.monitor = Some(monitor.clone()); + RootVideoMode { + video_mode: PlatformVideoMode::X(x), + } + }) } } @@ -139,8 +191,12 @@ impl XConnection { fn query_monitor_list(&self) -> Vec { unsafe { + let mut major = 0; + let mut minor = 0; + (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); + let root = (self.xlib.XDefaultRootWindow)(self.display); - let resources = if version_is_at_least(1, 3) { + let resources = if (major == 1 && minor >= 3) || major > 1 { (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) } else { // WARNING: this function is supposedly very slow, on the order of hundreds of ms. @@ -155,48 +211,19 @@ impl XConnection { let mut available; let mut has_primary = false; - if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT { - // We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and - // videowalls. - let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap(); - let mut monitor_count = 0; - let monitors = - (xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count); - assert!(monitor_count >= 0); - available = Vec::with_capacity(monitor_count as usize); - for monitor_index in 0..monitor_count { - let monitor = monitors.offset(monitor_index as isize); - let is_primary = (*monitor).primary != 0; + let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root); + available = Vec::with_capacity((*resources).ncrtc as usize); + for crtc_index in 0..(*resources).ncrtc { + let crtc_id = *((*resources).crtcs.offset(crtc_index as isize)); + let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); + let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0; + if is_active { + let is_primary = *(*crtc).outputs.offset(0) == primary; has_primary |= is_primary; - MonitorHandle::from_repr( - self, - resources, - monitor_index as u32, - monitor.into(), - is_primary, - ) - .map(|monitor_id| available.push(monitor_id)); - } - (xrandr_1_5.XRRFreeMonitors)(monitors); - } else { - // We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and - // videowall setups will also show monitors that aren't in the logical groups the user - // cares about. - let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root); - available = Vec::with_capacity((*resources).ncrtc as usize); - for crtc_index in 0..(*resources).ncrtc { - let crtc_id = *((*resources).crtcs.offset(crtc_index as isize)); - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0; - if is_active { - let crtc = util::MonitorRepr::from(crtc); - let is_primary = crtc.get_output() == primary; - has_primary |= is_primary; - MonitorHandle::from_repr(self, resources, crtc_id as u32, crtc, is_primary) - .map(|monitor_id| available.push(monitor_id)); - } - (self.xrandr.XRRFreeCrtcInfo)(crtc); + MonitorHandle::new(self, resources, crtc_id, crtc, is_primary) + .map(|monitor_id| available.push(monitor_id)); } + (self.xrandr.XRRFreeCrtcInfo)(crtc); } // If no monitors were detected as being primary, we just pick one ourselves! @@ -236,19 +263,15 @@ impl XConnection { } pub fn select_xrandr_input(&self, root: Window) -> Result { - { - let mut version_lock = XRANDR_VERSION.lock(); - if version_lock.is_none() { - let mut major = 0; - let mut minor = 0; - let has_extension = - unsafe { (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) }; - if has_extension != True { - panic!("[winit] XRandR extension not available."); - } - *version_lock = Some((major, minor)); - } - } + let has_xrandr = unsafe { + let mut major = 0; + let mut minor = 0; + (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) + }; + assert!( + has_xrandr == True, + "[winit] XRandR extension not available." + ); let mut event_offset = 0; let mut error_offset = 0; diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 1e9a41c6..1fbef41d 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -1,7 +1,10 @@ use std::{env, slice, str::FromStr}; -use super::*; -use crate::{dpi::validate_hidpi_factor, monitor::VideoMode}; +use super::{ + ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources}, + *, +}; +use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode}; pub fn calc_dpi_factor( (width_px, height_px): (u32, u32), @@ -34,47 +37,6 @@ pub fn calc_dpi_factor( dpi_factor } -pub enum MonitorRepr { - Monitor(*mut ffi::XRRMonitorInfo), - Crtc(*mut ffi::XRRCrtcInfo), -} - -impl MonitorRepr { - pub unsafe fn get_output(&self) -> ffi::RROutput { - match *self { - // Same member names, but different locations within the struct... - MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)), - MonitorRepr::Crtc(crtc) => *((*crtc).outputs.offset(0)), - } - } - - pub unsafe fn size(&self) -> (u32, u32) { - match *self { - MonitorRepr::Monitor(monitor) => ((*monitor).width as u32, (*monitor).height as u32), - MonitorRepr::Crtc(crtc) => ((*crtc).width as u32, (*crtc).height as u32), - } - } - - pub unsafe fn position(&self) -> (i32, i32) { - match *self { - MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32), - MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32), - } - } -} - -impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr { - fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self { - MonitorRepr::Monitor(monitor) - } -} - -impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr { - fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self { - MonitorRepr::Crtc(crtc) - } -} - impl XConnection { // Retrieve DPI from Xft.dpi property pub unsafe fn get_xft_dpi(&self) -> Option { @@ -96,11 +58,11 @@ impl XConnection { } pub unsafe fn get_output_info( &self, - resources: *mut ffi::XRRScreenResources, - repr: &MonitorRepr, + resources: *mut XRRScreenResources, + crtc: *mut XRRCrtcInfo, ) -> Option<(String, f64, Vec)> { let output_info = - (self.xrandr.XRRGetOutputInfo)(self.display, resources, repr.get_output()); + (self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0)); if output_info.is_null() { // When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display) // it's possible for it to return null. @@ -132,6 +94,10 @@ impl XConnection { size: (x.width, x.height), refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16, bit_depth: bit_depth as u16, + native_mode: x.id, + // This is populated in `MonitorHandle::video_modes` as the + // video mode is returned to the user + monitor: None, } }); @@ -144,7 +110,7 @@ impl XConnection { dpi / 96. } else { calc_dpi_factor( - repr.size(), + ((*crtc).width as u32, (*crtc).height as u32), ( (*output_info).mm_width as u64, (*output_info).mm_height as u64, @@ -155,4 +121,61 @@ impl XConnection { (self.xrandr.XRRFreeOutputInfo)(output_info); Some((name, hidpi_factor, modes.collect())) } + pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> { + unsafe { + let mut major = 0; + let mut minor = 0; + (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); + + let root = (self.xlib.XDefaultRootWindow)(self.display); + let resources = if (major == 1 && minor >= 3) || major > 1 { + (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) + } else { + (self.xrandr.XRRGetScreenResources)(self.display, root) + }; + + let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); + let status = (self.xrandr.XRRSetCrtcConfig)( + self.display, + resources, + crtc_id, + CurrentTime, + (*crtc).x, + (*crtc).y, + mode_id, + (*crtc).rotation, + (*crtc).outputs.offset(0), + 1, + ); + + (self.xrandr.XRRFreeCrtcInfo)(crtc); + (self.xrandr.XRRFreeScreenResources)(resources); + + if status == Success as i32 { + Ok(()) + } else { + Err(()) + } + } + } + pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode { + unsafe { + let mut major = 0; + let mut minor = 0; + (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); + + let root = (self.xlib.XDefaultRootWindow)(self.display); + let resources = if (major == 1 && minor >= 3) || major > 1 { + (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) + } else { + (self.xrandr.XRRGetScreenResources)(self.display, root) + }; + + let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); + let mode = (*crtc).mode; + (self.xrandr.XRRFreeCrtcInfo)(crtc); + (self.xrandr.XRRFreeScreenResources)(resources); + mode + } + } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 33a16e02..e65d8836 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -16,12 +16,13 @@ use parking_lot::Mutex; use crate::{ dpi::{LogicalPosition, LogicalSize}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, - monitor::MonitorHandle as RootMonitorHandle, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::{ x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle}, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, + VideoMode as PlatformVideoMode, }, - window::{CursorIcon, Icon, WindowAttributes}, + window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, }; use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError}; @@ -46,9 +47,11 @@ pub struct SharedState { pub guessed_dpi: Option, pub last_monitor: Option, pub dpi_adjusted: Option<(f64, f64)>, - pub fullscreen: Option, - // Used to restore position after exiting fullscreen. + pub fullscreen: Option, + // Used to restore position after exiting fullscreen pub restore_position: Option<(i32, i32)>, + // 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, @@ -408,6 +411,7 @@ impl UnownedWindow { if window_attrs.fullscreen.is_some() { window .set_fullscreen_inner(window_attrs.fullscreen.clone()) + .unwrap() .queue(); } if window_attrs.always_on_top { @@ -564,41 +568,122 @@ impl UnownedWindow { self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0)) } - fn set_fullscreen_inner(&self, monitor: Option) -> util::Flusher<'_> { - match monitor { + fn set_fullscreen_inner(&self, fullscreen: Option) -> Option> { + let mut shared_state_lock = self.shared_state.lock(); + let old_fullscreen = shared_state_lock.fullscreen.clone(); + if old_fullscreen == fullscreen { + return None; + } + shared_state_lock.fullscreen = fullscreen.clone(); + + match (&old_fullscreen, &fullscreen) { + // Store the desktop video mode before entering exclusive + // fullscreen, so we can restore it upon exit, as XRandR does not + // provide a mechanism to set this per app-session or restore this + // to the desktop video mode as macOS and Windows do + ( + &None, + &Some(Fullscreen::Exclusive(RootVideoMode { + video_mode: PlatformVideoMode::X(ref video_mode), + })), + ) + | ( + &Some(Fullscreen::Borderless(_)), + &Some(Fullscreen::Exclusive(RootVideoMode { + video_mode: PlatformVideoMode::X(ref video_mode), + })), + ) => { + let monitor = video_mode.monitor.as_ref().unwrap(); + shared_state_lock.desktop_video_mode = + Some((monitor.id, self.xconn.get_crtc_mode(monitor.id))); + } + // Restore desktop video mode upon exiting exclusive fullscreen + (&Some(Fullscreen::Exclusive(_)), &None) + | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { + let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap(); + self.xconn + .set_crtc_config(monitor_id, mode_id) + .expect("failed to restore desktop video mode"); + } + _ => (), + } + + drop(shared_state_lock); + + match fullscreen { None => { let flusher = self.set_fullscreen_hint(false); - if let Some(position) = self.shared_state.lock().restore_position.take() { + let mut shared_state_lock = self.shared_state.lock(); + if let Some(position) = shared_state_lock.restore_position.take() { self.set_position_inner(position.0, position.1).queue(); } - flusher + Some(flusher) } - Some(RootMonitorHandle { - inner: PlatformMonitorHandle::X(monitor), - }) => { + Some(fullscreen) => { + 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), + _ => unreachable!(), + }; + + if let Some(video_mode) = video_mode { + // FIXME: this is actually not correct if we're setting the + // video mode to a resolution higher than the current + // desktop resolution, because XRandR does not automatically + // reposition the monitors to the right and below this + // monitor. + // + // What ends up happening is we will get the fullscreen + // window showing up on those monitors as well, because + // their virtual position now overlaps with the monitor that + // we just made larger.. + // + // It'd be quite a bit of work to handle this correctly (and + // nobody else seems to bother doing this correctly either), + // so we're just leaving this broken. Fixing this would + // involve storing all CRTCs upon entering fullscreen, + // restoring them upon exit, and after entering fullscreen, + // repositioning displays to the right and below this + // display. I think there would still be edge cases that are + // difficult or impossible to handle correctly, e.g. what if + // a new monitor was plugged in while in fullscreen? + // + // I think we might just want to disallow setting the video + // mode higher than the current desktop video mode (I'm sure + // this will make someone unhappy, but it's very unusual for + // games to want to do this anyway). + self.xconn + .set_crtc_config(monitor.id, video_mode.native_mode) + .expect("failed to set video mode"); + } + let window_position = self.outer_position_physical(); self.shared_state.lock().restore_position = Some(window_position); let monitor_origin: (i32, i32) = monitor.position().into(); self.set_position_inner(monitor_origin.0, monitor_origin.1) .queue(); - self.set_fullscreen_hint(true) + Some(self.set_fullscreen_hint(true)) } - _ => unreachable!(), } } #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { self.shared_state.lock().fullscreen.clone() } #[inline] - pub fn set_fullscreen(&self, monitor: Option) { - self.shared_state.lock().fullscreen = monitor.clone(); - self.set_fullscreen_inner(monitor) - .flush() - .expect("Failed to change window fullscreen state"); - self.invalidate_cached_frame_extents(); + pub fn set_fullscreen(&self, fullscreen: Option) { + if let Some(flusher) = self.set_fullscreen_inner(fullscreen) { + flusher + .flush() + .expect("Failed to change window fullscreen state"); + self.invalidate_cached_frame_extents(); + } } fn get_rect(&self) -> util::AaRect { diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 7ddcdf6f..aec0fc97 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -6,6 +6,13 @@ use cocoa::{ base::id, foundation::{NSInteger, NSUInteger}, }; +use core_foundation::{ + array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef, +}; +use core_graphics::{ + base::CGError, + display::{CGDirectDisplayID, CGDisplayConfigRef}, +}; use objc; pub const NSNotFound: NSInteger = NSInteger::max_value(); @@ -108,3 +115,95 @@ pub enum NSWindowLevel { NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _, NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _, } + +pub type CGDisplayFadeInterval = f32; +pub type CGDisplayReservationInterval = f32; +pub type CGDisplayBlendFraction = f32; + +pub const kCGDisplayBlendNormal: f32 = 0.0; +pub const kCGDisplayBlendSolidColor: f32 = 1.0; + +pub type CGDisplayFadeReservationToken = u32; +pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0; + +pub type Boolean = u8; +pub const FALSE: Boolean = 0; +pub const TRUE: Boolean = 1; + +pub const kCGErrorSuccess: i32 = 0; +pub const kCGErrorFailure: i32 = 1000; +pub const kCGErrorIllegalArgument: i32 = 1001; +pub const kCGErrorInvalidConnection: i32 = 1002; +pub const kCGErrorInvalidContext: i32 = 1003; +pub const kCGErrorCannotComplete: i32 = 1004; +pub const kCGErrorNotImplemented: i32 = 1006; +pub const kCGErrorRangeCheck: i32 = 1007; +pub const kCGErrorTypeCheck: i32 = 1008; +pub const kCGErrorInvalidOperation: i32 = 1010; +pub const kCGErrorNoneAvailable: i32 = 1011; + +pub const IO1BitIndexedPixels: &str = "P"; +pub const IO2BitIndexedPixels: &str = "PP"; +pub const IO4BitIndexedPixels: &str = "PPPP"; +pub const IO8BitIndexedPixels: &str = "PPPPPPPP"; +pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB"; +pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB"; + +pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB"; +pub const kIO64BitDirectPixels: &str = "-16R16G16B16"; + +pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16"; +pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32"; + +pub const IOYUV422Pixels: &str = "Y4U2V2"; +pub const IO8BitOverlayPixels: &str = "O8"; + +pub type CGWindowLevel = i32; +pub type CGDisplayModeRef = *mut libc::c_void; + +#[link(name = "CoreGraphics", kind = "framework")] +extern "C" { + pub fn CGRestorePermanentDisplayConfiguration(); + pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError; + pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError; + pub fn CGConfigureDisplayFadeEffect( + config: CGDisplayConfigRef, + fadeOutSeconds: CGDisplayFadeInterval, + fadeInSeconds: CGDisplayFadeInterval, + fadeRed: f32, + fadeGreen: f32, + fadeBlue: f32, + ) -> CGError; + pub fn CGAcquireDisplayFadeReservation( + seconds: CGDisplayReservationInterval, + token: *mut CGDisplayFadeReservationToken, + ) -> CGError; + pub fn CGDisplayFade( + token: CGDisplayFadeReservationToken, + duration: CGDisplayFadeInterval, + startBlend: CGDisplayBlendFraction, + endBlend: CGDisplayBlendFraction, + redBlend: f32, + greenBlend: f32, + blueBlend: f32, + synchronous: Boolean, + ) -> CGError; + pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError; + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; + pub fn CGShieldingWindowLevel() -> CGWindowLevel; + pub fn CGDisplaySetDisplayMode( + display: CGDirectDisplayID, + mode: CGDisplayModeRef, + options: CFDictionaryRef, + ) -> CGError; + pub fn CGDisplayCopyAllDisplayModes( + display: CGDirectDisplayID, + options: CFDictionaryRef, + ) -> CFArrayRef; + pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize; + pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize; + pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64; + pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef; + pub fn CGDisplayModeRetain(mode: CGDisplayModeRef); + pub fn CGDisplayModeRelease(mode: CGDisplayModeRef); +} diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 02b63515..c26385da 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -17,7 +17,7 @@ use std::{fmt, ops::Deref, sync::Arc}; pub use self::{ event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow}, }; use crate::{ diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index cf74c644..c9408890 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -1,25 +1,119 @@ use std::{collections::VecDeque, fmt}; +use super::ffi; +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}, }; -use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayMode}; +use core_foundation::{ + array::{CFArrayGetCount, CFArrayGetValueAtIndex}, + base::{CFRelease, TCFType}, + string::CFString, +}; +use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}; use core_video_sys::{ kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay, CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease, }; -use crate::{ - dpi::{PhysicalPosition, PhysicalSize}, - monitor::VideoMode, - platform_impl::platform::util::IdRef, -}; +#[derive(Derivative)] +#[derivative(Debug, Clone, PartialEq, Hash)] +pub struct VideoMode { + pub(crate) size: (u32, u32), + pub(crate) bit_depth: u16, + pub(crate) refresh_rate: u16, + pub(crate) monitor: MonitorHandle, + #[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")] + pub(crate) native_mode: NativeDisplayMode, +} -#[derive(Clone, PartialEq)] +pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef); + +unsafe impl Send for NativeDisplayMode {} + +impl Drop for NativeDisplayMode { + fn drop(&mut self) { + unsafe { + ffi::CGDisplayModeRelease(self.0); + } + } +} + +impl Clone for NativeDisplayMode { + fn clone(&self) -> Self { + unsafe { + ffi::CGDisplayModeRetain(self.0); + } + NativeDisplayMode(self.0) + } +} + +impl VideoMode { + pub fn size(&self) -> PhysicalSize { + self.size.into() + } + + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> RootMonitorHandle { + RootMonitorHandle { + inner: self.monitor.clone(), + } + } +} + +#[derive(Clone)] pub struct MonitorHandle(CGDirectDisplayID); +// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that +// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an +// unique identifier that persists even across system reboots +impl PartialEq for MonitorHandle { + fn eq(&self, other: &Self) -> bool { + unsafe { + ffi::CGDisplayCreateUUIDFromDisplayID(self.0) + == ffi::CGDisplayCreateUUIDFromDisplayID(other.0) + } + } +} + +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 { + unsafe { + ffi::CGDisplayCreateUUIDFromDisplayID(self.0) + .cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0)) + } + } +} + +impl std::hash::Hash for MonitorHandle { + fn hash(&self, state: &mut H) { + unsafe { + ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state); + } + } +} + pub fn available_monitors() -> VecDeque { if let Ok(displays) = CGDisplay::active_displays() { let mut monitors = VecDeque::with_capacity(displays.len()); @@ -101,7 +195,7 @@ impl MonitorHandle { unsafe { NSScreen::backingScaleFactor(screen) as f64 } } - pub fn video_modes(&self) -> impl Iterator { + pub fn video_modes(&self) -> impl Iterator { let cv_refresh_rate = unsafe { let mut display_link = std::ptr::null_mut(); assert_eq!( @@ -117,11 +211,27 @@ impl MonitorHandle { time.timeScale as i64 / time.timeValue }; - CGDisplayMode::all_display_modes(self.0, std::ptr::null()) - .expect("failed to obtain list of display modes") - .into_iter() - .map(move |mode| { - let cg_refresh_rate = mode.refresh_rate().round() as i64; + let monitor = self.clone(); + + unsafe { + let modes = { + let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null()); + assert!(!array.is_null(), "failed to get list of display modes"); + let array_count = CFArrayGetCount(array); + let modes: Vec<_> = (0..array_count) + .into_iter() + .map(move |i| { + let mode = CFArrayGetValueAtIndex(array, i) as *mut _; + ffi::CGDisplayModeRetain(mode); + mode + }) + .collect(); + CFRelease(array as *const _); + modes + }; + + modes.into_iter().map(move |mode| { + let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; // CGDisplayModeGetRefreshRate returns 0.0 for any display that // isn't a CRT @@ -131,34 +241,55 @@ impl MonitorHandle { cv_refresh_rate }; - VideoMode { - size: (mode.width() as u32, mode.height() as u32), + let pixel_encoding = + CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode)) + .to_string(); + let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) { + 32 + } else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) { + 16 + } else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) { + 30 + } else { + unimplemented!() + }; + + let video_mode = VideoMode { + size: ( + ffi::CGDisplayModeGetPixelWidth(mode) as u32, + ffi::CGDisplayModeGetPixelHeight(mode) as u32, + ), refresh_rate: refresh_rate as u16, - bit_depth: mode.bit_depth() as u16, - } + bit_depth, + monitor: monitor.clone(), + native_mode: NativeDisplayMode(mode), + }; + + RootVideoMode { video_mode } }) + } } pub(crate) fn ns_screen(&self) -> Option { unsafe { - let native_id = self.native_identifier(); + 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 mut matching_screen: Option = None; for i in 0..count { let screen = msg_send![screens, objectAtIndex: i as NSUInteger]; let device_description = NSScreen::deviceDescription(screen); let value: id = msg_send![device_description, objectForKey:*key]; if value != nil { - let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue]; - if screen_number as u32 == native_id { - matching_screen = Some(screen); - break; + let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue]; + let other_uuid = + ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID); + if uuid == other_uuid { + return Some(screen); } } } - matching_screen + None } } } diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index db7a61d7..661ec82e 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -1,6 +1,6 @@ use std::{self, os::raw::*, ptr, time::Instant}; -use crate::platform_impl::platform::app_state::AppState; +use crate::platform_impl::platform::{app_state::AppState, ffi}; #[link(name = "CoreFoundation", kind = "framework")] extern "C" { @@ -13,7 +13,7 @@ extern "C" { pub fn CFRunLoopObserverCreate( allocator: CFAllocatorRef, activities: CFOptionFlags, - repeats: Boolean, + repeats: ffi::Boolean, order: CFIndex, callout: CFRunLoopObserverCallBack, context: *mut CFRunLoopObserverContext, @@ -51,11 +51,6 @@ extern "C" { pub fn CFRelease(cftype: *const c_void); } -pub type Boolean = u8; -#[allow(dead_code)] -const FALSE: Boolean = 0; -const TRUE: Boolean = 1; - pub enum CFAllocator {} pub type CFAllocatorRef = *mut CFAllocator; pub enum CFRunLoop {} @@ -102,7 +97,7 @@ pub struct CFRunLoopSourceContext { pub retain: extern "C" fn(*const c_void) -> *const c_void, pub release: extern "C" fn(*const c_void), pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, - pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, + pub equal: extern "C" fn(*const c_void, *const c_void) -> ffi::Boolean, pub hash: extern "C" fn(*const c_void) -> CFHashCode, pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), @@ -162,8 +157,8 @@ impl RunLoop { let observer = CFRunLoopObserverCreate( ptr::null_mut(), flags, - TRUE, // Indicates we want this to run repeatedly - priority, // The lower the value, the sooner this will run + ffi::TRUE, // Indicates we want this to run repeatedly + priority, // The lower the value, the sooner this will run handler, ptr::null_mut(), ); diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index bb8655fc..d7fe9592 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -206,7 +206,10 @@ extern "C" fn toggle_full_screen_callback(context: *mut c_void) { } } } - + // 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); diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index a99e7f6c..d0b0a557 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -8,6 +8,23 @@ use std::{ }, }; +use crate::{ + dpi::{LogicalPosition, LogicalSize}, + error::{ExternalError, NotSupportedError, OsError as RootOsError}, + icon::Icon, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, + platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, + platform_impl::platform::{ + app_state::AppState, + ffi, + monitor::{self, MonitorHandle, VideoMode}, + util::{self, IdRef}, + view::{self, new_view}, + window_delegate::new_delegate, + OsError, + }, + window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId}, +}; use cocoa::{ appkit::{ self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy, @@ -17,30 +34,12 @@ use cocoa::{ base::{id, nil}, foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, }; -use core_graphics::display::CGDisplay; +use core_graphics::display::{CGDisplay, CGDisplayMode}; use objc::{ declare::ClassDecl, runtime::{Class, Object, Sel, BOOL, NO, YES}, }; -use crate::{ - dpi::{LogicalPosition, LogicalSize}, - error::{ExternalError, NotSupportedError, OsError as RootOsError}, - icon::Icon, - monitor::MonitorHandle as RootMonitorHandle, - platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS}, - platform_impl::platform::{ - app_state::AppState, - ffi, - monitor::{self, MonitorHandle}, - util::{self, IdRef}, - view::{self, new_view}, - window_delegate::new_delegate, - OsError, - }, - window::{CursorIcon, WindowAttributes, WindowId as RootWindowId}, -}; - #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id(pub usize); @@ -119,11 +118,14 @@ fn create_window( unsafe { let pool = NSAutoreleasePool::new(nil); let screen = match attrs.fullscreen { - Some(ref monitor_id) => { - let monitor_screen = monitor_id.inner.ns_screen(); + Some(Fullscreen::Borderless(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))) } - _ => None, + None => None, }; let frame = match screen { Some(screen) => appkit::NSScreen::frame(screen), @@ -239,12 +241,15 @@ lazy_static! { #[derive(Default)] pub struct SharedState { pub resizable: bool, - pub fullscreen: Option, + pub fullscreen: Option, pub maximized: bool, pub standard_frame: Option, is_simple_fullscreen: bool, pub saved_style: Option, + /// Presentation options saved before entering `set_simple_fullscreen`, and + /// restored upon exiting it save_presentation_opts: Option, + pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>, } impl SharedState { @@ -362,16 +367,7 @@ impl UnownedWindow { let delegate = new_delegate(&window, fullscreen.is_some()); // Set fullscreen mode after we setup everything - if let Some(monitor) = fullscreen { - if monitor.inner != window.current_monitor().inner { - // To do this with native fullscreen, we probably need to - // warp the window... while we could use - // `enterFullScreenMode`, they're idiomatically different - // fullscreen modes, so we'd have to support both anyway. - unimplemented!(); - } - window.set_fullscreen(Some(monitor)); - } + window.set_fullscreen(fullscreen); // Setting the window as key has to happen *after* we set the fullscreen // state, since otherwise we'll briefly see the window at normal size @@ -601,22 +597,44 @@ impl UnownedWindow { } } + /// This is called when the window is exiting fullscreen, whether by the + /// user clicking on the green fullscreen button or programmatically by + /// `toggleFullScreen:` pub(crate) fn restore_state_from_fullscreen(&self) { - let maximized = { - trace!("Locked shared state in `restore_state_from_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); + trace!("Locked shared state in `restore_state_from_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.fullscreen = None; + shared_state_lock.fullscreen = None; - let mask = self.saved_style(&mut *shared_state_lock); + let maximized = shared_state_lock.maximized; + let mask = self.saved_style(&mut *shared_state_lock); - self.set_style_mask_async(mask); - shared_state_lock.maximized - }; + drop(shared_state_lock); trace!("Unocked shared state in `restore_state_from_fullscreen`"); + + self.set_style_mask_async(mask); self.set_maximized(maximized); } + fn restore_display_mode(&self) { + trace!("Locked shared state in `restore_display_mode`"); + let shared_state_lock = self.shared_state.lock().unwrap(); + + if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) = + shared_state_lock.fullscreen + { + unsafe { + ffi::CGRestorePermanentDisplayConfiguration(); + assert_eq!( + ffi::CGDisplayRelease(video_mode.monitor().inner.native_identifier()), + ffi::kCGErrorSuccess + ); + } + } + + trace!("Unlocked shared state in `restore_display_mode`"); + } + #[inline] pub fn set_maximized(&self, maximized: bool) { let is_zoomed = self.is_zoomed(); @@ -634,44 +652,159 @@ impl UnownedWindow { } #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { let shared_state_lock = self.shared_state.lock().unwrap(); shared_state_lock.fullscreen.clone() } #[inline] - /// TODO: Right now set_fullscreen do not work on switching monitors - /// in fullscreen mode - pub fn set_fullscreen(&self, monitor: Option) { + pub fn set_fullscreen(&self, fullscreen: Option) { + trace!("Locked shared state in `set_fullscreen`"); let shared_state_lock = self.shared_state.lock().unwrap(); if shared_state_lock.is_simple_fullscreen { + trace!("Unlocked shared state in `set_fullscreen`"); return; } - - let not_fullscreen = { - trace!("Locked shared state in `set_fullscreen`"); - let current = &shared_state_lock.fullscreen; - match (current, monitor) { - (&Some(ref a), Some(ref b)) if a.inner != b.inner => { - // Our best bet is probably to move to the origin of the - // target monitor. - unimplemented!() - } - (&None, None) | (&Some(_), Some(_)) => return, - _ => (), - } + let old_fullscreen = shared_state_lock.fullscreen.clone(); + if fullscreen == old_fullscreen { trace!("Unlocked shared state in `set_fullscreen`"); - current.is_none() - }; + return; + } + trace!("Unlocked shared state in `set_fullscreen`"); + drop(shared_state_lock); - unsafe { - util::toggle_full_screen_async( - *self.ns_window, - *self.ns_view, - not_fullscreen, - Arc::downgrade(&self.shared_state), - ) - }; + // If the fullscreen is on a different monitor, we must move the window + // to that monitor before we toggle fullscreen (as `toggleFullScreen` + // 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::Exclusive(RootVideoMode { + video_mode: VideoMode { ref monitor, .. }, + }) => monitor, + } + .ns_screen() + .unwrap(); + + unsafe { + let old_screen = NSWindow::screen(*self.ns_window); + if old_screen != new_screen { + let mut screen_frame: NSRect = msg_send![new_screen, frame]; + // The coordinate system here has its origin at bottom-left + // and Y goes up + screen_frame.origin.y += screen_frame.size.height; + util::set_frame_top_left_point_async(*self.ns_window, screen_frame.origin); + } + } + } + + if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen { + // Note: `enterFullScreenMode:withOptions:` seems to do the exact + // same thing as we're doing here (captures the display, sets the + // video mode, and hides the menu bar and dock), with the exception + // of that I couldn't figure out how to set the display mode with + // it. I think `enterFullScreenMode:withOptions:` is still using the + // older display mode API where display modes were of the type + // `CFDictionary`, but this has changed, so we can't obtain the + // correct parameter for this any longer. Apple's code samples for + // this function seem to just pass in "YES" for the display mode + // parameter, which is not consistent with the docs saying that it + // takes a `NSDictionary`.. + + let display_id = video_mode.monitor().inner.native_identifier(); + + let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken; + + unsafe { + // Fade to black (and wait for the fade to complete) to hide the + // flicker from capturing the display and switching display mode + if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token) + == ffi::kCGErrorSuccess + { + ffi::CGDisplayFade( + fade_token, + 0.3, + ffi::kCGDisplayBlendNormal, + ffi::kCGDisplayBlendSolidColor, + 0.0, + 0.0, + 0.0, + ffi::TRUE, + ); + } + + assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess); + } + + unsafe { + let result = ffi::CGDisplaySetDisplayMode( + display_id, + video_mode.video_mode.native_mode.0, + std::ptr::null(), + ); + assert!(result == ffi::kCGErrorSuccess, "failed to set video mode"); + + // After the display has been configured, fade back in + // asynchronously + if fade_token != ffi::kCGDisplayFadeReservationInvalidToken { + ffi::CGDisplayFade( + fade_token, + 0.6, + ffi::kCGDisplayBlendSolidColor, + ffi::kCGDisplayBlendNormal, + 0.0, + 0.0, + 0.0, + ffi::FALSE, + ); + ffi::CGReleaseDisplayFadeReservation(fade_token); + } + } + } + + match (&old_fullscreen, &fullscreen) { + (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe { + // If we're already in fullscreen mode, calling + // `CGDisplayCapture` will place the shielding window on top of + // our window, which results in a black display and is not what + // we want. So, we must place our window on top of the shielding + // window. Unfortunately, this also makes our window be on top + // of the menu bar, and this looks broken, so we must make sure + // that the menu bar is disabled. This is done in the window + // delegate in `window:willUseFullScreenPresentationOptions:`. + msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1]; + }, + (&Some(Fullscreen::Exclusive(_)), &None) => unsafe { + self.restore_display_mode(); + + util::toggle_full_screen_async( + *self.ns_window, + *self.ns_view, + old_fullscreen.is_none(), + Arc::downgrade(&self.shared_state), + ); + }, + (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { + self.restore_display_mode(); + } + (&None, &Some(Fullscreen::Exclusive(_))) + | (&None, &Some(Fullscreen::Borderless(_))) + | (&Some(Fullscreen::Borderless(_)), &None) => unsafe { + // Wish it were this simple for all cases + util::toggle_full_screen_async( + *self.ns_window, + *self.ns_view, + old_fullscreen.is_none(), + Arc::downgrade(&self.shared_state), + ); + }, + _ => (), + } + + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.fullscreen = fullscreen.clone(); + trace!("Unlocked shared state in `set_fullscreen`"); } #[inline] diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index c9680d89..9bce9ce7 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -5,9 +5,9 @@ use std::{ }; use cocoa::{ - appkit::{self, NSView, NSWindow}, + appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow}, base::{id, nil}, - foundation::NSAutoreleasePool, + foundation::{NSAutoreleasePool, NSUInteger}, }; use objc::{ declare::ClassDecl, @@ -22,7 +22,7 @@ use crate::{ util::{self, IdRef}, window::{get_window_id, UnownedWindow}, }, - window::WindowId, + window::{Fullscreen, WindowId}, }; pub struct WindowDelegateState { @@ -182,6 +182,11 @@ lazy_static! { dragging_exited as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(window:willUseFullScreenPresentationOptions:), + window_will_use_fullscreen_presentation_options + as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger, + ); decl.add_method( sel!(windowDidEnterFullScreen:), window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id), @@ -408,6 +413,26 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { trace!("Completed `windowWillEnterFullscreen:`"); } +extern "C" fn window_will_use_fullscreen_presentation_options( + _this: &Object, + _: Sel, + _: id, + _proposed_options: NSUInteger, +) -> NSUInteger { + // Generally, games will want to disable the menu bar and the dock. Ideally, + // this would be configurable by the user. Unfortunately because of our + // `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is + // placed on top of the menu bar in exclusive fullscreen mode. This looks + // broken so we always disable the menu bar in exclusive fullscreen. We may + // still want to make this configurable for borderless fullscreen. Right now + // we don't, for consistency. If we do, it should be documented that the + // user-provided options are ignored in exclusive fullscreen. + (NSApplicationPresentationOptions::NSApplicationPresentationFullScreen + | NSApplicationPresentationOptions::NSApplicationPresentationHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar) + .bits() +} + /// Invoked when entered fullscreen extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { trace!("Triggered `windowDidEnterFullscreen:`"); @@ -415,8 +440,21 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { state.with_window(|window| { let monitor = window.current_monitor(); trace!("Locked shared state in `window_did_enter_fullscreen`"); - window.shared_state.lock().unwrap().fullscreen = Some(monitor); - trace!("Unlocked shared state in `window_will_enter_fullscreen`"); + let mut shared_state = window.shared_state.lock().unwrap(); + match shared_state.fullscreen { + // Exclusive mode sets the state in `set_fullscreen` as the user + // can't enter exclusive mode by other means (like the + // fullscreen button on the window decorations) + Some(Fullscreen::Exclusive(_)) => (), + // `window_did_enter_fullscreen` was triggered and we're already + // in fullscreen, so we must've reached here by `set_fullscreen` + // as it updates the state + Some(Fullscreen::Borderless(_)) => (), + // Otherwise, we must've reached fullscreen by the user clicking + // on the green fullscreen button. Update state! + None => shared_state.fullscreen = Some(Fullscreen::Borderless(monitor)), + } + trace!("Unlocked shared state in `window_did_enter_fullscreen`"); }); state.initial_fullscreen = false; }); diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 6216fa29..34e9327d 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -4,7 +4,7 @@ use winapi::{self, shared::windef::HWND}; pub use self::{ event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}, - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::Window, }; diff --git a/src/platform_impl/windows/monitor.rs b/src/platform_impl/windows/monitor.rs index addb0175..cc2e1646 100644 --- a/src/platform_impl/windows/monitor.rs +++ b/src/platform_impl/windows/monitor.rs @@ -3,54 +3,64 @@ use winapi::{ minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD}, windef::{HDC, HMONITOR, HWND, LPRECT, POINT}, }, - um::{wingdi, winnt::LONG, winuser}, + um::{wingdi, winuser}, }; use std::{ - collections::{HashSet, VecDeque}, + collections::{BTreeSet, VecDeque}, io, mem, ptr, }; use super::{util, EventLoop}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - monitor::VideoMode, + monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::platform::{ dpi::{dpi_to_scale_factor, get_monitor_dpi}, window::Window, }, }; -/// Win32 implementation of the main `MonitorHandle` object. #[derive(Derivative)] -#[derivative(Debug, Clone)] -pub struct MonitorHandle { - /// Monitor handle. - hmonitor: HMonitor, - #[derivative(Debug = "ignore")] - monitor_info: winuser::MONITORINFOEXW, - /// The system name of the monitor. - monitor_name: String, - /// True if this is the primary monitor. - primary: bool, - /// The position of the monitor in pixels on the desktop. - /// - /// A window that is positioned at these coordinates will overlap the monitor. - position: (i32, i32), - /// The current resolution in pixels on the monitor. - dimensions: (u32, u32), - /// DPI scale factor. - hidpi_factor: f64, +#[derivative(Debug, Clone, Eq, PartialEq, Hash)] +pub struct VideoMode { + pub(crate) size: (u32, u32), + pub(crate) bit_depth: u16, + pub(crate) refresh_rate: u16, + pub(crate) monitor: MonitorHandle, + #[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")] + pub(crate) native_video_mode: wingdi::DEVMODEW, } +impl VideoMode { + pub fn size(&self) -> PhysicalSize { + self.size.into() + } + + pub fn bit_depth(&self) -> u16 { + self.bit_depth + } + + pub fn refresh_rate(&self) -> u16 { + self.refresh_rate + } + + pub fn monitor(&self) -> RootMonitorHandle { + RootMonitorHandle { + inner: self.monitor.clone(), + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] +pub struct MonitorHandle(HMONITOR); + // Send is not implemented for HMONITOR, we have to wrap it and implement it manually. // For more info see: // https://github.com/retep998/winapi-rs/issues/360 // https://github.com/retep998/winapi-rs/issues/396 -#[derive(Debug, Clone)] -struct HMonitor(HMONITOR); -unsafe impl Send for HMonitor {} +unsafe impl Send for MonitorHandle {} unsafe extern "system" fn monitor_enum_proc( hmonitor: HMONITOR, @@ -59,7 +69,7 @@ unsafe extern "system" fn monitor_enum_proc( data: LPARAM, ) -> BOOL { let monitors = data as *mut VecDeque; - (*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor)); + (*monitors).push_back(MonitorHandle::new(hmonitor)); TRUE // continue enumeration } @@ -79,12 +89,12 @@ pub fn available_monitors() -> VecDeque { pub fn primary_monitor() -> MonitorHandle { const ORIGIN: POINT = POINT { x: 0, y: 0 }; let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) }; - MonitorHandle::from_hmonitor(hmonitor) + MonitorHandle::new(hmonitor) } pub fn current_monitor(hwnd: HWND) -> MonitorHandle { let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) }; - MonitorHandle::from_hmonitor(hmonitor) + MonitorHandle::new(hmonitor) } impl EventLoop { @@ -125,73 +135,69 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result Self { - let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed"); - let place = monitor_info.rcMonitor; - let dimensions = ( - (place.right - place.left) as u32, - (place.bottom - place.top) as u32, - ); - MonitorHandle { - hmonitor: HMonitor(hmonitor), - monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()), - primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY), - position: (place.left as i32, place.top as i32), - dimensions, - hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)), - monitor_info, - } + pub(crate) fn new(hmonitor: HMONITOR) -> Self { + MonitorHandle(hmonitor) } pub(crate) fn contains_point(&self, point: &POINT) -> bool { - let left = self.position.0 as LONG; - let right = left + self.dimensions.0 as LONG; - let top = self.position.1 as LONG; - let bottom = top + self.dimensions.1 as LONG; - point.x >= left && point.x <= right && point.y >= top && point.y <= bottom + 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 { - Some(self.monitor_name.clone()) + let monitor_info = get_monitor_info(self.0).unwrap(); + Some(util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr())) } #[inline] pub fn native_identifier(&self) -> String { - self.monitor_name.clone() + self.name().unwrap() } #[inline] pub fn hmonitor(&self) -> HMONITOR { - self.hmonitor.0 + self.0 } #[inline] pub fn size(&self) -> PhysicalSize { - self.dimensions.into() + 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, + } } #[inline] pub fn position(&self) -> PhysicalPosition { - self.position.into() + let monitor_info = get_monitor_info(self.0).unwrap(); + PhysicalPosition { + x: monitor_info.rcMonitor.left as f64, + y: monitor_info.rcMonitor.top as f64, + } } #[inline] pub fn hidpi_factor(&self) -> f64 { - self.hidpi_factor + dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96)) } #[inline] - pub fn video_modes(&self) -> impl Iterator { + pub fn video_modes(&self) -> impl Iterator { // EnumDisplaySettingsExW can return duplicate values (or some of the // fields are probably changing, but we aren't looking at those fields - // anyway), so we're using a HashSet deduplicate - let mut modes = HashSet::new(); + // anyway), so we're using a BTreeSet deduplicate + let mut modes = BTreeSet::new(); let mut i = 0; loop { unsafe { - let device_name = self.monitor_info.szDevice.as_ptr(); + let monitor_info = get_monitor_info(self.0).unwrap(); + let device_name = monitor_info.szDevice.as_ptr(); let mut mode: wingdi::DEVMODEW = mem::zeroed(); mode.dmSize = mem::size_of_val(&mode) as WORD; if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 { @@ -205,10 +211,14 @@ impl MonitorHandle { | wingdi::DM_DISPLAYFREQUENCY; assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS); - modes.insert(VideoMode { - size: (mode.dmPelsWidth, mode.dmPelsHeight), - bit_depth: mode.dmBitsPerPel as u16, - refresh_rate: mode.dmDisplayFrequency as u16, + modes.insert(RootVideoMode { + video_mode: VideoMode { + size: (mode.dmPelsWidth, mode.dmPelsHeight), + bit_depth: mode.dmBitsPerPel as u16, + refresh_rate: mode.dmDisplayFrequency as u16, + monitor: self.clone(), + native_video_mode: mode, + }, }); } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 577ecfd7..39f6b478 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -46,7 +46,7 @@ use crate::{ window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, - window::{CursorIcon, Icon, WindowAttributes}, + window::{CursorIcon, Fullscreen, Icon, WindowAttributes}, }; /// The Win32 implementation of the main `Window` object. @@ -327,7 +327,7 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), window.0, None, |f| { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set(WindowFlags::RESIZABLE, resizable) }); }); @@ -421,80 +421,177 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), window.0, None, |f| { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set(WindowFlags::MAXIMIZED, maximized) }); }); } #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { let window_state = self.window_state.lock(); window_state.fullscreen.clone() } #[inline] - pub fn set_fullscreen(&self, monitor: Option) { - unsafe { - let window = self.window.clone(); - let window_state = Arc::clone(&self.window_state); + pub fn set_fullscreen(&self, fullscreen: Option) { + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); - match &monitor { - &Some(RootMonitorHandle { ref inner }) => { - let (x, y): (i32, i32) = inner.position().into(); - let (width, height): (u32, u32) = inner.size().into(); + let mut window_state_lock = window_state.lock(); + let old_fullscreen = window_state_lock.fullscreen.clone(); + if window_state_lock.fullscreen == fullscreen { + return; + } + window_state_lock.fullscreen = fullscreen.clone(); + drop(window_state_lock); - let mut monitor = monitor.clone(); - self.thread_executor.execute_in_thread(move || { - let mut window_state_lock = window_state.lock(); + self.thread_executor.execute_in_thread(move || { + let mut window_state_lock = window_state.lock(); - let client_rect = - util::get_client_rect(window.0).expect("get client rect failed!"); - window_state_lock.saved_window = Some(SavedWindow { - client_rect, - dpi_factor: window_state_lock.dpi_factor, - }); - - window_state_lock.fullscreen = monitor.take(); - WindowState::refresh_window_state( - window_state_lock, - window.0, - Some(RECT { - left: x, - top: y, - right: x + width as c_int, - bottom: y + height as c_int, - }), - ); - - mark_fullscreen(window.0, true); + // 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, + dpi_factor: window_state_lock.dpi_factor, }); } - &None => { - self.thread_executor.execute_in_thread(move || { - let mut window_state_lock = window_state.lock(); - window_state_lock.fullscreen = None; + _ => (), + } - if let Some(SavedWindow { - client_rect, - dpi_factor, - }) = window_state_lock.saved_window - { - window_state_lock.dpi_factor = dpi_factor; - window_state_lock.saved_window = None; + // Change video mode if we're transitioning to or from exclusive + // fullscreen + match (&old_fullscreen, &fullscreen) { + (&None, &Some(Fullscreen::Exclusive(ref video_mode))) + | ( + &Some(Fullscreen::Borderless(_)), + &Some(Fullscreen::Exclusive(ref video_mode)), + ) + | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) => + { + let monitor = video_mode.monitor(); - WindowState::refresh_window_state( - window_state_lock, + let mut display_name = OsStr::new(&monitor.inner.native_identifier()) + .encode_wide() + .collect::>(); + // `encode_wide` does not add a null-terminator but + // `ChangeDisplaySettingsExW` requires a null-terminated + // string, so add it + display_name.push(0); + + let mut native_video_mode = video_mode.video_mode.native_video_mode.clone(); + + let res = unsafe { + winuser::ChangeDisplaySettingsExW( + display_name.as_ptr(), + &mut native_video_mode, + std::ptr::null_mut(), + winuser::CDS_FULLSCREEN, + std::ptr::null_mut(), + ) + }; + + debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS); + debug_assert!(res != winuser::DISP_CHANGE_BADMODE); + debug_assert!(res != winuser::DISP_CHANGE_BADPARAM); + debug_assert!(res != winuser::DISP_CHANGE_FAILED); + assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL); + } + (&Some(Fullscreen::Exclusive(_)), &None) + | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { + let res = unsafe { + winuser::ChangeDisplaySettingsExW( + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + winuser::CDS_FULLSCREEN, + std::ptr::null_mut(), + ) + }; + + debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS); + debug_assert!(res != winuser::DISP_CHANGE_BADMODE); + debug_assert!(res != winuser::DISP_CHANGE_BADPARAM); + debug_assert!(res != winuser::DISP_CHANGE_FAILED); + assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL); + } + _ => (), + } + + unsafe { + // There are some scenarios where calling `ChangeDisplaySettingsExW` takes long + // enough to execute that the DWM thinks our program has frozen and takes over + // our program's window. When that happens, the `SetWindowPos` call below gets + // eaten and the window doesn't get set to the proper fullscreen position. + // + // Calling `PeekMessageW` here notifies Windows that our process is still running + // fine, taking control back from the DWM and ensuring that the `SetWindowPos` call + // below goes through. + let mut msg = mem::zeroed(); + winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0); + } + + // Update window style + WindowState::set_window_flags(window_state_lock, window.0, |f| { + f.set(WindowFlags::MARKER_FULLSCREEN, fullscreen.is_some()) + }); + + // 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 position: (i32, i32) = monitor.position().into(); + let size: (u32, u32) = monitor.size().into(); + + unsafe { + winuser::SetWindowPos( + window.0, + ptr::null_mut(), + position.0, + position.1, + size.0 as i32, + size.1 as i32, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER, + ); + winuser::UpdateWindow(window.0); + } + } + None => { + let mut window_state_lock = window_state.lock(); + if let Some(SavedWindow { + client_rect, + dpi_factor, + }) = window_state_lock.saved_window.take() + { + window_state_lock.dpi_factor = dpi_factor; + drop(window_state_lock); + + unsafe { + winuser::SetWindowPos( window.0, - Some(client_rect), + 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::UpdateWindow(window.0); } - - mark_fullscreen(window.0, false); - }); + } } } - } + + unsafe { + taskbar_mark_fullscreen(window.0, fullscreen.is_some()); + } + }); } #[inline] @@ -503,8 +600,7 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - let client_rect = util::get_client_rect(window.0).expect("get client rect failed!"); - WindowState::set_window_flags(window_state.lock(), window.0, Some(client_rect), |f| { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set(WindowFlags::DECORATIONS, decorations) }); }); @@ -516,7 +612,7 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), window.0, None, |f| { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top) }); }); @@ -769,9 +865,7 @@ unsafe fn init( let window_state = { let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor); let window_state = Arc::new(Mutex::new(window_state)); - WindowState::set_window_flags(window_state.lock(), real_window.0, None, |f| { - *f = window_flags - }); + WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); window_state }; @@ -865,7 +959,7 @@ pub fn com_initialized() { // is activated. If the window is not fullscreen, the Shell falls back to // heuristics to determine how the window should be treated, which means // that it could still consider the window as fullscreen. :( -unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) { +unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) { com_initialized(); TASKBAR_LIST.with(|task_bar_list_ptr| { diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 9d494508..5979f777 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,8 +1,7 @@ use crate::{ dpi::LogicalSize, - monitor::MonitorHandle, platform_impl::platform::{event_loop, icon::WinIcon, util}, - window::{CursorIcon, WindowAttributes}, + window::{CursorIcon, Fullscreen, WindowAttributes}, }; use parking_lot::MutexGuard; use std::{io, ptr}; @@ -29,7 +28,7 @@ pub struct WindowState { pub saved_window: Option, pub dpi_factor: f64, - pub fullscreen: Option, + pub fullscreen: Option, /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple /// times in `EventsCleared`. pub queued_out_of_band_redraw: bool, @@ -84,6 +83,7 @@ bitflags! { WindowFlags::RESIZABLE.bits | WindowFlags::MAXIMIZED.bits ); + const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits; const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; } @@ -122,32 +122,16 @@ impl WindowState { self.window_flags } - pub fn set_window_flags( - mut this: MutexGuard<'_, Self>, - window: HWND, - set_client_rect: Option, - f: F, - ) where + pub fn set_window_flags(mut this: MutexGuard<'_, Self>, window: HWND, f: F) + where F: FnOnce(&mut WindowFlags), { let old_flags = this.window_flags; f(&mut this.window_flags); - - let is_fullscreen = this.fullscreen.is_some(); - this.window_flags - .set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen); let new_flags = this.window_flags; drop(this); - old_flags.apply_diff(window, new_flags, set_client_rect); - } - - pub fn refresh_window_state( - this: MutexGuard<'_, Self>, - window: HWND, - set_client_rect: Option, - ) { - Self::set_window_flags(this, window, set_client_rect, |_| ()); + old_flags.apply_diff(window, new_flags); } pub fn set_window_flags_in_place(&mut self, f: F) @@ -185,6 +169,7 @@ impl WindowFlags { fn mask(mut self) -> WindowFlags { if self.contains(WindowFlags::MARKER_FULLSCREEN) { self &= WindowFlags::FULLSCREEN_AND_MASK; + self |= WindowFlags::FULLSCREEN_OR_MASK; } if !self.contains(WindowFlags::VISIBLE) { self &= WindowFlags::INVISIBLE_AND_MASK; @@ -236,7 +221,7 @@ impl WindowFlags { } /// Adjust the window client rectangle to the return value, if present. - fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option) { + fn apply_diff(mut self, window: HWND, mut new: WindowFlags) { self = self.mask(); new = new.mask(); @@ -295,45 +280,20 @@ impl WindowFlags { winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); - match set_client_rect - .and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r)) - { - Some(client_rect) => { - let (x, y, w, h) = ( - client_rect.left, - client_rect.top, - client_rect.right - client_rect.left, - client_rect.bottom - client_rect.top, - ); - winuser::SetWindowPos( - window, - ptr::null_mut(), - x, - y, - w, - h, - winuser::SWP_NOZORDER - | winuser::SWP_FRAMECHANGED - | winuser::SWP_NOACTIVATE, - ); - } - None => { - // Refresh the window frame. - winuser::SetWindowPos( - window, - ptr::null_mut(), - 0, - 0, - 0, - 0, - winuser::SWP_NOZORDER - | winuser::SWP_NOMOVE - | winuser::SWP_NOSIZE - | winuser::SWP_FRAMECHANGED - | winuser::SWP_NOACTIVATE, - ); - } + let mut flags = winuser::SWP_NOZORDER + | winuser::SWP_NOMOVE + | winuser::SWP_NOSIZE + | winuser::SWP_FRAMECHANGED; + + // 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) { + flags |= winuser::SWP_NOACTIVATE; } + + // Refresh the window frame + winuser::SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags); winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0); } } diff --git a/src/window.rs b/src/window.rs index e095e38d..f983d791 100644 --- a/src/window.rs +++ b/src/window.rs @@ -5,7 +5,7 @@ use crate::{ dpi::{LogicalPosition, LogicalSize}, error::{ExternalError, NotSupportedError, OsError}, event_loop::EventLoopWindowTarget, - monitor::{AvailableMonitorsIter, MonitorHandle}, + monitor::{AvailableMonitorsIter, MonitorHandle, VideoMode}, platform_impl, }; @@ -45,6 +45,18 @@ impl fmt::Debug for Window { } } +impl Drop for Window { + fn drop(&mut self) { + // If the window is in exclusive fullscreen, we must restore the desktop + // video mode (generally this would be done on application exit, but + // closing the window doesn't necessarily always mean application exit, + // such as when there are multiple windows) + if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() { + self.set_fullscreen(None); + } + } +} + /// Identifier of a window. Unique for each window. /// /// Can be obtained with `window.id()`. @@ -110,7 +122,7 @@ pub struct WindowAttributes { /// Whether the window should be set as fullscreen upon creation. /// /// The default is `None`. - pub fullscreen: Option, + pub fullscreen: Option, /// The title of the window in the title bar. /// @@ -222,14 +234,14 @@ impl WindowBuilder { self } - /// Sets the window fullscreen state. None means a normal window, Some(MonitorHandle) + /// Sets the window fullscreen state. None means a normal window, Some(Fullscreen) /// means a fullscreen window on that specific monitor /// /// ## Platform-specific /// /// - **Windows:** Screen saver is disabled in fullscreen mode. #[inline] - pub fn with_fullscreen(mut self, monitor: Option) -> WindowBuilder { + pub fn with_fullscreen(mut self, monitor: Option) -> WindowBuilder { self.window.fullscreen = monitor; self } @@ -295,7 +307,6 @@ impl WindowBuilder { self, window_target: &EventLoopWindowTarget, ) -> Result { - // building platform_impl::Window::new(&window_target.p, self.window, self.platform_specific) .map(|window| Window { window }) } @@ -537,11 +548,27 @@ impl Window { /// /// ## Platform-specific /// + /// - **macOS:** `Fullscreen::Exclusive` provides true exclusive mode with a + /// video mode change. *Caveat!* macOS doesn't provide task switching (or + /// spaces!) while in exclusive fullscreen mode. This mode should be used + /// when a video mode change is desired, but for a better user experience, + /// borderless fullscreen might be preferred. + /// + /// `Fullscreen::Borderless` provides a borderless fullscreen window on a + /// separate space. This is the idiomatic way for fullscreen games to work + /// on macOS. See [`WindowExtMacOs::set_simple_fullscreen`][simple] if + /// separate spaces are not preferred. + /// + /// 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. /// - **Windows:** Screen saver is disabled in fullscreen mode. + /// + /// [simple]: + /// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen #[inline] - pub fn set_fullscreen(&self, monitor: Option) { - self.window.set_fullscreen(monitor) + pub fn set_fullscreen(&self, fullscreen: Option) { + self.window.set_fullscreen(fullscreen) } /// Gets the window's current fullscreen state. @@ -550,7 +577,7 @@ impl Window { /// /// - **iOS:** Can only be called on the main thread. #[inline] - pub fn fullscreen(&self) -> Option { + pub fn fullscreen(&self) -> Option { self.window.fullscreen() } @@ -759,3 +786,9 @@ impl Default for CursorIcon { CursorIcon::Default } } + +#[derive(Clone, Debug, PartialEq)] +pub enum Fullscreen { + Exclusive(VideoMode), + Borderless(MonitorHandle), +} From 3c27e7d88f4ad0bf37fb87db2ebf742c0a1d55cf Mon Sep 17 00:00:00 2001 From: mtak- Date: Tue, 30 Jul 2019 23:57:31 -0700 Subject: [PATCH 21/61] iOS: add support for controlling the home indicator, and Exclusive video mode (#1078) * iOS: platform specific edge home indicator control * iOS: exclusive video mode support * address nits, and linkify all the ios documentation --- CHANGELOG.md | 3 + Cargo.toml | 2 +- FEATURES.md | 2 +- src/lib.rs | 2 +- src/platform/ios.rs | 163 ++++++++++++++++++++++++++----- src/platform_impl/ios/ffi.rs | 32 +++++- src/platform_impl/ios/monitor.rs | 80 +++++++++++---- src/platform_impl/ios/view.rs | 150 ++++++++++++++++++---------- src/platform_impl/ios/window.rs | 67 ++++++++++--- 9 files changed, 383 insertions(+), 118 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8ec9346..98faa296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ consists of `Fullscreen::Exclusive(VideoMode)` and `Fullscreen::Borderless(MonitorHandle)` variants. - Adds support for exclusive fullscreen mode. +- On iOS, add support for hiding the home indicator. +- On iOS, add support for deferring system gestures. +- On iOS, fix a crash that occurred while acquiring a monitor's name. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/Cargo.toml b/Cargo.toml index e205b9a2..9c1678ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ version = "0.1.3" default_features = false features = ["display_link"] -[target.'cfg(target_os = "windows")'.dependencies] +[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies] bitflags = "1" [target.'cfg(target_os = "windows")'.dependencies.winapi] diff --git a/FEATURES.md b/FEATURES.md index 73994604..7e697570 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -160,7 +160,7 @@ Legend: |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | -|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |❌ |❌ | +|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❌ | |HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | diff --git a/src/lib.rs b/src/lib.rs index a4a65f54..4a85a2ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,7 +123,7 @@ extern crate serde; #[macro_use] extern crate derivative; #[macro_use] -#[cfg(target_os = "windows")] +#[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/ios.rs b/src/platform/ios.rs index 8c1034a4..a2a06a65 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -4,13 +4,13 @@ use std::os::raw::c_void; use crate::{ event_loop::EventLoop, - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::{Window, WindowBuilder}, }; -/// Additional methods on `EventLoop` that are specific to iOS. +/// Additional methods on [`EventLoop`] that are specific to iOS. pub trait EventLoopExtIOS { - /// Returns the idiom (phone/tablet/tv/etc) for the current device. + /// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device. fn idiom(&self) -> Idiom; } @@ -20,32 +20,66 @@ impl EventLoopExtIOS for EventLoop { } } -/// Additional methods on `Window` that are specific to iOS. +/// Additional methods on [`Window`] that are specific to iOS. pub trait WindowExtIOS { - /// Returns a pointer to the `UIWindow` that is used by this window. + /// Returns a pointer to the [`UIWindow`] that is used by this window. /// - /// The pointer will become invalid when the `Window` is destroyed. + /// The pointer will become invalid when the [`Window`] is destroyed. + /// + /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc fn ui_window(&self) -> *mut c_void; - /// Returns a pointer to the `UIViewController` that is used by this window. + /// Returns a pointer to the [`UIViewController`] that is used by this window. /// - /// The pointer will become invalid when the `Window` is destroyed. + /// The pointer will become invalid when the [`Window`] is destroyed. + /// + /// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc fn ui_view_controller(&self) -> *mut c_void; - /// Returns a pointer to the `UIView` that is used by this window. + /// Returns a pointer to the [`UIView`] that is used by this window. /// - /// The pointer will become invalid when the `Window` is destroyed. + /// The pointer will become invalid when the [`Window`] is destroyed. + /// + /// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc fn ui_view(&self) -> *mut c_void; - /// Sets the HiDpi factor used by this window. + /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`. /// - /// This translates to `-[UIWindow setContentScaleFactor:hidpi_factor]`. + /// The default value is device dependent, and it's recommended GLES or Metal applications set + /// this to [`MonitorHandle::hidpi_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); - /// Sets the valid orientations for screens showing this `Window`. + /// Sets the valid orientations for the [`Window`]. /// - /// On iPhones and iPods upside down portrait is never enabled. + /// The default value is [`ValidOrientations::LandscapeAndPortrait`]. + /// + /// This changes the value returned by + /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc), + /// and then calls + /// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc). fn set_valid_orientations(&self, valid_orientations: ValidOrientations); + + /// Sets whether the [`Window`] prefers the home indicator hidden. + /// + /// The default is to prefer showing the home indicator. + /// + /// This changes the value returned by + /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc), + /// and then calls + /// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc). + fn set_prefers_home_indicator_hidden(&self, hidden: bool); + + /// Sets the screen edges for which the system gestures will take a lower priority than the + /// application's touch handling. + /// + /// This changes the value returned by + /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc), + /// and then calls + /// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc). + fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge); } impl WindowExtIOS for Window { @@ -73,23 +107,62 @@ impl WindowExtIOS for Window { fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { self.window.set_valid_orientations(valid_orientations) } + + #[inline] + fn set_prefers_home_indicator_hidden(&self, hidden: bool) { + self.window.set_prefers_home_indicator_hidden(hidden) + } + + #[inline] + fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { + self.window + .set_preferred_screen_edges_deferring_system_gestures(edges) + } } -/// Additional methods on `WindowBuilder` that are specific to iOS. +/// Additional methods on [`WindowBuilder`] that are specific to iOS. pub trait WindowBuilderExtIOS { - /// Sets the root view class used by the `Window`, otherwise a barebones `UIView` is provided. + /// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided. /// - /// The class will be initialized by calling `[root_view initWithFrame:CGRect]` + /// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc). + /// + /// [`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 `hidpi_factor`. /// /// The default value is device dependent, and it's recommended GLES or Metal applications set - /// this to `MonitorHandle::hidpi_factor()`. + /// this to [`MonitorHandle::hidpi_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; - /// Sets the valid orientations for the `Window`. + /// Sets the valid orientations for the [`Window`]. + /// + /// The default value is [`ValidOrientations::LandscapeAndPortrait`]. + /// + /// This sets the initial value returned by + /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc). fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder; + + /// Sets whether the [`Window`] prefers the home indicator hidden. + /// + /// The default is to prefer showing the home indicator. + /// + /// This sets the initial value returned by + /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc). + fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder; + + /// Sets the screen edges for which the system gestures will take a lower priority than the + /// application's touch handling. + /// + /// This sets the initial value returned by + /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc). + fn with_preferred_screen_edges_deferring_system_gestures( + self, + edges: ScreenEdge, + ) -> WindowBuilder; } impl WindowBuilderExtIOS for WindowBuilder { @@ -110,12 +183,35 @@ impl WindowBuilderExtIOS for WindowBuilder { self.platform_specific.valid_orientations = valid_orientations; self } + + #[inline] + fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder { + self.platform_specific.prefers_home_indicator_hidden = hidden; + self + } + + #[inline] + fn with_preferred_screen_edges_deferring_system_gestures( + mut self, + edges: ScreenEdge, + ) -> WindowBuilder { + self.platform_specific + .preferred_screen_edges_deferring_system_gestures = edges; + self + } } -/// Additional methods on `MonitorHandle` that are specific to iOS. +/// Additional methods on [`MonitorHandle`] that are specific to iOS. pub trait MonitorHandleExtIOS { - /// Returns a pointer to the `UIScreen` that is used by this monitor. + /// Returns a pointer to the [`UIScreen`] that is used by this monitor. + /// + /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc fn ui_screen(&self) -> *mut c_void; + + /// Returns the preferred [`VideoMode`] for this monitor. + /// + /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc). + fn preferred_video_mode(&self) -> VideoMode; } impl MonitorHandleExtIOS for MonitorHandle { @@ -123,9 +219,14 @@ impl MonitorHandleExtIOS for MonitorHandle { fn ui_screen(&self) -> *mut c_void { self.inner.ui_screen() as _ } + + #[inline] + fn preferred_video_mode(&self) -> VideoMode { + self.inner.preferred_video_mode() + } } -/// Valid orientations for a particular `Window`. +/// Valid orientations for a particular [`Window`]. #[derive(Clone, Copy, Debug)] pub enum ValidOrientations { /// Excludes `PortraitUpsideDown` on iphone @@ -161,3 +262,19 @@ pub enum Idiom { TV, CarPlay, } + +bitflags! { + /// The [edges] of a screen. + /// + /// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc + #[derive(Default)] + pub struct ScreenEdge: u8 { + const NONE = 0; + const TOP = 1 << 0; + const LEFT = 1 << 1; + const BOTTOM = 1 << 2; + const RIGHT = 1 << 3; + const ALL = ScreenEdge::TOP.bits | ScreenEdge::LEFT.bits + | ScreenEdge::BOTTOM.bits | ScreenEdge::RIGHT.bits; + } +} diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 9b0c26be..4782cf1a 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -1,10 +1,10 @@ #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] -use std::{ffi::CString, ops::BitOr, os::raw::*}; +use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*}; use objc::{runtime::Object, Encode, Encoding}; -use crate::platform::ios::{Idiom, ValidOrientations}; +use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations}; pub type id = *mut Object; pub const nil: id = 0 as id; @@ -173,6 +173,34 @@ impl UIInterfaceOrientationMask { } } +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UIRectEdge(NSUInteger); + +unsafe impl Encode for UIRectEdge { + fn encode() -> Encoding { + NSUInteger::encode() + } +} + +impl From for UIRectEdge { + fn from(screen_edge: ScreenEdge) -> UIRectEdge { + assert_eq!( + screen_edge.bits() & !ScreenEdge::ALL.bits(), + 0, + "invalid `ScreenEdge`" + ); + UIRectEdge(screen_edge.bits().into()) + } +} + +impl Into for UIRectEdge { + fn into(self) -> ScreenEdge { + let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`"); + ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`") + } +} + #[link(name = "UIKit", kind = "framework")] #[link(name = "CoreFoundation", kind = "framework")] extern "C" { diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index 90257077..96b6f7eb 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -10,15 +10,50 @@ use crate::{ platform_impl::platform::ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger}, }; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, PartialEq, Eq, Hash)] 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) monitor: MonitorHandle, } +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] }, + 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"); + 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"); + let refresh_rate: NSInteger = msg_send![uiscreen, maximumFramesPerSecond]; + let size: CGSize = msg_send![screen_mode, size]; + 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], + monitor: MonitorHandle::retained_new(uiscreen), + } + } + pub fn size(&self) -> PhysicalSize { self.size.into() } @@ -133,9 +168,10 @@ impl MonitorHandle { impl Inner { pub fn name(&self) -> Option { unsafe { - if self.uiscreen == main_uiscreen().uiscreen { + let main = main_uiscreen(); + if self.uiscreen == main.uiscreen { Some("Primary".to_string()) - } else if self.uiscreen == mirrored_uiscreen().uiscreen { + } else if self.uiscreen == mirrored_uiscreen(&main).uiscreen { Some("Mirrored".to_string()) } else { uiscreens() @@ -168,24 +204,17 @@ impl Inner { } pub fn video_modes(&self) -> impl Iterator { - let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] }; - - let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] }; - let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] }; - let mut modes = BTreeSet::new(); + unsafe { + let available_modes: id = msg_send![self.uiscreen, availableModes]; + let available_mode_count: NSUInteger = msg_send![available_modes, count]; - for i in 0..available_mode_count { - let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] }; - let size: CGSize = unsafe { msg_send![mode, size] }; - modes.insert(RootVideoMode { - video_mode: VideoMode { - size: (size.width as u32, size.height as u32), - bit_depth: 32, - refresh_rate: refresh_rate as u16, - monitor: MonitorHandle::retained_new(self.uiscreen), - }, - }); + for i in 0..available_mode_count { + let mode: id = msg_send![available_modes, objectAtIndex: i]; + modes.insert(RootVideoMode { + video_mode: VideoMode::retained_new(self.uiscreen, mode), + }); + } } modes.into_iter() @@ -197,6 +226,15 @@ impl Inner { pub fn ui_screen(&self) -> id { self.uiscreen } + + pub fn preferred_video_mode(&self) -> RootVideoMode { + unsafe { + let mode: id = msg_send![self.uiscreen, preferredMode]; + RootVideoMode { + video_mode: VideoMode::retained_new(self.uiscreen, mode), + } + } + } } // requires being run on main thread @@ -206,8 +244,8 @@ pub unsafe fn main_uiscreen() -> MonitorHandle { } // requires being run on main thread -unsafe fn mirrored_uiscreen() -> MonitorHandle { - let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen]; +unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle { + let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen]; MonitorHandle::retained_new(uiscreen) } diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 95047814..79211404 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -11,13 +11,48 @@ use crate::{ platform_impl::platform::{ app_state::AppState, event_loop, - ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase}, + ffi::{ + id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UIRectEdge, UITouchPhase, + }, window::PlatformSpecificWindowBuilderAttributes, DeviceId, }, window::{Fullscreen, WindowAttributes, WindowId as RootWindowId}, }; +macro_rules! add_property { + ( + $decl:ident, + $name:ident: $t:ty, + $setter_name:ident: |$object:ident| $after_set:expr, + $getter_name:ident, + ) => { + { + const VAR_NAME: &'static str = concat!("_", stringify!($name)); + $decl.add_ivar::<$t>(VAR_NAME); + #[allow(non_snake_case)] + extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) { + unsafe { + $object.set_ivar::<$t>(VAR_NAME, value); + } + $after_set + } + #[allow(non_snake_case)] + extern "C" fn $getter_name($object: &Object, _: Sel) -> $t { + unsafe { *$object.get_ivar::<$t>(VAR_NAME) } + } + $decl.add_method( + sel!($setter_name:), + $setter_name as extern "C" fn(&mut Object, Sel, $t), + ); + $decl.add_method( + sel!($getter_name), + $getter_name as extern "C" fn(&Object, Sel) -> $t, + ); + } + }; +} + // requires main thread unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { static mut CLASSES: Option> = None; @@ -91,67 +126,56 @@ unsafe fn get_view_controller_class() -> &'static Class { if CLASS.is_none() { let uiviewcontroller_class = class!(UIViewController); - extern "C" fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) { - unsafe { - object.set_ivar::("_prefers_status_bar_hidden", hidden); - let () = msg_send![object, setNeedsStatusBarAppearanceUpdate]; - } - } - - extern "C" fn prefers_status_bar_hidden(object: &Object, _: Sel) -> BOOL { - unsafe { *object.get_ivar::("_prefers_status_bar_hidden") } - } - - extern "C" fn set_supported_orientations( - object: &mut Object, - _: Sel, - orientations: UIInterfaceOrientationMask, - ) { - unsafe { - object.set_ivar::( - "_supported_orientations", - orientations, - ); - let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation]; - } - } - - extern "C" fn supported_orientations( - object: &Object, - _: Sel, - ) -> UIInterfaceOrientationMask { - unsafe { *object.get_ivar::("_supported_orientations") } - } - extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL { YES } let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class) .expect("Failed to declare class `WinitUIViewController`"); - decl.add_ivar::("_prefers_status_bar_hidden"); - decl.add_ivar::("_supported_orientations"); - decl.add_method( - sel!(setPrefersStatusBarHidden:), - set_prefers_status_bar_hidden as extern "C" fn(&mut Object, Sel, BOOL), - ); - decl.add_method( - sel!(prefersStatusBarHidden), - prefers_status_bar_hidden as extern "C" fn(&Object, Sel) -> BOOL, - ); - decl.add_method( - sel!(setSupportedInterfaceOrientations:), - set_supported_orientations - as extern "C" fn(&mut Object, Sel, UIInterfaceOrientationMask), - ); - decl.add_method( - sel!(supportedInterfaceOrientations), - supported_orientations as extern "C" fn(&Object, Sel) -> UIInterfaceOrientationMask, - ); decl.add_method( sel!(shouldAutorotate), should_autorotate as extern "C" fn(&Object, Sel) -> BOOL, ); + add_property! { + decl, + prefers_status_bar_hidden: BOOL, + setPrefersStatusBarHidden: |object| { + unsafe { + let () = msg_send![object, setNeedsStatusBarAppearanceUpdate]; + } + }, + prefersStatusBarHidden, + } + add_property! { + decl, + prefers_home_indicator_auto_hidden: BOOL, + setPrefersHomeIndicatorAutoHidden: |object| { + unsafe { + let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden]; + } + }, + prefersHomeIndicatorAutoHidden, + } + add_property! { + decl, + supported_orientations: UIInterfaceOrientationMask, + setSupportedInterfaceOrientations: |object| { + unsafe { + let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation]; + } + }, + supportedInterfaceOrientations, + } + add_property! { + decl, + preferred_screen_edges_deferring_system_gestures: UIRectEdge, + setPreferredScreenEdgesDeferringSystemGestures: |object| { + unsafe { + let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; + } + }, + preferredScreenEdgesDeferringSystemGestures, + } CLASS = Some(decl.register()); } CLASS.unwrap() @@ -333,6 +357,14 @@ pub unsafe fn create_view_controller( platform_attributes.valid_orientations, idiom, ); + let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden { + YES + } else { + NO + }; + let edges: UIRectEdge = platform_attributes + .preferred_screen_edges_deferring_system_gestures + .into(); let () = msg_send![ view_controller, setPrefersStatusBarHidden: status_bar_hidden @@ -341,6 +373,14 @@ pub unsafe fn create_view_controller( view_controller, setSupportedInterfaceOrientations: supported_orientations ]; + let () = msg_send![ + view_controller, + setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden + ]; + let () = msg_send![ + view_controller, + setPreferredScreenEdgesDeferringSystemGestures: edges + ]; let () = msg_send![view_controller, setView: view]; view_controller } @@ -366,7 +406,11 @@ pub unsafe fn create_window( let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat]; } match window_attributes.fullscreen { - Some(Fullscreen::Exclusive(_)) => unimplemented!(), + 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]; + msg_send![window, setScreen:video_mode.monitor().ui_screen()] + } Some(Fullscreen::Borderless(ref monitor)) => { msg_send![window, setScreen:monitor.ui_screen()] } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 05b68ea4..62f00281 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -10,11 +10,14 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::MonitorHandle as RootMonitorHandle, - platform::ios::{MonitorHandleExtIOS, ValidOrientations}, + platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations}, platform_impl::platform::{ app_state::AppState, event_loop, - ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask}, + ffi::{ + id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, + UIRectEdge, + }, monitor, view, EventLoopWindowTarget, MonitorHandle, }, window::{CursorIcon, Fullscreen, WindowAttributes}, @@ -159,21 +162,27 @@ impl Inner { pub fn set_fullscreen(&self, monitor: Option) { unsafe { - match monitor { - Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO - Some(Fullscreen::Borderless(monitor)) => { - let uiscreen = monitor.ui_screen() as id; - let current: id = msg_send![self.window, screen]; - let bounds: CGRect = msg_send![uiscreen, bounds]; - - // this is pretty slow on iOS, so avoid doing it if we can - if uiscreen != current { - let () = msg_send![self.window, setScreen: uiscreen]; - } - let () = msg_send![self.window, setFrame: bounds]; + let uiscreen = match monitor { + Some(Fullscreen::Exclusive(video_mode)) => { + let uiscreen = video_mode.video_mode.monitor.ui_screen() as id; + let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode]; + uiscreen } - None => warn!("`Window::set_fullscreen(None)` ignored on iOS"), + Some(Fullscreen::Borderless(monitor)) => monitor.ui_screen() as id, + None => { + warn!("`Window::set_fullscreen(None)` ignored on iOS"); + return; + } + }; + + let current: id = msg_send![self.window, screen]; + let bounds: CGRect = msg_send![uiscreen, bounds]; + + // this is pretty slow on iOS, so avoid doing it if we can + if uiscreen != current { + let () = msg_send![self.window, setScreen: uiscreen]; } + let () = msg_send![self.window, setFrame: bounds]; } } @@ -295,7 +304,9 @@ impl Window { unsafe { let screen = match window_attributes.fullscreen { - Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO: do we set the frame to video mode bounds instead of screen bounds? + 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(), }; @@ -375,6 +386,26 @@ impl Inner { ]; } } + + pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { + unsafe { + let prefers_home_indicator_hidden = if hidden { NO } else { YES }; + let () = msg_send![ + self.view_controller, + setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden + ]; + } + } + + pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { + let edges: UIRectEdge = edges.into(); + unsafe { + let () = msg_send![ + self.view_controller, + setPreferredScreenEdgesDeferringSystemGestures: edges + ]; + } + } } impl Inner { @@ -496,6 +527,8 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub root_view_class: &'static Class, pub hidpi_factor: Option, pub valid_orientations: ValidOrientations, + pub prefers_home_indicator_hidden: bool, + pub preferred_screen_edges_deferring_system_gestures: ScreenEdge, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -504,6 +537,8 @@ impl Default for PlatformSpecificWindowBuilderAttributes { root_view_class: class!(UIView), hidpi_factor: None, valid_orientations: Default::default(), + prefers_home_indicator_hidden: false, + preferred_screen_edges_deferring_system_gestures: Default::default(), } } } From 1e4c176506834e3a0491d9501eb35cb9359e0af1 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Date: Thu, 1 Aug 2019 10:30:05 +0300 Subject: [PATCH 22/61] Fix armv7-apple-ios compile target (#1083) --- .travis.yml | 24 ++++++++++++++++++++---- CHANGELOG.md | 1 + src/platform_impl/ios/view.rs | 8 ++++---- src/platform_impl/ios/window.rs | 20 ++++++++++---------- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17a0e4af..aaa56d9e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ matrix: os: osx rust: stable - # iOS + # iOS x86_64 - env: TARGET=x86_64-apple-ios os: osx rust: nightly @@ -42,6 +42,22 @@ matrix: 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 + install: - rustup self update - rustup target add $TARGET; true @@ -52,9 +68,9 @@ script: - cargo +stable fmt --all -- --check - cargo build --target $TARGET --verbose - cargo build --target $TARGET --features serde --verbose - # Running iOS apps on OSX requires the simulator so we skip that for now - - if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --verbose; fi - - if [ "$TARGET" != "x86_64-apple-ios" ]; then cargo test --target $TARGET --features serde --verbose; fi + # Running iOS apps on macOS requires the Simulator so we skip that for now + - if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --verbose; fi + - if [[ $TARGET != *-apple-ios ]]; then cargo test --target $TARGET --features serde --verbose; fi after_success: - | diff --git a/CHANGELOG.md b/CHANGELOG.md index 98faa296..ee30e9c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - On iOS, add support for hiding the home indicator. - On iOS, add support for deferring system gestures. - On iOS, fix a crash that occurred while acquiring a monitor's name. +- On iOS, fix armv7-apple-ios compile target. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 79211404..4bc5096a 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -93,8 +93,8 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; let size = crate::dpi::LogicalSize { - width: screen_frame.size.width, - height: screen_frame.size.height, + width: screen_frame.size.width as _, + height: screen_frame.size.height as _, }; AppState::handle_nonuser_event(Event::WindowEvent { window_id: RootWindowId(window.into()), @@ -258,8 +258,8 @@ unsafe fn get_window_class() -> &'static Class { let screen_frame: CGRect = msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; let size = crate::dpi::LogicalSize { - width: screen_frame.size.width, - height: screen_frame.size.height, + width: screen_frame.size.width as _, + height: screen_frame.size.height as _, }; AppState::handle_nonuser_events( std::iter::once(Event::WindowEvent { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 62f00281..9e15aa9a 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -66,8 +66,8 @@ impl Inner { unsafe { let safe_area = self.safe_area_screen_space(); Ok(LogicalPosition { - x: safe_area.origin.x, - y: safe_area.origin.y, + x: safe_area.origin.x as _, + y: safe_area.origin.y as _, }) } } @@ -76,8 +76,8 @@ impl Inner { unsafe { let screen_frame = self.screen_frame(); Ok(LogicalPosition { - x: screen_frame.origin.x, - y: screen_frame.origin.y, + x: screen_frame.origin.x as _, + y: screen_frame.origin.y as _, }) } } @@ -101,8 +101,8 @@ impl Inner { unsafe { let safe_area = self.safe_area_screen_space(); LogicalSize { - width: safe_area.size.width, - height: safe_area.size.height, + width: safe_area.size.width as _, + height: safe_area.size.height as _, } } } @@ -111,8 +111,8 @@ impl Inner { unsafe { let screen_frame = self.screen_frame(); LogicalSize { - width: screen_frame.size.width, - height: screen_frame.size.height, + width: screen_frame.size.width as _, + height: screen_frame.size.height as _, } } } @@ -317,8 +317,8 @@ impl Window { Some(dim) => CGRect { origin: screen_bounds.origin, size: CGSize { - width: dim.width, - height: dim.height, + width: dim.width as _, + height: dim.height as _, }, }, None => screen_bounds, From 8a1c5277eb96d25d2c5e821eb24aa4a4e3020b74 Mon Sep 17 00:00:00 2001 From: mtak- Date: Thu, 1 Aug 2019 14:07:22 -0700 Subject: [PATCH 23/61] iOS: update feature table (#1085) --- FEATURES.md | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/FEATURES.md b/FEATURES.md index 7e697570..fd4c2bf9 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -132,6 +132,19 @@ If your PR makes notable changes to Winit's features, please update this section * GTK Theme Variant * Base window size +### iOS +* Get the `UIWindow` object pointer +* Get the `UIViewController` object pointer +* Get the `UIView` object pointer +* Get the `UIScreen` object pointer +* Setting the `UIView` hidpi factor +* Valid orientations +* Home indicator visibility +* Deferrring system gestures +* Support for custom `UIView` derived class +* Getting the device idiom +* Getting the preferred video mode + ## Usability * `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial) @@ -151,15 +164,15 @@ Legend: |Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ | |Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ | |Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A** | -|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A** | -|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | +|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|✔️ |**N/A** | +|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A** | |Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ | |Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❌ | |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** | -|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | -|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | +|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❌ | +|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❌ | |Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❌ | |HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | @@ -167,7 +180,7 @@ Legend: ### System information |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| |---------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | -|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | +|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A** | |Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❌ | ### Input handling @@ -178,7 +191,7 @@ Legend: |Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ | |Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | -|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |❌ |❌ | +|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❌ | |Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | |Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❌ | @@ -191,7 +204,7 @@ Changes in the API that have been agreed upon but aren't implemented across all |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | |New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | -|Event Loop 2.0 ([#459]) |✔️ |❌ |❌ |✔️ |❌ |❌ |❌ | +|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❌ | |Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ | ### Completed API Reworks From 73cf10e4f361a854f4f16ada31b1c391e7ec6846 Mon Sep 17 00:00:00 2001 From: YVT Date: Tue, 6 Aug 2019 05:51:42 +0900 Subject: [PATCH 24/61] Do not require `T: Clone` for `EventLoopProxy: Clone` (#1086) * Do not require `T: Clone` for `EventLoopProxy: Clone` * Update `CHANGELOG.md` * Remove the conflicting `Clone` impl * Fix match statement --- CHANGELOG.md | 1 + src/event_loop.rs | 9 ++++++++- src/platform_impl/linux/mod.rs | 10 +++++++++- src/platform_impl/linux/wayland/event_loop.rs | 9 ++++++++- src/platform_impl/linux/x11/mod.rs | 9 ++++++++- src/platform_impl/macos/event_loop.rs | 7 ++++++- src/platform_impl/windows/event_loop.rs | 10 +++++++++- 7 files changed, 49 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee30e9c9..d68f064d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - On iOS, add support for deferring system gestures. - On iOS, fix a crash that occurred while acquiring a monitor's name. - On iOS, fix armv7-apple-ios compile target. +- Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy`. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/event_loop.rs b/src/event_loop.rs index a7a1134c..a2d90d3e 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -173,11 +173,18 @@ impl Deref for EventLoop { } /// Used to send custom events to `EventLoop`. -#[derive(Clone)] pub struct EventLoopProxy { event_loop_proxy: platform_impl::EventLoopProxy, } +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + Self { + event_loop_proxy: self.event_loop_proxy.clone(), + } + } +} + impl EventLoopProxy { /// Send an event to the `EventLoop` from which this proxy was created. This emits a /// `UserEvent(event)` event in the event loop, where `event` is the value passed to this diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index bd2656ed..93238d60 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -479,12 +479,20 @@ pub enum EventLoop { X(x11::EventLoop), } -#[derive(Clone)] pub enum EventLoopProxy { X(x11::EventLoopProxy), 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()), + } + } +} + impl EventLoop { pub fn new() -> EventLoop { if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) { diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 2cdfb168..8c4cfa65 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -90,7 +90,6 @@ pub struct EventLoop { // 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. -#[derive(Clone)] pub struct EventLoopProxy { user_sender: ::calloop::channel::Sender, } @@ -111,6 +110,14 @@ pub struct EventLoopWindowTarget { _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(|_| EventLoopClosed) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 345ebeeb..b239c571 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -66,11 +66,18 @@ pub struct EventLoop { target: Rc>, } -#[derive(Clone)] pub struct EventLoopProxy { user_sender: ::calloop::channel::Sender, } +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + EventLoopProxy { + user_sender: self.user_sender.clone(), + } + } +} + impl EventLoop { pub fn new(xconn: Arc) -> EventLoop { let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) }; diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 1631daa8..32d8739c 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -108,7 +108,6 @@ impl EventLoop { } } -#[derive(Clone)] pub struct Proxy { sender: mpsc::Sender, source: CFRunLoopSourceRef, @@ -117,6 +116,12 @@ pub struct Proxy { unsafe impl Send for Proxy {} unsafe impl Sync for Proxy {} +impl Clone for Proxy { + fn clone(&self) -> Self { + Proxy::new(self.sender.clone()) + } +} + impl Proxy { fn new(sender: mpsc::Sender) -> Self { unsafe { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index c84716f7..3b1052cf 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -678,13 +678,21 @@ impl EventLoopThreadExecutor { type ThreadExecFn = Box>; -#[derive(Clone)] pub struct EventLoopProxy { target_window: HWND, event_send: Sender, } unsafe impl Send for EventLoopProxy {} +impl Clone for EventLoopProxy { + fn clone(&self) -> Self { + Self { + target_window: self.target_window, + event_send: self.event_send.clone(), + } + } +} + impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { unsafe { From c0c22c8ff1ebe95e8848d206a8ffc4619597afa0 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Date: Tue, 6 Aug 2019 23:47:00 +0300 Subject: [PATCH 25/61] Disable overscan compensation for external displays on iOS (#1088) --- CHANGELOG.md | 2 ++ src/platform_impl/ios/ffi.rs | 17 +++++++++++++++++ src/platform_impl/ios/window.rs | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d68f064d..e3f35ef9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ - On iOS, fix a crash that occurred while acquiring a monitor's name. - On iOS, fix armv7-apple-ios compile target. - Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy`. +- On iOS, disable overscan compensation for external displays (removes black + bars surrounding the image). # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 4782cf1a..1536c7b2 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -201,6 +201,23 @@ impl Into for UIRectEdge { } } +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UIScreenOverscanCompensation(NSInteger); + +unsafe impl Encode for UIScreenOverscanCompensation { + fn encode() -> Encoding { + NSInteger::encode() + } +} + +#[allow(dead_code)] +impl UIScreenOverscanCompensation { + pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0); + pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1); + pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2); +} + #[link(name = "UIKit", kind = "framework")] #[link(name = "CoreFoundation", kind = "framework")] extern "C" { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 9e15aa9a..7c8b9501 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -16,7 +16,7 @@ use crate::{ event_loop, ffi::{ id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, - UIRectEdge, + UIRectEdge, UIScreenOverscanCompensation, }, monitor, view, EventLoopWindowTarget, MonitorHandle, }, @@ -175,14 +175,22 @@ impl Inner { } }; - let current: id = msg_send![self.window, screen]; - let bounds: CGRect = msg_send![uiscreen, bounds]; - // this is pretty slow on iOS, so avoid doing it if we can + let current: id = msg_send![self.window, screen]; if uiscreen != current { let () = msg_send![self.window, setScreen: uiscreen]; } + + let bounds: CGRect = msg_send![uiscreen, bounds]; let () = msg_send![self.window, setFrame: bounds]; + + // For external displays, we must disable overscan compensation or + // the displayed image will have giant black bars surrounding it on + // each side + let () = msg_send![ + uiscreen, + setOverscanCompensation: UIScreenOverscanCompensation::None + ]; } } From cf0b8babbdb39abc443c94c8a94281201e6e9b0c Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Thu, 8 Aug 2019 14:50:22 -0700 Subject: [PATCH 26/61] Add new `EventLoopWindowTargetExtUnix` trait. (#1026) * Add new `EventLoopWindowTargetExtUnix` trait. Signed-off-by: Hal Gentz * Slide. Signed-off-by: Hal Gentz * Travis, damn you. Signed-off-by: Hal Gentz * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/platform/unix.rs | 99 ++++++++++--------- src/platform_impl/linux/mod.rs | 26 ++--- src/platform_impl/linux/wayland/event_loop.rs | 10 +- src/platform_impl/linux/x11/mod.rs | 25 ++--- 5 files changed, 89 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3f35ef9..8c73544e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Removed the `T: Clone` requirement from the `Clone` impl of `EventLoopProxy`. - On iOS, disable overscan compensation for external displays (removes black bars surrounding the image). +- On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 494ed398..eb9675e4 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -6,14 +6,15 @@ use smithay_client_toolkit::window::{ButtonState, Theme}; use crate::{ dpi::LogicalSize, - event_loop::EventLoop, + event_loop::{EventLoop, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, }; use crate::platform_impl::{ x11::{ffi::XVisualInfo, XConnection}, - EventLoop as LinuxEventLoop, Window as LinuxWindow, + EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget, + Window as LinuxWindow, }; // TODO: stupid hack so that glutin can do its work @@ -90,6 +91,57 @@ impl Theme for WaylandThemeObject { } } +/// Additional methods on `EventLoopWindowTarget` that are specific to Unix. +pub trait EventLoopWindowTargetExtUnix { + /// True if the `EventLoopWindowTarget` uses Wayland. + fn is_wayland(&self) -> bool; + /// + /// True if the `EventLoopWindowTarget` uses X11. + fn is_x11(&self) -> bool; + + #[doc(hidden)] + fn xlib_xconnection(&self) -> Option>; + + /// Returns a pointer to the `wl_display` object of wayland that is used by this + /// `EventLoopWindowTarget`. + /// + /// 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. + fn wayland_display(&self) -> Option<*mut raw::c_void>; +} + +impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { + #[inline] + fn is_wayland(&self) -> bool { + self.p.is_wayland() + } + + #[inline] + fn is_x11(&self) -> bool { + !self.p.is_wayland() + } + + #[inline] + #[doc(hidden)] + fn xlib_xconnection(&self) -> Option> { + match self.p { + LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()), + _ => None, + } + } + + #[inline] + fn wayland_display(&self) -> Option<*mut raw::c_void> { + match self.p { + LinuxEventLoopWindowTarget::Wayland(ref p) => { + Some(p.display().get_display_ptr() as *mut _) + } + _ => None, + } + } +} + /// Additional methods on `EventLoop` that are specific to Unix. pub trait EventLoopExtUnix { /// Builds a new `EventLoops` that is forced to use X11. @@ -101,22 +153,6 @@ pub trait EventLoopExtUnix { fn new_wayland() -> Self where Self: Sized; - - /// True if the `EventLoop` uses Wayland. - fn is_wayland(&self) -> bool; - - /// True if the `EventLoop` uses X11. - fn is_x11(&self) -> bool; - - #[doc(hidden)] - fn xlib_xconnection(&self) -> Option>; - - /// Returns a pointer to the `wl_display` object of wayland that is used by this `EventLoop`. - /// - /// Returns `None` if the `EventLoop` doesn't use wayland (if it uses xlib for example). - /// - /// The pointer will become invalid when the glutin `EventLoop` is destroyed. - fn wayland_display(&self) -> Option<*mut raw::c_void>; } impl EventLoopExtUnix for EventLoop { @@ -138,33 +174,6 @@ impl EventLoopExtUnix for EventLoop { _marker: ::std::marker::PhantomData, } } - - #[inline] - fn is_wayland(&self) -> bool { - self.event_loop.is_wayland() - } - - #[inline] - fn is_x11(&self) -> bool { - !self.event_loop.is_wayland() - } - - #[inline] - #[doc(hidden)] - fn xlib_xconnection(&self) -> Option> { - match self.event_loop { - LinuxEventLoop::X(ref e) => Some(e.x_connection().clone()), - _ => None, - } - } - - #[inline] - fn wayland_display(&self) -> Option<*mut raw::c_void> { - match self.event_loop { - LinuxEventLoop::Wayland(ref e) => Some(e.display().get_display_ptr() as *mut _), - _ => None, - } - } } /// Additional methods on `Window` that are specific to Unix. diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 93238d60..f036f9f0 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -6,7 +6,7 @@ use parking_lot::Mutex; use smithay_client_toolkit::reexports::client::ConnectError; pub use self::x11::XNotSupported; -use self::x11::{ffi::XVisualInfo, XConnection, XError}; +use self::x11::{ffi::XVisualInfo, get_xtarget, XConnection, XError}; use crate::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, @@ -550,7 +550,7 @@ impl EventLoop { .into_iter() .map(MonitorHandle::Wayland) .collect(), - EventLoop::X(ref evlp) => evlp + EventLoop::X(ref evlp) => get_xtarget(&evlp.target) .x_connection() .available_monitors() .into_iter() @@ -563,7 +563,9 @@ 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(evlp.x_connection().primary_monitor()), + EventLoop::X(ref evlp) => { + MonitorHandle::X(get_xtarget(&evlp.target).x_connection().primary_monitor()) + } } } @@ -594,14 +596,6 @@ impl EventLoop { } } - #[inline] - pub fn is_wayland(&self) -> bool { - match *self { - EventLoop::Wayland(_) => true, - EventLoop::X(_) => false, - } - } - pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget { match *self { EventLoop::Wayland(ref evl) => evl.window_target(), @@ -624,6 +618,16 @@ pub enum EventLoopWindowTarget { X(x11::EventLoopWindowTarget), } +impl EventLoopWindowTarget { + #[inline] + pub fn is_wayland(&self) -> bool { + match *self { + EventLoopWindowTarget::Wayland(_) => true, + EventLoopWindowTarget::X(_) => false, + } + } +} + fn sticky_exit_callback( evt: Event, target: &RootELW, diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index 8c4cfa65..fa1ef347 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -390,15 +390,17 @@ impl EventLoop { available_monitors(&self.outputs) } - pub fn display(&self) -> &Display { - &*self.display - } - pub fn window_target(&self) -> &RootELW { &self.window_target } } +impl EventLoopWindowTarget { + pub fn display(&self) -> &Display { + &*self.display + } +} + /* * Private EventLoop Internals */ diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index b239c571..420dfb2d 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -63,7 +63,7 @@ pub struct EventLoop { event_processor: Rc>>, user_sender: ::calloop::channel::Sender, pending_events: Rc>>>, - target: Rc>, + pub(crate) target: Rc>, } pub struct EventLoopProxy { @@ -232,12 +232,6 @@ impl EventLoop { result } - /// Returns the `XConnection` of this events loop. - #[inline] - pub fn x_connection(&self) -> &Arc { - &get_xtarget(&self.target).xconn - } - pub fn create_proxy(&self) -> EventLoopProxy { EventLoopProxy { user_sender: self.user_sender.clone(), @@ -413,11 +407,18 @@ fn drain_events( } } -fn get_xtarget(rt: &RootELW) -> &EventLoopWindowTarget { - if let super::EventLoopWindowTarget::X(ref target) = rt.p { - target - } else { - unreachable!(); +pub(crate) fn get_xtarget(target: &RootELW) -> &EventLoopWindowTarget { + match target.p { + super::EventLoopWindowTarget::X(ref target) => target, + _ => unreachable!(), + } +} + +impl EventLoopWindowTarget { + /// Returns the `XConnection` of this events loop. + #[inline] + pub fn x_connection(&self) -> &Arc { + &self.xconn } } From 30b4f8dc9f4ed8a32aac14486dcaaae73e9d9e01 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Date: Fri, 9 Aug 2019 02:10:54 +0300 Subject: [PATCH 27/61] Replace `set_decorations` with `set_prefers_status_bar_hidden` on iOS (#1092) --- CHANGELOG.md | 4 +++- FEATURES.md | 5 +++-- src/platform/ios.rs | 29 +++++++++++++++++++++++++++++ src/platform_impl/ios/view.rs | 8 ++++---- src/platform_impl/ios/window.rs | 22 ++++++++++++++-------- src/window.rs | 5 +---- 6 files changed, 54 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c73544e..0f15ae5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,9 @@ - On iOS, disable overscan compensation for external displays (removes black bars surrounding the image). - On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait. - +- On iOS, add `set_prefers_status_bar_hidden` extension function instead of + hijacking `set_decorations` for this purpose. + # 0.20.0 Alpha 2 (2019-07-09) - On X11, non-resizable windows now have maximize explicitly disabled. diff --git a/FEATURES.md b/FEATURES.md index fd4c2bf9..134aef96 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -140,6 +140,7 @@ If your PR makes notable changes to Winit's features, please update this section * Setting the `UIView` hidpi factor * Valid orientations * Home indicator visibility +* Status bar visibility * Deferrring system gestures * Support for custom `UIView` derived class * Getting the device idiom @@ -164,8 +165,8 @@ Legend: |Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ | |Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ | |Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A** | -|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|✔️ |**N/A** | -|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A** | +|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A** | +|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ | |Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❌ | |Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | diff --git a/src/platform/ios.rs b/src/platform/ios.rs index a2a06a65..fd69489a 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -80,6 +80,16 @@ pub trait WindowExtIOS { /// and then calls /// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc). fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge); + + /// Sets whether the [`Window`] prefers the status bar hidden. + /// + /// The default is to prefer showing the status bar. + /// + /// This changes the value returned by + /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc), + /// and then calls + /// [`-[UIViewController setNeedsStatusBarAppearanceUpdate]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc). + fn set_prefers_status_bar_hidden(&self, hidden: bool); } impl WindowExtIOS for Window { @@ -118,6 +128,11 @@ impl WindowExtIOS for Window { self.window .set_preferred_screen_edges_deferring_system_gestures(edges) } + + #[inline] + fn set_prefers_status_bar_hidden(&self, hidden: bool) { + self.window.set_prefers_status_bar_hidden(hidden) + } } /// Additional methods on [`WindowBuilder`] that are specific to iOS. @@ -163,6 +178,14 @@ pub trait WindowBuilderExtIOS { self, edges: ScreenEdge, ) -> WindowBuilder; + + /// Sets whether the [`Window`] prefers the status bar hidden. + /// + /// The default is to prefer showing the status bar. + /// + /// This sets the initial value returned by + /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc). + fn with_prefers_status_bar_hidden(self, hidden: bool) -> WindowBuilder; } impl WindowBuilderExtIOS for WindowBuilder { @@ -199,6 +222,12 @@ impl WindowBuilderExtIOS for WindowBuilder { .preferred_screen_edges_deferring_system_gestures = edges; self } + + #[inline] + fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> WindowBuilder { + self.platform_specific.prefers_status_bar_hidden = hidden; + self + } } /// Additional methods on [`MonitorHandle`] that are specific to iOS. diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 4bc5096a..2e385c6f 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -331,7 +331,7 @@ pub unsafe fn create_view( // requires main thread pub unsafe fn create_view_controller( - window_attributes: &WindowAttributes, + _window_attributes: &WindowAttributes, platform_attributes: &PlatformSpecificWindowBuilderAttributes, view: id, ) -> id { @@ -347,10 +347,10 @@ pub unsafe fn create_view_controller( !view_controller.is_null(), "Failed to initialize `UIViewController` instance" ); - let status_bar_hidden = if window_attributes.decorations { - NO - } else { + let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden { YES + } else { + NO }; let idiom = event_loop::get_idiom(); let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom( diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 7c8b9501..4e2167fc 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -214,14 +214,8 @@ impl Inner { } } - pub fn set_decorations(&self, decorations: bool) { - unsafe { - let status_bar_hidden = if decorations { NO } else { YES }; - let () = msg_send![ - self.view_controller, - setPrefersStatusBarHidden: status_bar_hidden - ]; - } + pub fn set_decorations(&self, _decorations: bool) { + warn!("`Window::set_decorations` is ignored on iOS") } pub fn set_always_on_top(&self, _always_on_top: bool) { @@ -414,6 +408,16 @@ impl Inner { ]; } } + + pub fn set_prefers_status_bar_hidden(&self, hidden: bool) { + unsafe { + let status_bar_hidden = if hidden { YES } else { NO }; + let () = msg_send![ + self.view_controller, + setPrefersStatusBarHidden: status_bar_hidden + ]; + } + } } impl Inner { @@ -536,6 +540,7 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub hidpi_factor: Option, pub valid_orientations: ValidOrientations, pub prefers_home_indicator_hidden: bool, + pub prefers_status_bar_hidden: bool, pub preferred_screen_edges_deferring_system_gestures: ScreenEdge, } @@ -546,6 +551,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { hidpi_factor: None, valid_orientations: Default::default(), prefers_home_indicator_hidden: false, + prefers_status_bar_hidden: false, preferred_screen_edges_deferring_system_gestures: Default::default(), } } diff --git a/src/window.rs b/src/window.rs index f983d791..36079051 100644 --- a/src/window.rs +++ b/src/window.rs @@ -585,10 +585,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS:** Can only be called on the main thread. Controls whether the status bar is hidden - /// via [`setPrefersStatusBarHidden`]. - /// - /// [`setPrefersStatusBarHidden`]: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc + /// - **iOS:** Has no effect. #[inline] pub fn set_decorations(&self, decorations: bool) { self.window.set_decorations(decorations) From 31ada5a052c79e16c7ad53ef030962e5f63a45d5 Mon Sep 17 00:00:00 2001 From: YVT Date: Fri, 9 Aug 2019 09:13:14 +0900 Subject: [PATCH 28/61] macOS/iOS: Fix auto trait impls of `EventLoopProxy` (#1084) * macOS/iOS: Fix auto trait impls of `EventLoopProxy` `EventLoopProxy` allows sending `T` from an arbitrary thread that owns the proxy object. Thus, if `T` is `!Send`, `EventLoopProxy` must not be allowed to leave the main thread. `EventLoopProxy` uses `std::sync::mpsc::Sender` under the hood, meaning the `!Sync` restriction of it also applies to `EventLoopProxy`. That is, even if `T` is thread-safe, a single `EventLoopProxy` object cannot be shared between threads. * Update `CHANGELOG.md` --- CHANGELOG.md | 3 ++- src/platform_impl/ios/event_loop.rs | 3 +-- src/platform_impl/macos/event_loop.rs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f15ae5a..4352b252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,8 @@ - On Linux, the functions `is_wayland`, `is_x11`, `xlib_xconnection` and `wayland_display` have been moved to a new `EventLoopWindowTargetExtUnix` trait. - On iOS, add `set_prefers_status_bar_hidden` extension function instead of hijacking `set_decorations` for this purpose. - +- On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`. + # 0.20.0 Alpha 2 (2019-07-09) - On X11, non-resizable windows now have maximize explicitly disabled. diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index adbdd7c3..956b910a 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -142,8 +142,7 @@ pub struct EventLoopProxy { source: CFRunLoopSourceRef, } -unsafe impl Send for EventLoopProxy {} -unsafe impl Sync for EventLoopProxy {} +unsafe impl Send for EventLoopProxy {} impl Clone for EventLoopProxy { fn clone(&self) -> EventLoopProxy { diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 32d8739c..b5fea9d1 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -113,8 +113,7 @@ pub struct Proxy { source: CFRunLoopSourceRef, } -unsafe impl Send for Proxy {} -unsafe impl Sync for Proxy {} +unsafe impl Send for Proxy {} impl Clone for Proxy { fn clone(&self) -> Self { From 7eed52a97a3639f5e284a750f47898b1244c90ec Mon Sep 17 00:00:00 2001 From: Emmanouil Katefidis Date: Sun, 11 Aug 2019 23:31:30 +0300 Subject: [PATCH 29/61] Update parking_lot to 0.9 (#1097) (#1099) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9c1678ef..5fb8a88b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,4 +78,4 @@ x11-dl = "2.18.3" 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.8" +version = "0.9" From 8e73287646396b23278a646b9a7d09f8e7decf03 Mon Sep 17 00:00:00 2001 From: satrix321 Date: Wed, 14 Aug 2019 00:09:34 +0200 Subject: [PATCH 30/61] Change 'proxy.rs' into 'custom_events.rs" (#1101) * changed i32 to CustomEvent enum * added a match case for custom event * minor cleanup * fixes #953 --- examples/custom_events.rs | 41 +++++++++++++++++++++++++++++++++++++++ examples/proxy.rs | 37 ----------------------------------- 2 files changed, 41 insertions(+), 37 deletions(-) create mode 100644 examples/custom_events.rs delete mode 100644 examples/proxy.rs diff --git a/examples/custom_events.rs b/examples/custom_events.rs new file mode 100644 index 00000000..c85d4ecd --- /dev/null +++ b/examples/custom_events.rs @@ -0,0 +1,41 @@ +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +#[derive(Debug, Clone, Copy)] +enum CustomEvent { + Timer, +} + +fn main() { + let event_loop = EventLoop::::with_user_event(); + + let _window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + // `EventLoopProxy` allows you to dispatch custom events to the main Winit event + // loop from any thread. + let event_loop_proxy = event_loop.create_proxy(); + + std::thread::spawn(move || { + // Wake up the `event_loop` once every second and dispatch a custom event + // from a different thread. + loop { + std::thread::sleep(std::time::Duration::from_secs(1)); + event_loop_proxy.send_event(CustomEvent::Timer).ok(); + } + }); + + 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, + }); +} diff --git a/examples/proxy.rs b/examples/proxy.rs deleted file mode 100644 index d078f230..00000000 --- a/examples/proxy.rs +++ /dev/null @@ -1,37 +0,0 @@ -use winit::{ - event::{Event, WindowEvent}, - event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, -}; - -fn main() { - let event_loop: EventLoop = EventLoop::with_user_event(); - - let _window = WindowBuilder::new() - .with_title("A fantastic window!") - .build(&event_loop) - .unwrap(); - - let proxy = event_loop.create_proxy(); - - std::thread::spawn(move || { - let mut counter = 0; - // Wake up the `event_loop` once every second. - loop { - std::thread::sleep(std::time::Duration::from_secs(1)); - proxy.send_event(counter).unwrap(); - counter += 1; - } - }); - - event_loop.run(move |event, _, control_flow| { - println!("{:?}", event); - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => *control_flow = ControlFlow::Exit, - _ => *control_flow = ControlFlow::Wait, - } - }); -} From 1366dc326ae62ad11f0a87688c7dda07d3c02481 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Date: Wed, 14 Aug 2019 01:12:13 +0300 Subject: [PATCH 31/61] Add touch pressure information for touch events on iOS (#1090) * Add touch pressure information for touch events on iOS * Add a variant for calibrated touch pressure --- CHANGELOG.md | 1 + FEATURES.md | 2 + src/event.rs | 88 ++++++++++++++++--- src/platform_impl/android/mod.rs | 1 + src/platform_impl/emscripten/mod.rs | 1 + src/platform_impl/ios/ffi.rs | 18 ++++ src/platform_impl/ios/view.rs | 27 +++++- src/platform_impl/linux/wayland/touch.rs | 4 + .../linux/x11/event_processor.rs | 1 + src/platform_impl/windows/event_loop.rs | 2 + 10 files changed, 131 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4352b252..95e7385b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - On iOS, add `set_prefers_status_bar_hidden` extension function instead of hijacking `set_decorations` for this purpose. - On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`. +- On iOS, add touch pressure information for touch events. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/FEATURES.md b/FEATURES.md index 134aef96..fe8e50ea 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -103,6 +103,7 @@ If your PR makes notable changes to Winit's features, please update this section - **Cursor grab**: Locking the cursor so it cannot exit the client area of a window. - **Cursor icon**: Changing the cursor icon, or hiding the cursor. - **Touch events**: Single-touch events. +- **Touch pressure**: Touch events contain information about the amount of force being applied. - **Multitouch**: Multi-touch events, including cancellation of a gesture. - **Keyboard events**: Properly processing keyboard events using the user-specified keymap and translating keypresses into UTF-8 characters, handling dead keys and IMEs. @@ -192,6 +193,7 @@ Legend: |Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ | |Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | +|Touch pressure |❌ |❌ |❌ |❌ |❌ |✔️ |❌ | |Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❌ | |Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | diff --git a/src/event.rs b/src/event.rs index 8e0ac043..401ed8b6 100644 --- a/src/event.rs +++ b/src/event.rs @@ -303,30 +303,94 @@ pub enum TouchPhase { Cancelled, } -/// Represents touch event +/// Represents a touch event /// -/// Every time user touches screen new Start event with some finger id is generated. -/// When the finger is removed from the screen End event with same id is generated. +/// Every time the user touches the screen, a new `Start` event with an unique +/// identifier for the finger is generated. When the finger is lifted, an `End` +/// event is generated with the same finger id. /// -/// For every id there will be at least 2 events with phases Start and End (or Cancelled). -/// There may be 0 or more Move events. +/// After a `Start` event has been emitted, there may be zero or more `Move` +/// events when the finger is moved or the touch pressure changes. /// +/// The finger id may be reused by the system after an `End` event. The user +/// should assume that a new `Start` event received with the same id has nothing +/// to do with the old finger and is a new finger. /// -/// Depending on platform implementation id may or may not be reused by system after End event. -/// -/// Gesture regonizer using this event should assume that Start event received with same id -/// as previously received End event is a new finger and has nothing to do with an old one. -/// -/// Touch may be cancelled if for example window lost focus. +/// A `Cancelled` event is emitted when the system has canceled tracking this +/// touch, such as when the window loses focus, or on iOS if the user moves the +/// device against their face. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Touch { pub device_id: DeviceId, pub phase: TouchPhase, pub location: LogicalPosition, - /// unique identifier of a finger. + /// Describes how hard the screen was pressed. May be `None` if the platform + /// does not support pressure sensitivity. + /// + /// ## Platform-specific + /// + /// - Only available on **iOS**. + pub force: Option, + /// Unique identifier of a finger. pub id: u64, } +/// Describes the force of a touch event +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Force { + /// On iOS, the force is calibrated so that the same number corresponds to + /// roughly the same amount of pressure on the screen regardless of the + /// device. + Calibrated { + /// The force of the touch, where a value of 1.0 represents the force of + /// an average touch (predetermined by the system, not user-specific). + /// + /// The force reported by Apple Pencil is measured along the axis of the + /// pencil. If you want a force perpendicular to the device, you need to + /// calculate this value using the `altitude_angle` value. + force: f64, + /// The maximum possible force for a touch. + /// + /// The value of this field is sufficiently high to provide a wide + /// dynamic range for values of the `force` field. + max_possible_force: f64, + /// The altitude (in radians) of the stylus. + /// + /// A value of 0 radians indicates that the stylus is parallel to the + /// surface. The value of this property is Pi/2 when the stylus is + /// perpendicular to the surface. + altitude_angle: Option, + }, + /// If the platform reports the force as normalized, we have no way of + /// knowing how much pressure 1.0 corresponds to – we know it's the maximum + /// amount of force, but as to how much force, you might either have to + /// press really really hard, or not hard at all, depending on the device. + Normalized(f64), +} + +impl Force { + /// Returns the force normalized to the range between 0.0 and 1.0 inclusive. + /// Instead of normalizing the force, you should prefer to handle + /// `Force::Calibrated` so that the amount of force the user has to apply is + /// consistent across devices. + pub fn normalized(&self) -> f64 { + match self { + Force::Calibrated { + force, + max_possible_force, + altitude_angle, + } => { + let force = match altitude_angle { + Some(altitude_angle) => force / altitude_angle.sin(), + None => *force, + }; + force / max_possible_force + } + Force::Normalized(force) => *force, + } + } +} + /// Hardware-dependent keyboard scan code. pub type ScanCode = u32; diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 42430d01..f88251ea 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -75,6 +75,7 @@ impl EventLoop { android_glue::MotionAction::Cancel => TouchPhase::Cancelled, }, location, + force: None, // TODO id: motion.pointer_id as u64, device_id: DEVICE_ID, }), diff --git a/src/platform_impl/emscripten/mod.rs b/src/platform_impl/emscripten/mod.rs index 9148dab0..2e9c9709 100644 --- a/src/platform_impl/emscripten/mod.rs +++ b/src/platform_impl/emscripten/mod.rs @@ -362,6 +362,7 @@ extern "C" fn touch_callback( phase, id: touch.identifier as u64, location, + force: None, // TODO }), }); } diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 1536c7b2..fcaf395e 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -70,6 +70,24 @@ pub enum UITouchPhase { Cancelled, } +#[derive(Debug, PartialEq)] +#[allow(dead_code)] +#[repr(isize)] +pub enum UIForceTouchCapability { + Unknown = 0, + Unavailable, + Available, +} + +#[derive(Debug, PartialEq)] +#[allow(dead_code)] +#[repr(isize)] +pub enum UITouchType { + Direct = 0, + Indirect, + Pencil, +} + #[repr(C)] #[derive(Debug, Clone)] pub struct UIEdgeInsets { diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 2e385c6f..dda1dbdf 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -6,13 +6,14 @@ use objc::{ }; use crate::{ - event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent}, + event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, platform::ios::MonitorHandleExtIOS, platform_impl::platform::{ app_state::AppState, event_loop, ffi::{ - id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UIRectEdge, UITouchPhase, + id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, + UIRectEdge, UITouchPhase, UITouchType, }, window::PlatformSpecificWindowBuilderAttributes, DeviceId, @@ -218,6 +219,27 @@ unsafe fn get_window_class() -> &'static Class { break; } let location: CGPoint = msg_send![touch, locationInView: nil]; + let touch_type: UITouchType = msg_send![touch, type]; + let trait_collection: id = msg_send![object, traitCollection]; + let touch_capability: UIForceTouchCapability = + msg_send![trait_collection, forceTouchCapability]; + let force = if touch_capability == UIForceTouchCapability::Available { + let force: CGFloat = msg_send![touch, force]; + let max_possible_force: CGFloat = msg_send![touch, maximumPossibleForce]; + let altitude_angle: Option = if touch_type == UITouchType::Pencil { + let angle: CGFloat = msg_send![touch, altitudeAngle]; + Some(angle as _) + } else { + None + }; + Some(Force::Calibrated { + force: force as _, + max_possible_force: max_possible_force as _, + altitude_angle, + }) + } else { + None + }; let touch_id = touch as u64; let phase: UITouchPhase = msg_send![touch, phase]; let phase = match phase { @@ -235,6 +257,7 @@ unsafe fn get_window_class() -> &'static Class { device_id: RootDeviceId(DeviceId { uiscreen }), id: touch_id, location: (location.x as f64, location.y as f64).into(), + force, phase, }), }); diff --git a/src/platform_impl/linux/wayland/touch.rs b/src/platform_impl/linux/wayland/touch.rs index b72be7b8..33644a86 100644 --- a/src/platform_impl/linux/wayland/touch.rs +++ b/src/platform_impl/linux/wayland/touch.rs @@ -39,6 +39,7 @@ pub(crate) fn implement_touch( ), phase: TouchPhase::Started, location: (x, y).into(), + force: None, // TODO id: id as u64, }), wid, @@ -61,6 +62,7 @@ pub(crate) fn implement_touch( ), phase: TouchPhase::Ended, location: pt.location.into(), + force: None, // TODO id: id as u64, }), pt.wid, @@ -78,6 +80,7 @@ pub(crate) fn implement_touch( ), phase: TouchPhase::Moved, location: (x, y).into(), + force: None, // TODO id: id as u64, }), pt.wid, @@ -94,6 +97,7 @@ pub(crate) fn implement_touch( ), phase: TouchPhase::Cancelled, location: pt.location.into(), + force: None, // TODO id: pt.id as u64, }), pt.wid, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 572889b4..4dea6d27 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -918,6 +918,7 @@ impl EventProcessor { device_id: mkdid(xev.deviceid), phase, location, + force: None, // TODO id: xev.detail as u64, }), }) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3b1052cf..749c0d5d 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1499,6 +1499,7 @@ unsafe extern "system" fn public_window_callback( continue; }, location, + force: None, // TODO id: input.dwID as u64, device_id: DEVICE_ID, }), @@ -1603,6 +1604,7 @@ unsafe extern "system" fn public_window_callback( continue; }, location, + force: None, // TODO id: pointer_info.pointerId as u64, device_id: DEVICE_ID, }), From 1aab328e2a6a57fbf6e27d823907fbb6e10422f9 Mon Sep 17 00:00:00 2001 From: mtak- Date: Tue, 13 Aug 2019 21:01:22 -0700 Subject: [PATCH 32/61] macos: fix an incorrect type signature on NSView drawRect (#1104) --- CHANGELOG.md | 1 + src/platform_impl/macos/view.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e7385b..8ddc5886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ hijacking `set_decorations` for this purpose. - On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`. - On iOS, add touch pressure information for touch events. +- On macOS, fix the signature of `-[NSView drawRect:]`. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 5a0fff9a..31a427c3 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -104,7 +104,7 @@ lazy_static! { ); decl.add_method( sel!(drawRect:), - draw_rect as extern "C" fn(&Object, Sel, id), + draw_rect as extern "C" fn(&Object, Sel, NSRect), ); decl.add_method( sel!(acceptsFirstResponder), @@ -280,7 +280,7 @@ extern "C" fn view_did_move_to_window(this: &Object, _sel: Sel) { trace!("Completed `viewDidMoveToWindow`"); } -extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: id) { +extern "C" fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) { unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); From 604016d69daaedab2bc29dc83a33535f7041591d Mon Sep 17 00:00:00 2001 From: Osspial Date: Wed, 14 Aug 2019 07:57:16 -0400 Subject: [PATCH 33/61] Implement raw_window_handle::HasRawWindowHandle for Window type (#1105) * Implement raw_window_handle::HasRawWindowHandle for Window type * Format * Address compilation issues * Fix Linux build hopefully * Fix iOS build --- CHANGELOG.md | 1 + Cargo.toml | 1 + src/platform_impl/ios/window.rs | 11 +++++++++++ src/platform_impl/linux/mod.rs | 8 ++++++++ src/platform_impl/linux/wayland/window.rs | 9 +++++++++ src/platform_impl/linux/x11/window.rs | 10 ++++++++++ src/platform_impl/macos/window.rs | 11 +++++++++++ src/platform_impl/windows/window.rs | 10 ++++++++++ src/window.rs | 6 ++++++ 9 files changed, 67 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ddc5886..29bb856a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ hijacking `set_decorations` for this purpose. - On macOS and iOS, corrected the auto trait impls of `EventLoopProxy`. - On iOS, add touch pressure information for touch events. +- Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms. - On macOS, fix the signature of `-[NSView drawRect:]`. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/Cargo.toml b/Cargo.toml index 5fb8a88b..3d87c38b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ libc = "0.2" log = "0.4" serde = { version = "1", optional = true, features = ["serde_derive"] } derivative = "1.0.2" +raw-window-handle = "0.1" [dev-dependencies] image = "0.21" diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 4e2167fc..46bc5c2e 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -1,3 +1,4 @@ +use raw_window_handle::{ios::IOSHandle, RawWindowHandle}; use std::{ collections::VecDeque, ops::{Deref, DerefMut}, @@ -250,6 +251,16 @@ impl Inner { pub fn id(&self) -> WindowId { self.window.into() } + + pub fn raw_window_handle(&self) -> RawWindowHandle { + let handle = IOSHandle { + ui_window: self.window as _, + ui_view: self.view as _, + ui_view_controller: self.view_controller as _, + ..IOSHandle::empty() + }; + RawWindowHandle::IOS(handle) + } } pub struct Window { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index f036f9f0..420fa469 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -3,6 +3,7 @@ use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc}; use parking_lot::Mutex; +use raw_window_handle::RawWindowHandle; use smithay_client_toolkit::reexports::client::ConnectError; pub use self::x11::XNotSupported; @@ -440,6 +441,13 @@ impl Window { &Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()), } } + + pub fn raw_window_handle(&self) -> RawWindowHandle { + match self { + &Window::X(ref window) => RawWindowHandle::X11(window.raw_window_handle()), + &Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()), + } + } } unsafe extern "C" fn x_error_callback( diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index b4a04aa7..ddd4b4b9 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -1,3 +1,4 @@ +use raw_window_handle::unix::WaylandHandle; use std::{ collections::VecDeque, sync::{Arc, Mutex, Weak}, @@ -333,6 +334,14 @@ impl Window { pub fn primary_monitor(&self) -> MonitorHandle { primary_monitor(&self.outputs) } + + 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 { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index e65d8836..0e63261c 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1,3 +1,4 @@ +use raw_window_handle::unix::X11Handle; use std::{ cmp, collections::HashSet, @@ -1375,4 +1376,13 @@ impl UnownedWindow { .unwrap() .insert(WindowId(self.xwindow)); } + + #[inline] + pub fn raw_window_handle(&self) -> X11Handle { + X11Handle { + window: self.xwindow, + display: self.xconn.display as _, + ..X11Handle::empty() + } + } } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index d0b0a557..75adf8a3 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1,3 +1,4 @@ +use raw_window_handle::{macos::MacOSHandle, RawWindowHandle}; use std::{ collections::VecDeque, f64, @@ -904,6 +905,16 @@ impl UnownedWindow { pub fn primary_monitor(&self) -> MonitorHandle { monitor::primary_monitor() } + + #[inline] + pub fn raw_window_handle(&self) -> RawWindowHandle { + let handle = MacOSHandle { + ns_window: *self.ns_window as *mut _, + ns_view: *self.ns_view as *mut _, + ..MacOSHandle::empty() + }; + RawWindowHandle::MacOS(handle) + } } impl WindowExtMacOS for UnownedWindow { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 39f6b478..8704ccfb 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -1,6 +1,7 @@ #![cfg(target_os = "windows")] use parking_lot::Mutex; +use raw_window_handle::{windows::WindowsHandle, RawWindowHandle}; use std::{ cell::Cell, ffi::OsStr, @@ -339,6 +340,15 @@ impl Window { self.window.0 } + #[inline] + pub fn raw_window_handle(&self) -> RawWindowHandle { + let handle = WindowsHandle { + hwnd: self.window.0 as *mut _, + ..WindowsHandle::empty() + }; + RawWindowHandle::Windows(handle) + } + #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { self.window_state.lock().mouse.cursor = cursor; diff --git a/src/window.rs b/src/window.rs index 36079051..0afb8354 100644 --- a/src/window.rs +++ b/src/window.rs @@ -721,6 +721,12 @@ impl Window { } } +unsafe impl raw_window_handle::HasRawWindowHandle for Window { + fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle { + self.window.raw_window_handle() + } +} + /// Describes the appearance of the mouse cursor. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] From 31110be3960bb97caeacdd0a8cbad292722f94f1 Mon Sep 17 00:00:00 2001 From: Osspial Date: Wed, 14 Aug 2019 11:09:47 -0400 Subject: [PATCH 34/61] Release alpha 3 (#1106) --- 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 29bb856a..43c11793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 0.20.0 Alpha 3 (2019-08-14) + - On macOS, drop the run closure on exit. - On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates. - On X11, fix delayed events after window redraw. diff --git a/Cargo.toml b/Cargo.toml index 3d87c38b..cc992c6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.20.0-alpha2" +version = "0.20.0-alpha3" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index 6abcecdf..3524bfaf 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ```toml [dependencies] -winit = "0.20.0-alpha2" +winit = "0.20.0-alpha3" ``` ## [Documentation](https://docs.rs/winit) From 7b707e7d758266fec28dd77ef8042be918e89f37 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 23 Aug 2019 12:30:53 +0300 Subject: [PATCH 35/61] macos: Implement run_return (#1108) * macos: Implement run_return * Update comments * Fix CHANGELOG.md --- CHANGELOG.md | 2 ++ src/platform_impl/macos/app_state.rs | 32 +++++++++++++++------------ src/platform_impl/macos/event_loop.rs | 29 ++++++++++++------------ 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c11793..b8594254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- On macOS, implement `run_return`. + # 0.20.0 Alpha 3 (2019-08-14) - On macOS, drop the run closure on exit. diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index fb020d72..4a18ed83 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -3,6 +3,7 @@ use std::{ fmt::{self, Debug}, hint::unreachable_unchecked, mem, + rc::Rc, sync::{ atomic::{AtomicBool, Ordering}, Mutex, MutexGuard, @@ -37,13 +38,13 @@ pub trait EventHandler: Debug { fn handle_user_events(&mut self, control_flow: &mut ControlFlow); } -struct EventLoopHandler { - callback: F, +struct EventLoopHandler { + callback: Box, &RootWindowTarget, &mut ControlFlow)>, will_exit: bool, - window_target: RootWindowTarget, + window_target: Rc>, } -impl Debug for EventLoopHandler { +impl Debug for EventLoopHandler { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter .debug_struct("EventLoopHandler") @@ -52,11 +53,7 @@ impl Debug for EventLoopHandler { } } -impl EventHandler for EventLoopHandler -where - F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), - T: 'static, -{ +impl EventHandler for EventLoopHandler { fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow) { (self.callback)(event.userify(), &self.window_target, control_flow); self.will_exit |= *control_flow == ControlFlow::Exit; @@ -180,13 +177,20 @@ impl Handler { pub enum AppState {} impl AppState { - pub fn set_callback(callback: F, window_target: RootWindowTarget) + // This function extends lifetime of `callback` to 'static as its side effect + pub unsafe fn set_callback(callback: F, window_target: Rc>) where - F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), - T: 'static, + F: FnMut(Event, &RootWindowTarget, &mut ControlFlow), { *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { - callback, + // This transmute is always safe, in case it was reached through `run`, since our + // lifetime will be already 'static. In other cases caller should ensure that all data + // 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::new(callback)), will_exit: false, window_target, })); @@ -299,7 +303,7 @@ impl AppState { } HANDLER.update_start_time(); match HANDLER.get_old_and_new_control_flow() { - (ControlFlow::Exit, _) | (_, ControlFlow::Exit) => unreachable!(), + (ControlFlow::Exit, _) | (_, ControlFlow::Exit) => (), (old, new) if old == new => (), (_, ControlFlow::Wait) => HANDLER.waker().stop(), (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index b5fea9d1..f3df1b52 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -1,5 +1,6 @@ use std::{ - collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, sync::mpsc, + collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, rc::Rc, + sync::mpsc, }; use cocoa::{ @@ -34,7 +35,7 @@ impl Default for EventLoopWindowTarget { } pub struct EventLoop { - window_target: RootWindowTarget, + window_target: Rc>, _delegate: IdRef, } @@ -59,10 +60,10 @@ impl EventLoop { }; setup_control_flow_observers(); EventLoop { - window_target: RootWindowTarget { + window_target: Rc::new(RootWindowTarget { p: Default::default(), _marker: PhantomData, - }, + }), _delegate: delegate, } } @@ -81,28 +82,28 @@ impl EventLoop { &self.window_target } - pub fn run(self, callback: F) -> ! + pub fn run(mut self, callback: F) -> ! where F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), + { + self.run_return(callback); + process::exit(0); + } + + pub fn run_return(&mut self, callback: F) + where + F: FnMut(Event, &RootWindowTarget, &mut ControlFlow), { unsafe { let _pool = NSAutoreleasePool::new(nil); let app = NSApp(); assert_ne!(app, nil); - AppState::set_callback(callback, self.window_target); + AppState::set_callback(callback, Rc::clone(&self.window_target)); let _: () = msg_send![app, run]; AppState::exit(); - process::exit(0) } } - pub fn run_return(&mut self, _callback: F) - where - F: FnMut(Event, &RootWindowTarget, &mut ControlFlow), - { - unimplemented!(); - } - pub fn create_proxy(&self) -> Proxy { Proxy::new(self.window_target.p.sender.clone()) } From f53683f01fb1924766968342007ea337f7ac3299 Mon Sep 17 00:00:00 2001 From: mtak- Date: Mon, 26 Aug 2019 15:47:23 -0700 Subject: [PATCH 36/61] iOS os version checking around certain APIs (#1094) * iOS os version checking * iOS, fix some incorrect msg_send return types * address nits, and fix OS version check for unsupported os versions * source for 60fps guarantee --- CHANGELOG.md | 1 + FEATURES.md | 1 + src/event.rs | 2 +- src/platform/ios.rs | 8 +++ src/platform_impl/ios/app_state.rs | 101 +++++++++++++++++++++++++- src/platform_impl/ios/event_loop.rs | 34 +-------- src/platform_impl/ios/monitor.rs | 25 ++++++- src/platform_impl/ios/view.rs | 106 +++++++++++++++++++--------- src/platform_impl/ios/window.rs | 12 ++-- 9 files changed, 209 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8594254..a3d7a299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - On iOS, add touch pressure information for touch events. - Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms. - On macOS, fix the signature of `-[NSView drawRect:]`. +- On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/FEATURES.md b/FEATURES.md index fe8e50ea..e8927957 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -134,6 +134,7 @@ If your PR makes notable changes to Winit's features, please update this section * Base window size ### iOS +* `winit` has a minimum OS requirement of iOS 8 * Get the `UIWindow` object pointer * Get the `UIViewController` object pointer * Get the `UIView` object pointer diff --git a/src/event.rs b/src/event.rs index 401ed8b6..e226bd70 100644 --- a/src/event.rs +++ b/src/event.rs @@ -329,7 +329,7 @@ pub struct Touch { /// /// ## Platform-specific /// - /// - Only available on **iOS**. + /// - Only available on **iOS** 9.0+. pub force: Option, /// Unique identifier of a finger. pub id: u64, diff --git a/src/platform/ios.rs b/src/platform/ios.rs index fd69489a..9218204f 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -70,6 +70,8 @@ pub trait WindowExtIOS { /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc), /// and then calls /// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc). + /// + /// This only has an effect on iOS 11.0+. fn set_prefers_home_indicator_hidden(&self, hidden: bool); /// Sets the screen edges for which the system gestures will take a lower priority than the @@ -79,6 +81,8 @@ pub trait WindowExtIOS { /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc), /// and then calls /// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc). + /// + /// This only has an effect on iOS 11.0+. fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge); /// Sets whether the [`Window`] prefers the status bar hidden. @@ -167,6 +171,8 @@ pub trait WindowBuilderExtIOS { /// /// This sets the initial value returned by /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc). + /// + /// This only has an effect on iOS 11.0+. fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder; /// Sets the screen edges for which the system gestures will take a lower priority than the @@ -174,6 +180,8 @@ pub trait WindowBuilderExtIOS { /// /// This sets the initial value returned by /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc). + /// + /// This only has an effect on iOS 11.0+. fn with_preferred_screen_edges_deferring_system_gestures( self, edges: ScreenEdge, diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index 32dae8ed..e8987e54 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -6,6 +6,8 @@ use std::{ time::Instant, }; +use objc::runtime::{BOOL, YES}; + use crate::{ event::{Event, StartCause}, event_loop::ControlFlow, @@ -16,7 +18,8 @@ use crate::platform_impl::platform::{ ffi::{ id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, - CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSUInteger, + CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSInteger, NSOperatingSystemVersion, + NSUInteger, }, }; @@ -126,7 +129,7 @@ impl AppState { .. } => { queued_windows.push(window); - msg_send![window, retain]; + let _: id = msg_send![window, retain]; return; } &mut AppStateImpl::ProcessingEvents { .. } => {} @@ -199,7 +202,7 @@ impl AppState { // completed. This may result in incorrect visual appearance. // ``` let screen: id = msg_send![window, screen]; - let () = msg_send![screen, retain]; + let _: id = msg_send![screen, retain]; let () = msg_send![window, setScreen:0 as id]; let () = msg_send![window, setScreen: screen]; let () = msg_send![screen, release]; @@ -618,3 +621,95 @@ impl EventLoopWaker { } } } + +macro_rules! os_capabilities { + ( + $( + $(#[$attr:meta])* + $error_name:ident: $objc_call:literal, + $name:ident: $major:literal-$minor:literal + ),* + $(,)* + ) => { + #[derive(Clone, Debug)] + pub struct OSCapabilities { + $( + pub $name: bool, + )* + + os_version: NSOperatingSystemVersion, + } + + impl From for OSCapabilities { + fn from(os_version: NSOperatingSystemVersion) -> OSCapabilities { + $(let $name = os_version.meets_requirements($major, $minor);)* + OSCapabilities { $($name,)* os_version, } + } + } + + impl OSCapabilities {$( + $(#[$attr])* + pub fn $error_name(&self, extra_msg: &str) { + log::warn!( + concat!("`", $objc_call, "` requires iOS {}.{}+. This device is running iOS {}.{}.{}. {}"), + $major, $minor, self.os_version.major, self.os_version.minor, self.os_version.patch, + extra_msg + ) + } + )*} + }; +} + +os_capabilities! { + /// https://developer.apple.com/documentation/uikit/uiview/2891103-safeareainsets?language=objc + #[allow(unused)] // error message unused + safe_area_err_msg: "-[UIView safeAreaInsets]", + safe_area: 11-0, + /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc + home_indicator_hidden_err_msg: "-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]", + home_indicator_hidden: 11-0, + /// https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc + defer_system_gestures_err_msg: "-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystem]", + defer_system_gestures: 11-0, + /// https://developer.apple.com/documentation/uikit/uiscreen/2806814-maximumframespersecond?language=objc + maximum_frames_per_second_err_msg: "-[UIScreen maximumFramesPerSecond]", + maximum_frames_per_second: 10-3, + /// https://developer.apple.com/documentation/uikit/uitouch/1618110-force?language=objc + #[allow(unused)] // error message unused + force_touch_err_msg: "-[UITouch force]", + force_touch: 9-0, +} + +impl NSOperatingSystemVersion { + fn meets_requirements(&self, required_major: NSInteger, required_minor: NSInteger) -> bool { + (self.major, self.minor) >= (required_major, required_minor) + } +} + +pub fn os_capabilities() -> OSCapabilities { + lazy_static! { + static ref OS_CAPABILITIES: OSCapabilities = { + let version: NSOperatingSystemVersion = unsafe { + let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; + let atleast_ios_8: BOOL = msg_send![ + process_info, + respondsToSelector: sel!(operatingSystemVersion) + ]; + // winit requires atleast iOS 8 because no one has put the time into supporting earlier os versions. + // Older iOS versions are increasingly difficult to test. For example, Xcode 11 does not support + // debugging on devices with an iOS version of less than 8. Another example, in order to use an iOS + // simulator older than iOS 8, you must download an older version of Xcode (<9), and at least Xcode 7 + // has been tested to not even run on macOS 10.15 - Xcode 8 might? + // + // The minimum required iOS version is likely to grow in the future. + assert!( + atleast_ios_8 == YES, + "`winit` requires iOS version 8 or greater" + ); + msg_send![process_info, operatingSystemVersion] + }; + version.into() + }; + } + OS_CAPABILITIES.clone() +} diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 956b910a..b5b56739 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -23,8 +23,7 @@ use crate::platform_impl::platform::{ CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, - CFRunLoopSourceSignal, CFRunLoopWakeUp, NSOperatingSystemVersion, NSString, - UIApplicationMain, UIUserInterfaceIdiom, + CFRunLoopSourceSignal, CFRunLoopWakeUp, NSString, UIApplicationMain, UIUserInterfaceIdiom, }, monitor, view, MonitorHandle, }; @@ -32,13 +31,6 @@ use crate::platform_impl::platform::{ pub struct EventLoopWindowTarget { receiver: Receiver, sender_to_clone: Sender, - capabilities: Capabilities, -} - -impl EventLoopWindowTarget { - pub fn capabilities(&self) -> &Capabilities { - &self.capabilities - } } pub struct EventLoop { @@ -64,18 +56,11 @@ impl EventLoop { // this line sets up the main run loop before `UIApplicationMain` setup_control_flow_observers(); - let version: NSOperatingSystemVersion = unsafe { - let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; - msg_send![process_info, operatingSystemVersion] - }; - let capabilities = version.into(); - EventLoop { window_target: RootEventLoopWindowTarget { p: EventLoopWindowTarget { receiver, sender_to_clone, - capabilities, }, _marker: PhantomData, }, @@ -296,20 +281,3 @@ pub unsafe fn get_idiom() -> Idiom { let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom]; raw_idiom.into() } - -pub struct Capabilities { - pub supports_safe_area: bool, -} - -impl From for Capabilities { - fn from(os_version: NSOperatingSystemVersion) -> Capabilities { - assert!( - os_version.major >= 8, - "`winit` current requires iOS version 8 or greater" - ); - - let supports_safe_area = os_version.major >= 11; - - Capabilities { supports_safe_area } - } -} diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index 96b6f7eb..14279f66 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -7,7 +7,10 @@ use std::{ use crate::{ dpi::{PhysicalPosition, PhysicalSize}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, - platform_impl::platform::ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger}, + platform_impl::platform::{ + app_state, + ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger}, + }, }; #[derive(Debug, PartialEq, Eq, Hash)] @@ -35,7 +38,7 @@ impl Drop for VideoMode { fn drop(&mut self) { unsafe { assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS"); - msg_send![self.screen_mode, release]; + let () = msg_send![self.screen_mode, release]; } } } @@ -43,7 +46,23 @@ impl Drop for VideoMode { 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"); - let refresh_rate: NSInteger = msg_send![uiscreen, maximumFramesPerSecond]; + let os_capabilities = app_state::os_capabilities(); + let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second { + msg_send![uiscreen, maximumFramesPerSecond] + } else { + // https://developer.apple.com/library/archive/technotes/tn2460/_index.html + // https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison + // + // All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not + // supported, they are all guaranteed to have 60hz refresh rates. This does not + // correctly handle external displays. ProMotion displays support 120fps, but they were + // introduced at the same time as the `maximumFramesPerSecond` API. + // + // FIXME: earlier OSs could calculate the refresh rate using + // `-[CADisplayLink duration]`. + os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps"); + 60 + }; let size: CGSize = msg_send![screen_mode, size]; VideoMode { size: (size.width as u32, size.height as u32), diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index dda1dbdf..e871ab46 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -9,7 +9,7 @@ use crate::{ event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, platform::ios::MonitorHandleExtIOS, platform_impl::platform::{ - app_state::AppState, + app_state::{self, AppState, OSCapabilities}, event_loop, ffi::{ id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, @@ -27,24 +27,49 @@ macro_rules! add_property { $name:ident: $t:ty, $setter_name:ident: |$object:ident| $after_set:expr, $getter_name:ident, + ) => { + add_property!( + $decl, + $name: $t, + $setter_name: true, |_, _|{}; |$object| $after_set, + $getter_name, + ) + }; + ( + $decl:ident, + $name:ident: $t:ty, + $setter_name:ident: $capability:expr, $err:expr; |$object:ident| $after_set:expr, + $getter_name:ident, ) => { { const VAR_NAME: &'static str = concat!("_", stringify!($name)); $decl.add_ivar::<$t>(VAR_NAME); - #[allow(non_snake_case)] - extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) { - unsafe { - $object.set_ivar::<$t>(VAR_NAME, value); + let setter = if $capability { + #[allow(non_snake_case)] + extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) { + unsafe { + $object.set_ivar::<$t>(VAR_NAME, value); + } + $after_set } - $after_set - } + $setter_name + } else { + #[allow(non_snake_case)] + extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) { + unsafe { + $object.set_ivar::<$t>(VAR_NAME, value); + } + $err(&app_state::os_capabilities(), "ignoring") + } + $setter_name + }; #[allow(non_snake_case)] extern "C" fn $getter_name($object: &Object, _: Sel) -> $t { unsafe { *$object.get_ivar::<$t>(VAR_NAME) } } $decl.add_method( sel!($setter_name:), - $setter_name as extern "C" fn(&mut Object, Sel, $t), + setter as extern "C" fn(&mut Object, Sel, $t), ); $decl.add_method( sel!($getter_name), @@ -125,6 +150,8 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { unsafe fn get_view_controller_class() -> &'static Class { static mut CLASS: Option<&'static Class> = None; if CLASS.is_none() { + let os_capabilities = app_state::os_capabilities(); + let uiviewcontroller_class = class!(UIViewController); extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL { @@ -150,11 +177,14 @@ unsafe fn get_view_controller_class() -> &'static Class { add_property! { decl, prefers_home_indicator_auto_hidden: BOOL, - setPrefersHomeIndicatorAutoHidden: |object| { - unsafe { - let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden]; - } - }, + setPrefersHomeIndicatorAutoHidden: + os_capabilities.home_indicator_hidden, + OSCapabilities::home_indicator_hidden_err_msg; + |object| { + unsafe { + let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden]; + } + }, prefersHomeIndicatorAutoHidden, } add_property! { @@ -170,11 +200,14 @@ unsafe fn get_view_controller_class() -> &'static Class { add_property! { decl, preferred_screen_edges_deferring_system_gestures: UIRectEdge, - setPreferredScreenEdgesDeferringSystemGestures: |object| { - unsafe { - let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; - } - }, + setPreferredScreenEdgesDeferringSystemGestures: + os_capabilities.defer_system_gestures, + OSCapabilities::defer_system_gestures_err_msg; + |object| { + unsafe { + let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures]; + } + }, preferredScreenEdgesDeferringSystemGestures, } CLASS = Some(decl.register()); @@ -213,6 +246,7 @@ unsafe fn get_window_class() -> &'static Class { let uiscreen = msg_send![object, screen]; let touches_enum: id = msg_send![touches, objectEnumerator]; let mut touch_events = Vec::new(); + let os_supports_force = app_state::os_capabilities().force_touch; loop { let touch: id = msg_send![touches_enum, nextObject]; if touch == nil { @@ -220,23 +254,29 @@ unsafe fn get_window_class() -> &'static Class { } let location: CGPoint = msg_send![touch, locationInView: nil]; let touch_type: UITouchType = msg_send![touch, type]; - let trait_collection: id = msg_send![object, traitCollection]; - let touch_capability: UIForceTouchCapability = - msg_send![trait_collection, forceTouchCapability]; - let force = if touch_capability == UIForceTouchCapability::Available { - let force: CGFloat = msg_send![touch, force]; - let max_possible_force: CGFloat = msg_send![touch, maximumPossibleForce]; - let altitude_angle: Option = if touch_type == UITouchType::Pencil { - let angle: CGFloat = msg_send![touch, altitudeAngle]; - Some(angle as _) + let force = if os_supports_force { + let trait_collection: id = msg_send![object, traitCollection]; + let touch_capability: UIForceTouchCapability = + msg_send![trait_collection, forceTouchCapability]; + // Both the OS _and_ the device need to be checked for force touch support. + if touch_capability == UIForceTouchCapability::Available { + let force: CGFloat = msg_send![touch, force]; + let max_possible_force: CGFloat = + msg_send![touch, maximumPossibleForce]; + let altitude_angle: Option = if touch_type == UITouchType::Pencil { + let angle: CGFloat = msg_send![touch, altitudeAngle]; + Some(angle as _) + } else { + None + }; + Some(Force::Calibrated { + force: force as _, + max_possible_force: max_possible_force as _, + altitude_angle, + }) } else { None - }; - Some(Force::Calibrated { - force: force as _, - max_possible_force: max_possible_force as _, - altitude_angle, - }) + } } else { None }; diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 46bc5c2e..cca2923d 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -13,7 +13,7 @@ use crate::{ monitor::MonitorHandle as RootMonitorHandle, platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations}, platform_impl::platform::{ - app_state::AppState, + app_state::{self, AppState}, event_loop, ffi::{ id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, @@ -28,7 +28,6 @@ pub struct Inner { pub window: id, pub view_controller: id, pub view: id, - supports_safe_area: bool, } impl Drop for Inner { @@ -300,7 +299,7 @@ impl DerefMut for Window { impl Window { pub fn new( - event_loop: &EventLoopWindowTarget, + _event_loop: &EventLoopWindowTarget, window_attributes: WindowAttributes, platform_attributes: PlatformSpecificWindowBuilderAttributes, ) -> Result { @@ -347,14 +346,11 @@ impl Window { view_controller, ); - let supports_safe_area = event_loop.capabilities().supports_safe_area; - let result = Window { inner: Inner { window, view_controller, view, - supports_safe_area, }, }; AppState::set_key_window(window); @@ -396,7 +392,7 @@ impl Inner { msg_send![ self.view_controller, setSupportedInterfaceOrientations: supported_orientations - ]; + ] } } @@ -462,7 +458,7 @@ impl Inner { // requires main thread unsafe fn safe_area_screen_space(&self) -> CGRect { let bounds: CGRect = msg_send![self.window, bounds]; - if self.supports_safe_area { + if app_state::os_capabilities().safe_area { let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets]; let safe_bounds = CGRect { origin: CGPoint { From dd99b3bd736a6ac690184f4f5e4ac6caa93d0427 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Date: Tue, 27 Aug 2019 02:56:10 +0300 Subject: [PATCH 37/61] Fix inverted parameter in `set_prefers_home_indicator_hidden` on iOS (#1123) --- CHANGELOG.md | 1 + src/platform_impl/ios/window.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3d7a299..6d5b2a5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On macOS, implement `run_return`. +- On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index cca2923d..0655add1 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -398,7 +398,7 @@ impl Inner { pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { unsafe { - let prefers_home_indicator_hidden = if hidden { NO } else { YES }; + let prefers_home_indicator_hidden = if hidden { YES } else { NO }; let () = msg_send![ self.view_controller, setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden From f085b7349c711a8a5cd295105c7ced5ac7878c5f Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 26 Aug 2019 22:06:40 -0400 Subject: [PATCH 38/61] Remove outdated noop comment (#1126) --- src/window.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/window.rs b/src/window.rs index 0afb8354..7866b70d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -415,8 +415,6 @@ impl Window { /// /// See `outer_position` for more information about the coordinates. /// - /// This is a no-op if the window has already been closed. - /// /// ## Platform-specific /// /// - **iOS:** Can only be called on the main thread. Sets the top left coordinates of the From 0b497b62d8b6bb4b077b6d0e1e595f592806b394 Mon Sep 17 00:00:00 2001 From: Murarth Date: Mon, 26 Aug 2019 19:06:59 -0700 Subject: [PATCH 39/61] X11: Improve performance of `Window::set_cursor_icon` (#1116) * X11: Fix performance issue with rapidly resetting cursor icon * When setting cursor icon, if the new icon value is the same as the current value, no messages are sent the X server. * X11: Cache cursor objects in XConnection * Add changelog entry --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/util/cursor.rs | 129 ++++++++++++++++++++ src/platform_impl/linux/x11/util/mod.rs | 1 + src/platform_impl/linux/x11/window.rs | 132 ++------------------- src/platform_impl/linux/x11/xdisplay.rs | 6 +- 5 files changed, 143 insertions(+), 126 deletions(-) create mode 100644 src/platform_impl/linux/x11/util/cursor.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d5b2a5c..a37fe9aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - On macOS, implement `run_return`. - On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`. +- On X11, performance is improved when rapidly calling `Window::set_cursor_icon`. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/linux/x11/util/cursor.rs b/src/platform_impl/linux/x11/util/cursor.rs new file mode 100644 index 00000000..684af49d --- /dev/null +++ b/src/platform_impl/linux/x11/util/cursor.rs @@ -0,0 +1,129 @@ +use crate::window::CursorIcon; + +use super::*; + +impl XConnection { + pub fn set_cursor_icon(&self, window: ffi::Window, cursor: Option) { + let cursor = *self + .cursor_cache + .lock() + .entry(cursor) + .or_insert_with(|| self.get_cursor(cursor)); + + self.update_cursor(window, cursor); + } + + fn create_empty_cursor(&self) -> ffi::Cursor { + let data = 0; + let pixmap = unsafe { + let screen = (self.xlib.XDefaultScreen)(self.display); + let window = (self.xlib.XRootWindow)(self.display, screen); + (self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1) + }; + + if pixmap == 0 { + panic!("failed to allocate pixmap for cursor"); + } + + unsafe { + // We don't care about this color, since it only fills bytes + // in the pixmap which are not 0 in the mask. + let mut dummy_color = MaybeUninit::uninit(); + let cursor = (self.xlib.XCreatePixmapCursor)( + self.display, + pixmap, + pixmap, + dummy_color.as_mut_ptr(), + dummy_color.as_mut_ptr(), + 0, + 0, + ); + (self.xlib.XFreePixmap)(self.display, pixmap); + + cursor + } + } + + fn load_cursor(&self, name: &[u8]) -> ffi::Cursor { + unsafe { + (self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char) + } + } + + fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor { + for name in names.iter() { + let xcursor = self.load_cursor(name); + if xcursor != 0 { + return xcursor; + } + } + 0 + } + + fn get_cursor(&self, cursor: Option) -> ffi::Cursor { + let cursor = match cursor { + Some(cursor) => cursor, + None => return self.create_empty_cursor(), + }; + + let load = |name: &[u8]| self.load_cursor(name); + + let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names); + + // Try multiple names in some cases where the name + // differs on the desktop environments or themes. + // + // Try the better looking (or more suiting) names first. + match cursor { + CursorIcon::Alias => load(b"link\0"), + CursorIcon::Arrow => load(b"arrow\0"), + CursorIcon::Cell => load(b"plus\0"), + CursorIcon::Copy => load(b"copy\0"), + CursorIcon::Crosshair => load(b"crosshair\0"), + CursorIcon::Default => load(b"left_ptr\0"), + CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]), + CursorIcon::Help => load(b"question_arrow\0"), + CursorIcon::Move => load(b"move\0"), + CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]), + CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]), + CursorIcon::Progress => load(b"left_ptr_watch\0"), + CursorIcon::AllScroll => load(b"all-scroll\0"), + CursorIcon::ContextMenu => load(b"context-menu\0"), + + CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]), + CursorIcon::NotAllowed => load(b"crossed_circle\0"), + + // Resize cursors + CursorIcon::EResize => load(b"right_side\0"), + CursorIcon::NResize => load(b"top_side\0"), + CursorIcon::NeResize => load(b"top_right_corner\0"), + CursorIcon::NwResize => load(b"top_left_corner\0"), + CursorIcon::SResize => load(b"bottom_side\0"), + CursorIcon::SeResize => load(b"bottom_right_corner\0"), + CursorIcon::SwResize => load(b"bottom_left_corner\0"), + CursorIcon::WResize => load(b"left_side\0"), + CursorIcon::EwResize => load(b"h_double_arrow\0"), + CursorIcon::NsResize => load(b"v_double_arrow\0"), + CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]), + CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]), + CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]), + CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]), + + CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]), + CursorIcon::VerticalText => load(b"vertical-text\0"), + + CursorIcon::Wait => load(b"watch\0"), + + CursorIcon::ZoomIn => load(b"zoom-in\0"), + CursorIcon::ZoomOut => load(b"zoom-out\0"), + } + } + + fn update_cursor(&self, window: ffi::Window, cursor: ffi::Cursor) { + unsafe { + (self.xlib.XDefineCursor)(self.display, window, cursor); + + self.flush_requests().expect("Failed to set the cursor"); + } + } +} diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 58e0c332..dba0f9d2 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -3,6 +3,7 @@ mod atom; mod client_msg; +mod cursor; mod format; mod geometry; mod hint; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 0e63261c..73e53987 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -4,7 +4,7 @@ use std::{ collections::HashSet, env, ffi::CString, - mem::{self, MaybeUninit}, + mem::{self, replace, MaybeUninit}, os::raw::*, path::Path, ptr, slice, @@ -1124,131 +1124,14 @@ impl UnownedWindow { unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ } } - fn load_cursor(&self, name: &[u8]) -> ffi::Cursor { - unsafe { - (self.xconn.xcursor.XcursorLibraryLoadCursor)( - self.xconn.display, - name.as_ptr() as *const c_char, - ) - } - } - - fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor { - for name in names.iter() { - let xcursor = self.load_cursor(name); - if xcursor != 0 { - return xcursor; - } - } - 0 - } - - fn get_cursor(&self, cursor: CursorIcon) -> ffi::Cursor { - let load = |name: &[u8]| self.load_cursor(name); - - let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names); - - // Try multiple names in some cases where the name - // differs on the desktop environments or themes. - // - // Try the better looking (or more suiting) names first. - match cursor { - CursorIcon::Alias => load(b"link\0"), - CursorIcon::Arrow => load(b"arrow\0"), - CursorIcon::Cell => load(b"plus\0"), - CursorIcon::Copy => load(b"copy\0"), - CursorIcon::Crosshair => load(b"crosshair\0"), - CursorIcon::Default => load(b"left_ptr\0"), - CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]), - CursorIcon::Help => load(b"question_arrow\0"), - CursorIcon::Move => load(b"move\0"), - CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]), - CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]), - CursorIcon::Progress => load(b"left_ptr_watch\0"), - CursorIcon::AllScroll => load(b"all-scroll\0"), - CursorIcon::ContextMenu => load(b"context-menu\0"), - - CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]), - CursorIcon::NotAllowed => load(b"crossed_circle\0"), - - // Resize cursors - CursorIcon::EResize => load(b"right_side\0"), - CursorIcon::NResize => load(b"top_side\0"), - CursorIcon::NeResize => load(b"top_right_corner\0"), - CursorIcon::NwResize => load(b"top_left_corner\0"), - CursorIcon::SResize => load(b"bottom_side\0"), - CursorIcon::SeResize => load(b"bottom_right_corner\0"), - CursorIcon::SwResize => load(b"bottom_left_corner\0"), - CursorIcon::WResize => load(b"left_side\0"), - CursorIcon::EwResize => load(b"h_double_arrow\0"), - CursorIcon::NsResize => load(b"v_double_arrow\0"), - CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]), - CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]), - CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]), - CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]), - - CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]), - CursorIcon::VerticalText => load(b"vertical-text\0"), - - CursorIcon::Wait => load(b"watch\0"), - - CursorIcon::ZoomIn => load(b"zoom-in\0"), - CursorIcon::ZoomOut => load(b"zoom-out\0"), - } - } - - fn update_cursor(&self, cursor: ffi::Cursor) { - unsafe { - (self.xconn.xlib.XDefineCursor)(self.xconn.display, self.xwindow, cursor); - if cursor != 0 { - (self.xconn.xlib.XFreeCursor)(self.xconn.display, cursor); - } - self.xconn - .flush_requests() - .expect("Failed to set or free the cursor"); - } - } - #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { - *self.cursor.lock() = cursor; - if *self.cursor_visible.lock() { - self.update_cursor(self.get_cursor(cursor)); + let old_cursor = replace(&mut *self.cursor.lock(), cursor); + if cursor != old_cursor && *self.cursor_visible.lock() { + self.xconn.set_cursor_icon(self.xwindow, Some(cursor)); } } - // TODO: This could maybe be cached. I don't think it's worth - // the complexity, since cursor changes are not so common, - // and this is just allocating a 1x1 pixmap... - fn create_empty_cursor(&self) -> Option { - let data = 0; - let pixmap = unsafe { - (self.xconn.xlib.XCreateBitmapFromData)(self.xconn.display, self.xwindow, &data, 1, 1) - }; - if pixmap == 0 { - // Failed to allocate - return None; - } - - let cursor = unsafe { - // We don't care about this color, since it only fills bytes - // in the pixmap which are not 0 in the mask. - let mut dummy_color = MaybeUninit::uninit(); - let cursor = (self.xconn.xlib.XCreatePixmapCursor)( - self.xconn.display, - pixmap, - pixmap, - dummy_color.as_mut_ptr(), - dummy_color.as_mut_ptr(), - 0, - 0, - ); - (self.xconn.xlib.XFreePixmap)(self.xconn.display, pixmap); - cursor - }; - Some(cursor) - } - #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { let mut grabbed_lock = self.cursor_grabbed.lock(); @@ -1318,14 +1201,13 @@ impl UnownedWindow { return; } let cursor = if visible { - self.get_cursor(*self.cursor.lock()) + Some(*self.cursor.lock()) } else { - self.create_empty_cursor() - .expect("Failed to create empty cursor") + None }; *visible_lock = visible; drop(visible_lock); - self.update_cursor(cursor); + self.xconn.set_cursor_icon(self.xwindow, cursor); } #[inline] diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index 79ace84f..176323ec 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -1,8 +1,10 @@ -use std::{error::Error, fmt, os::raw::c_int, ptr}; +use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr}; use libc; use parking_lot::Mutex; +use crate::window::CursorIcon; + use super::ffi; /// A connection to an X server. @@ -19,6 +21,7 @@ pub struct XConnection { pub display: *mut ffi::Display, pub x11_fd: c_int, pub latest_error: Mutex>, + pub cursor_cache: Mutex, ffi::Cursor>>, } unsafe impl Send for XConnection {} @@ -64,6 +67,7 @@ impl XConnection { display, x11_fd: fd, latest_error: Mutex::new(None), + cursor_cache: Default::default(), }) } From b03e589987d7c95b526ad5d85ae8f1f3d0f5d050 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 26 Aug 2019 22:07:15 -0400 Subject: [PATCH 40/61] On Windows, Unset maximized when transforming window (#1014) * Unset maximized when functionally transforming window * Add docs * Fix compile issues --- CHANGELOG.md | 1 + src/platform_impl/windows/window.rs | 18 ++++++++++++++++++ src/window.rs | 6 ++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a37fe9aa..3cfd9f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and `WindowEvent::HoveredFile`. - On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected. - On Windows, fix `with_maximized` not properly setting window size to entire window. - On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`. +- On Windows, unset `maximized` when manually changing the window's position or size. # 0.20.0 Alpha 1 (2019-06-21) diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 8704ccfb..5b55c7c8 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -212,6 +212,15 @@ impl Window { 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); } @@ -287,6 +296,15 @@ impl Window { pub fn set_inner_size(&self, logical_size: LogicalSize) { let dpi_factor = self.hidpi_factor(); let (width, height) = logical_size.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_inner_size_physical(width, height); } diff --git a/src/window.rs b/src/window.rs index 7866b70d..db86773d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -413,7 +413,8 @@ impl Window { /// Modifies the position of the window. /// - /// See `outer_position` for more information about the coordinates. + /// See `outer_position` for more information about the coordinates. This automatically un-maximizes the + /// window if it's maximized. /// /// ## Platform-specific /// @@ -443,7 +444,8 @@ impl Window { /// Modifies the inner size of the window. /// - /// See `inner_size` for more information about the values. + /// See `inner_size` for more information about the values. This automatically un-maximizes the + /// window if it's maximized. /// /// ## Platform-specific /// From 1e7376847b4f65e4e888aa9768329550c6d26a48 Mon Sep 17 00:00:00 2001 From: Osspial Date: Tue, 27 Aug 2019 19:20:24 -0400 Subject: [PATCH 41/61] Move changelog entries into proper position (#1131) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cfd9f5c..e9a4f05e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ - On macOS, implement `run_return`. - On iOS, fix inverted parameter in `set_prefers_home_indicator_hidden`. - On X11, performance is improved when rapidly calling `Window::set_cursor_icon`. +- On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized. +- On Windows, unset `maximized` when manually changing the window's position or size. # 0.20.0 Alpha 3 (2019-08-14) @@ -31,7 +33,6 @@ - On iOS, add touch pressure information for touch events. - Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms. - On macOS, fix the signature of `-[NSView drawRect:]`. -- On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized. # 0.20.0 Alpha 2 (2019-07-09) @@ -51,7 +52,6 @@ and `WindowEvent::HoveredFile`. - On Windows, fix the trail effect happening on transparent decorated windows. Borderless (or un-decorated) windows were not affected. - On Windows, fix `with_maximized` not properly setting window size to entire window. - On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`. -- On Windows, unset `maximized` when manually changing the window's position or size. # 0.20.0 Alpha 1 (2019-06-21) From c99bba1655470b8b48b00333ee728b0c4a716500 Mon Sep 17 00:00:00 2001 From: Murarth Date: Sat, 31 Aug 2019 01:58:45 -0700 Subject: [PATCH 42/61] Remove unused unix dlopen module (#1138) --- src/platform_impl/linux/dlopen.rs | 15 --------------- src/platform_impl/linux/mod.rs | 1 - 2 files changed, 16 deletions(-) delete mode 100644 src/platform_impl/linux/dlopen.rs diff --git a/src/platform_impl/linux/dlopen.rs b/src/platform_impl/linux/dlopen.rs deleted file mode 100644 index 8be592e4..00000000 --- a/src/platform_impl/linux/dlopen.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] -#![allow(dead_code)] - -use std::os::raw::{c_char, c_int, c_void}; - -pub const RTLD_LAZY: c_int = 0x001; -pub const RTLD_NOW: c_int = 0x002; - -#[link(name = "dl")] -extern "C" { - pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void; - pub fn dlerror() -> *mut c_char; - pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void; - pub fn dlclose(handle: *mut c_void) -> c_int; -} diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 420fa469..bc960190 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -18,7 +18,6 @@ use crate::{ window::{CursorIcon, Fullscreen, WindowAttributes}, }; -mod dlopen; pub mod wayland; pub mod x11; From bfcd85ab150d088be254f43870e7d485f6f95348 Mon Sep 17 00:00:00 2001 From: mtak- Date: Wed, 4 Sep 2019 14:23:11 -0700 Subject: [PATCH 43/61] [ios] Groundwork for new Redraw API, refactoring AppState, and bugfixes (#1133) * fix #1087. the CFRunLoopTimer was never started if the user never changed the controlflow. * RedrawRequested ordering matches the new redraw api consistent asserts lots of appstate refactoring to rely less on unsafe, and hopefully make it easier to maintain * ios: dpi bugfix. inputs to setContentScaleFactor are not to be trusted as iOS uses 0.0 as a sentinel value for "default device dpi". the fix is to always go through the getter. * move touch handling onto uiview * update changelog * rustfmt weirdness * fix use option around nullable function pointers in ffi * Document why gl and metal views don't use setNeedsDisplay * change main events cleared observer priority to 0 instead of magic number log when processing non-redraw events when we expect to only be processing redraw events --- CHANGELOG.md | 4 + src/platform_impl/ios/app_state.rs | 1041 +++++++++++++++++---------- src/platform_impl/ios/event_loop.rs | 49 +- src/platform_impl/ios/ffi.rs | 16 +- src/platform_impl/ios/view.rs | 288 ++++---- src/platform_impl/ios/window.rs | 33 +- 6 files changed, 880 insertions(+), 551 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9a4f05e..8dbb42c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,10 @@ - On iOS, add touch pressure information for touch events. - Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms. - On macOS, fix the signature of `-[NSView drawRect:]`. +- On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application. +- 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. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index e8987e54..5b5b726b 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -1,6 +1,9 @@ +#![deny(unused_results)] + use std::{ cell::{RefCell, RefMut}, - mem::{self, ManuallyDrop}, + collections::HashSet, + mem, os::raw::c_void, ptr, time::Instant, @@ -9,45 +12,85 @@ use std::{ use objc::runtime::{BOOL, YES}; use crate::{ - event::{Event, StartCause}, + event::{Event, StartCause, WindowEvent}, event_loop::ControlFlow, -}; - -use crate::platform_impl::platform::{ - event_loop::{EventHandler, Never}, - ffi::{ - id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, - CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, - CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSInteger, NSOperatingSystemVersion, - NSUInteger, + platform_impl::platform::{ + event_loop::{EventHandler, Never}, + ffi::{ + id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, + CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, + CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSInteger, NSOperatingSystemVersion, + NSUInteger, + }, }, + window::WindowId as RootWindowId, }; macro_rules! bug { - ($msg:expr) => { - panic!("winit iOS bug, file an issue: {}", $msg) + ($($msg:tt)*) => { + panic!("winit iOS bug, file an issue: {}", format!($($msg)*)) }; } +macro_rules! bug_assert { + ($test:expr, $($msg:tt)*) => { + assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*)) + }; +} + +enum UserCallbackTransitionResult<'a> { + Success { + event_handler: Box, + active_control_flow: ControlFlow, + processing_redraws: bool, + }, + ReentrancyPrevented { + queued_events: &'a mut Vec>, + }, +} + +impl Event { + fn is_redraw(&self) -> bool { + if let Event::WindowEvent { + window_id: _, + event: WindowEvent::RedrawRequested, + } = self + { + true + } else { + false + } + } +} + // this is the state machine for the app lifecycle #[derive(Debug)] +#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] enum AppStateImpl { NotLaunched { queued_windows: Vec, queued_events: Vec>, + queued_gpu_redraws: HashSet, }, Launching { queued_windows: Vec, queued_events: Vec>, queued_event_handler: Box, + queued_gpu_redraws: HashSet, }, ProcessingEvents { event_handler: Box, + queued_gpu_redraws: HashSet, active_control_flow: ControlFlow, }, // special state to deal with reentrancy and prevent mutable aliasing. InUserCallback { queued_events: Vec>, + queued_gpu_redraws: HashSet, + }, + ProcessingRedraws { + event_handler: Box, + active_control_flow: ControlFlow, }, Waiting { waiting_event_handler: Box, @@ -59,9 +102,16 @@ enum AppStateImpl { Terminated, } -impl Drop for AppStateImpl { +struct AppState { + // This should never be `None`, except for briefly during a state transition. + app_state: Option, + control_flow: ControlFlow, + waker: EventLoopWaker, +} + +impl Drop for AppState { fn drop(&mut self) { - match self { + match self.state_mut() { &mut AppStateImpl::NotLaunched { ref mut queued_windows, .. @@ -69,22 +119,18 @@ impl Drop for AppStateImpl { | &mut AppStateImpl::Launching { ref mut queued_windows, .. - } => unsafe { + } => { for &mut window in queued_windows { - let () = msg_send![window, release]; + unsafe { + let () = msg_send![window, release]; + } } - }, + } _ => {} } } } -pub struct AppState { - app_state: AppStateImpl, - control_flow: ControlFlow, - waker: EventLoopWaker, -} - impl AppState { // requires main thread unsafe fn get_mut() -> RefMut<'static, AppState> { @@ -98,7 +144,6 @@ impl AppState { "bug in winit: `AppState::get_mut()` can only be called on the main thread" ); } - let mut guard = APP_STATE.borrow_mut(); if guard.is_none() { #[inline(never)] @@ -106,10 +151,11 @@ impl AppState { unsafe fn init_guard(guard: &mut RefMut<'static, Option>) { let waker = EventLoopWaker::new(CFRunLoopGetMain()); **guard = Some(AppState { - app_state: AppStateImpl::NotLaunched { + app_state: Some(AppStateImpl::NotLaunched { queued_windows: Vec::new(), queued_events: Vec::new(), - }, + queued_gpu_redraws: HashSet::new(), + }), control_flow: ControlFlow::default(), waker, }); @@ -119,233 +165,153 @@ impl AppState { RefMut::map(guard, |state| state.as_mut().unwrap()) } - // requires main thread and window is a UIWindow - // retains window - pub unsafe fn set_key_window(window: id) { - let mut this = AppState::get_mut(); - match &mut this.app_state { - &mut AppStateImpl::NotLaunched { - ref mut queued_windows, - .. - } => { - queued_windows.push(window); - let _: id = msg_send![window, retain]; - return; - } - &mut AppStateImpl::ProcessingEvents { .. } => {} - &mut AppStateImpl::InUserCallback { .. } => {} - &mut AppStateImpl::Terminated => panic!( - "Attempt to create a `Window` \ - after the app has terminated" - ), - app_state => unreachable!("unexpected state: {:#?}", app_state), /* all other cases should be impossible */ + fn state(&self) -> &AppStateImpl { + match &self.app_state { + Some(ref state) => state, + None => bug!("`AppState` previously failed a state transition"), } - drop(this); - msg_send![window, makeKeyAndVisible] } - // requires main thread - pub unsafe fn will_launch(queued_event_handler: Box) { - let mut this = AppState::get_mut(); - let (queued_windows, queued_events) = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { - ref mut queued_windows, - ref mut queued_events, - } => { - let windows = ptr::read(queued_windows); - let events = ptr::read(queued_events); - (windows, events) - } - _ => panic!( - "winit iOS expected the app to be in a `NotLaunched` \ - state, but was not - please file an issue" - ), + fn state_mut(&mut self) -> &mut AppStateImpl { + match &mut self.app_state { + Some(ref mut state) => state, + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn take_state(&mut self) -> AppStateImpl { + match self.app_state.take() { + Some(state) => state, + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn set_state(&mut self, new_state: AppStateImpl) { + bug_assert!( + self.app_state.is_none(), + "attempted to set an `AppState` without calling `take_state` first {:?}", + self.app_state + ); + self.app_state = Some(new_state) + } + + fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl { + match &mut self.app_state { + Some(ref mut state) => mem::replace(state, new_state), + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn has_launched(&self) -> bool { + match self.state() { + &AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } => false, + _ => true, + } + } + + fn will_launch_transition(&mut self, queued_event_handler: Box) { + let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { + AppStateImpl::NotLaunched { + queued_windows, + queued_events, + queued_gpu_redraws, + } => (queued_windows, queued_events, queued_gpu_redraws), + s => bug!("unexpected state {:?}", s), }; - ptr::write( - &mut this.app_state, + self.set_state(AppStateImpl::Launching { + queued_windows, + queued_events, + queued_event_handler, + queued_gpu_redraws, + }); + } + + 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, queued_events, queued_event_handler, - }, - ); + queued_gpu_redraws, + } => ( + queued_windows, + queued_events, + queued_event_handler, + queued_gpu_redraws, + ), + s => bug!("unexpected state {:?}", s), + }; + self.set_state(AppStateImpl::ProcessingEvents { + event_handler, + active_control_flow: ControlFlow::Poll, + queued_gpu_redraws, + }); + (windows, events) } - // requires main thread - pub unsafe fn did_finish_launching() { - let mut this = AppState::get_mut(); - let windows = match &mut this.app_state { - &mut AppStateImpl::Launching { - ref mut queued_windows, - .. - } => mem::replace(queued_windows, Vec::new()), - _ => panic!( - "winit iOS expected the app to be in a `Launching` \ - state, but was not - please file an issue" - ), - }; - // have to drop RefMut because the window setup code below can trigger new events - drop(this); - - for window in windows { - let count: NSUInteger = msg_send![window, retainCount]; - // make sure the window is still referenced - if count > 1 { - // Do a little screen dance here to account for windows being created before - // `UIApplicationMain` is called. This fixes visual issues such as being - // offcenter and sized incorrectly. Additionally, to fix orientation issues, we - // gotta reset the `rootViewController`. - // - // relevant iOS log: - // ``` - // [ApplicationLifecycle] Windows were created before application initialzation - // completed. This may result in incorrect visual appearance. - // ``` - let screen: id = msg_send![window, screen]; - let _: id = msg_send![screen, retain]; - let () = msg_send![window, setScreen:0 as id]; - let () = msg_send![window, setScreen: screen]; - let () = msg_send![screen, release]; - let controller: id = msg_send![window, rootViewController]; - let () = msg_send![window, setRootViewController:ptr::null::<()>()]; - let () = msg_send![window, setRootViewController: controller]; - let () = msg_send![window, makeKeyAndVisible]; - } - let () = msg_send![window, release]; + fn wakeup_transition(&mut self) -> Option> { + // before `AppState::did_finish_launching` is called, pretend there is no running + // event loop. + if !self.has_launched() { + return None; } - let mut this = AppState::get_mut(); - let (windows, events, event_handler) = match &mut this.app_state { - &mut AppStateImpl::Launching { - ref mut queued_windows, - ref mut queued_events, - ref mut queued_event_handler, - } => { - let windows = ptr::read(queued_windows); - let events = ptr::read(queued_events); - let event_handler = ptr::read(queued_event_handler); - (windows, events, event_handler) - } - _ => panic!( - "winit iOS expected the app to be in a `Launching` \ - state, but was not - please file an issue" + let (event_handler, event) = match (self.control_flow, self.take_state()) { + ( + ControlFlow::Poll, + AppStateImpl::PollFinished { + waiting_event_handler, + }, + ) => (waiting_event_handler, Event::NewEvents(StartCause::Poll)), + ( + ControlFlow::Wait, + AppStateImpl::Waiting { + waiting_event_handler, + start, + }, + ) => ( + waiting_event_handler, + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: None, + }), ), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow: ControlFlow::Poll, - }, - ); - drop(this); - - let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events); - AppState::handle_nonuser_events(events); - - // the above window dance hack, could possibly trigger new windows to be created. - // we can just set those windows up normally, as they were created after didFinishLaunching - for window in windows { - let count: NSUInteger = msg_send![window, retainCount]; - // make sure the window is still referenced - if count > 1 { - let () = msg_send![window, makeKeyAndVisible]; - } - let () = msg_send![window, release]; - } - } - - // requires main thread - // AppState::did_finish_launching handles the special transition `Init` - pub unsafe fn handle_wakeup_transition() { - let mut this = AppState::get_mut(); - let event = - match this.control_flow { - ControlFlow::Poll => { - let event_handler = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } - | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::PollFinished { - ref mut waiting_event_handler, - } => ptr::read(waiting_event_handler), - _ => bug!("`EventHandler` unexpectedly started polling"), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow: ControlFlow::Poll, - }, - ); - Event::NewEvents(StartCause::Poll) - } - ControlFlow::Wait => { - let (event_handler, start) = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } - | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::Waiting { - ref mut waiting_event_handler, - ref mut start, - } => (ptr::read(waiting_event_handler), *start), - _ => bug!("`EventHandler` unexpectedly woke up"), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow: ControlFlow::Wait, - }, - ); + ( + ControlFlow::WaitUntil(requested_resume), + AppStateImpl::Waiting { + waiting_event_handler, + start, + }, + ) => { + let event = if Instant::now() >= requested_resume { + Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume, + }) + } else { Event::NewEvents(StartCause::WaitCancelled { start, - requested_resume: None, + requested_resume: Some(requested_resume), }) - } - ControlFlow::WaitUntil(requested_resume) => { - let (event_handler, start) = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } - | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::Waiting { - ref mut waiting_event_handler, - ref mut start, - } => (ptr::read(waiting_event_handler), *start), - _ => bug!("`EventHandler` unexpectedly woke up"), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow: ControlFlow::WaitUntil(requested_resume), - }, - ); - if Instant::now() >= requested_resume { - Event::NewEvents(StartCause::ResumeTimeReached { - start, - requested_resume, - }) - } else { - Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: Some(requested_resume), - }) - } - } - ControlFlow::Exit => bug!("unexpected controlflow `Exit`"), - }; - drop(this); - AppState::handle_nonuser_event(event) + }; + (waiting_event_handler, event) + } + (ControlFlow::Exit, _) => bug!("unexpected `ControlFlow` `Exit`"), + s => bug!("`EventHandler` unexpectedly woke up {:?}", s), + }; + + self.set_state(AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws: Default::default(), + active_control_flow: self.control_flow, + }); + Some(event) } - // requires main thread - pub unsafe fn handle_nonuser_event(event: Event) { - AppState::handle_nonuser_events(std::iter::once(event)) - } - - // requires main thread - pub unsafe fn handle_nonuser_events>>(events: I) { - let mut this = AppState::get_mut(); - let mut control_flow = this.control_flow; - let (mut event_handler, active_control_flow) = match &mut this.app_state { + fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> { + // If we're not able to process an event due to recursion or `Init` not having been sent out + // yet, then queue the events up. + match self.state_mut() { &mut AppStateImpl::Launching { ref mut queued_events, .. @@ -358,211 +324,488 @@ impl AppState { ref mut queued_events, .. } => { - queued_events.extend(events); - return; + // A lifetime cast: early returns are not currently handled well with NLL, but + // polonius handles them well. This transmute is a safe workaround. + return unsafe { + mem::transmute::< + UserCallbackTransitionResult<'_>, + UserCallbackTransitionResult<'_>, + >(UserCallbackTransitionResult::ReentrancyPrevented { + queued_events, + }) + }; } - &mut AppStateImpl::ProcessingEvents { - ref mut event_handler, - ref mut active_control_flow, - } => (ptr::read(event_handler), *active_control_flow), - &mut AppStateImpl::PollFinished { .. } - | &mut AppStateImpl::Waiting { .. } - | &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::InUserCallback { - queued_events: Vec::new(), - }, - ); - drop(this); - for event in events { - event_handler.handle_nonuser_event(event, &mut control_flow) + &mut AppStateImpl::ProcessingEvents { .. } + | &mut AppStateImpl::ProcessingRedraws { .. } => {} + + s @ &mut AppStateImpl::PollFinished { .. } + | s @ &mut AppStateImpl::Waiting { .. } + | s @ &mut AppStateImpl::Terminated => { + bug!("unexpected attempted to process an event {:?}", s) + } } - loop { - let mut this = AppState::get_mut(); - let queued_events = match &mut this.app_state { - &mut AppStateImpl::InUserCallback { - ref mut queued_events, - } => mem::replace(queued_events, Vec::new()), - _ => bug!("unexpected `AppStateImpl`"), - }; - if queued_events.is_empty() { - this.app_state = AppStateImpl::ProcessingEvents { + + let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) = + match self.take_state() { + AppStateImpl::Launching { .. } + | AppStateImpl::NotLaunched { .. } + | AppStateImpl::InUserCallback { .. } => unreachable!(), + AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + } => ( + event_handler, + queued_gpu_redraws, + active_control_flow, + false, + ), + AppStateImpl::ProcessingRedraws { event_handler, active_control_flow, - }; - this.control_flow = control_flow; - break; - } - drop(this); - for event in queued_events { - event_handler.handle_nonuser_event(event, &mut control_flow) - } + } => (event_handler, Default::default(), active_control_flow, true), + AppStateImpl::PollFinished { .. } + | AppStateImpl::Waiting { .. } + | AppStateImpl::Terminated => unreachable!(), + }; + self.set_state(AppStateImpl::InUserCallback { + queued_events: Vec::new(), + queued_gpu_redraws, + }); + UserCallbackTransitionResult::Success { + event_handler, + active_control_flow, + processing_redraws, } } - // requires main thread - pub unsafe fn handle_user_events() { - let mut this = AppState::get_mut(); - let mut control_flow = this.control_flow; - let (mut event_handler, active_control_flow) = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::ProcessingEvents { - ref mut event_handler, - ref mut active_control_flow, - } => (ptr::read(event_handler), *active_control_flow), - &mut AppStateImpl::InUserCallback { .. } - | &mut AppStateImpl::PollFinished { .. } - | &mut AppStateImpl::Waiting { .. } - | &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"), + fn main_events_cleared_transition(&mut self) -> HashSet { + let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { + AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + } => (event_handler, queued_gpu_redraws, active_control_flow), + s => bug!("unexpected state {:?}", s), }; - ptr::write( - &mut this.app_state, - AppStateImpl::InUserCallback { - queued_events: Vec::new(), - }, - ); - drop(this); - - event_handler.handle_user_events(&mut control_flow); - loop { - let mut this = AppState::get_mut(); - let queued_events = match &mut this.app_state { - &mut AppStateImpl::InUserCallback { - ref mut queued_events, - } => mem::replace(queued_events, Vec::new()), - _ => bug!("unexpected `AppStateImpl`"), - }; - if queued_events.is_empty() { - this.app_state = AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow, - }; - this.control_flow = control_flow; - break; - } - drop(this); - for event in queued_events { - event_handler.handle_nonuser_event(event, &mut control_flow) - } - event_handler.handle_user_events(&mut control_flow); - } + self.set_state(AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + }); + queued_gpu_redraws } - // requires main thread - pub unsafe fn handle_events_cleared() { - let mut this = AppState::get_mut(); - match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::ProcessingEvents { .. } => {} - _ => unreachable!(), - }; - drop(this); - - AppState::handle_user_events(); - AppState::handle_nonuser_event(Event::EventsCleared); - - let mut this = AppState::get_mut(); - let (event_handler, old) = match &mut this.app_state { - &mut AppStateImpl::ProcessingEvents { - ref mut event_handler, - ref mut active_control_flow, - } => ( - ManuallyDrop::new(ptr::read(event_handler)), - *active_control_flow, - ), - _ => unreachable!(), + fn events_cleared_transition(&mut self) { + if !self.has_launched() { + return; + } + let (waiting_event_handler, old) = match self.take_state() { + AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + } => (event_handler, active_control_flow), + s => bug!("unexpected state {:?}", s), }; - let new = this.control_flow; + let new = self.control_flow; match (old, new) { - (ControlFlow::Poll, ControlFlow::Poll) => ptr::write( - &mut this.app_state, - AppStateImpl::PollFinished { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - }, - ), + (ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished { + waiting_event_handler, + }), (ControlFlow::Wait, ControlFlow::Wait) => { let start = Instant::now(); - ptr::write( - &mut this.app_state, - AppStateImpl::Waiting { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - start, - }, - ) + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); } (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) if old_instant == new_instant => { let start = Instant::now(); - ptr::write( - &mut this.app_state, - AppStateImpl::Waiting { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - start, - }, - ) + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); } (_, ControlFlow::Wait) => { let start = Instant::now(); - ptr::write( - &mut this.app_state, - AppStateImpl::Waiting { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - start, - }, - ); - this.waker.stop() + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); + self.waker.stop() } (_, ControlFlow::WaitUntil(new_instant)) => { let start = Instant::now(); - ptr::write( - &mut this.app_state, - AppStateImpl::Waiting { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - start, - }, - ); - this.waker.start_at(new_instant) + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); + self.waker.start_at(new_instant) } (_, ControlFlow::Poll) => { - ptr::write( - &mut this.app_state, - AppStateImpl::PollFinished { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - }, - ); - this.waker.start() + self.set_state(AppStateImpl::PollFinished { + waiting_event_handler, + }); + self.waker.start() } (_, ControlFlow::Exit) => { // https://developer.apple.com/library/archive/qa/qa1561/_index.html // it is not possible to quit an iOS app gracefully and programatically warn!("`ControlFlow::Exit` ignored on iOS"); - this.control_flow = old + self.control_flow = old } } } - pub fn terminated() { - let mut this = unsafe { AppState::get_mut() }; - let mut old = mem::replace(&mut this.app_state, AppStateImpl::Terminated); - let mut control_flow = this.control_flow; - if let AppStateImpl::ProcessingEvents { - ref mut event_handler, - .. - } = old - { - drop(this); - event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow) - } else { - bug!("`LoopDestroyed` happened while not processing events") + fn terminated_transition(&mut self) -> Box { + match self.replace_state(AppStateImpl::Terminated) { + AppStateImpl::ProcessingRedraws { event_handler, .. } => event_handler, + s => bug!( + "`LoopDestroyed` happened while not processing events {:?}", + s + ), } } } +// requires main thread and window is a UIWindow +// retains window +pub unsafe fn set_key_window(window: id) { + bug_assert!( + { + let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)]; + is_window == YES + }, + "set_key_window called with an incorrect type" + ); + let mut this = AppState::get_mut(); + match this.state_mut() { + &mut AppStateImpl::NotLaunched { + ref mut queued_windows, + .. + } => return queued_windows.push(msg_send![window, retain]), + &mut AppStateImpl::ProcessingEvents { .. } + | &mut AppStateImpl::InUserCallback { .. } + | &mut AppStateImpl::ProcessingRedraws { .. } => {} + s @ &mut AppStateImpl::Launching { .. } + | s @ &mut AppStateImpl::Waiting { .. } + | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), + &mut AppStateImpl::Terminated => { + panic!("Attempt to create a `Window` after the app has terminated") + } + } + drop(this); + msg_send![window, makeKeyAndVisible] +} + +// requires main thread and window is a UIWindow +// retains window +pub unsafe fn queue_gl_or_metal_redraw(window: id) { + bug_assert!( + { + let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)]; + is_window == YES + }, + "set_key_window called with an incorrect type" + ); + let mut this = AppState::get_mut(); + match this.state_mut() { + &mut AppStateImpl::NotLaunched { + ref mut queued_gpu_redraws, + .. + } + | &mut AppStateImpl::Launching { + ref mut queued_gpu_redraws, + .. + } + | &mut AppStateImpl::ProcessingEvents { + ref mut queued_gpu_redraws, + .. + } + | &mut AppStateImpl::InUserCallback { + ref mut queued_gpu_redraws, + .. + } => drop(queued_gpu_redraws.insert(window)), + s @ &mut AppStateImpl::ProcessingRedraws { .. } + | s @ &mut AppStateImpl::Waiting { .. } + | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), + &mut AppStateImpl::Terminated => { + panic!("Attempt to create a `Window` after the app has terminated") + } + } + drop(this); +} + +// requires main thread +pub unsafe fn will_launch(queued_event_handler: Box) { + AppState::get_mut().will_launch_transition(queued_event_handler) +} + +// requires main thread +pub unsafe fn did_finish_launching() { + let mut this = AppState::get_mut(); + let windows = match this.state_mut() { + AppStateImpl::Launching { queued_windows, .. } => mem::replace(queued_windows, Vec::new()), + s => bug!("unexpected state {:?}", s), + }; + + // start waking up the event loop now! + bug_assert!( + this.control_flow == ControlFlow::Poll, + "unexpectedly not setup to `Poll` on launch!" + ); + this.waker.start(); + + // have to drop RefMut because the window setup code below can trigger new events + drop(this); + + for window in windows { + let count: NSUInteger = msg_send![window, retainCount]; + // make sure the window is still referenced + if count > 1 { + // Do a little screen dance here to account for windows being created before + // `UIApplicationMain` is called. This fixes visual issues such as being + // offcenter and sized incorrectly. Additionally, to fix orientation issues, we + // gotta reset the `rootViewController`. + // + // relevant iOS log: + // ``` + // [ApplicationLifecycle] Windows were created before application initialzation + // completed. This may result in incorrect visual appearance. + // ``` + let screen: id = msg_send![window, screen]; + let _: id = msg_send![screen, retain]; + let () = msg_send![window, setScreen:0 as id]; + let () = msg_send![window, setScreen: screen]; + let () = msg_send![screen, release]; + let controller: id = msg_send![window, rootViewController]; + let () = msg_send![window, setRootViewController:ptr::null::<()>()]; + let () = msg_send![window, setRootViewController: controller]; + let () = msg_send![window, makeKeyAndVisible]; + } + let () = msg_send![window, release]; + } + + let (windows, events) = AppState::get_mut().did_finish_launching_transition(); + + let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events); + handle_nonuser_events(events); + + // the above window dance hack, could possibly trigger new windows to be created. + // we can just set those windows up normally, as they were created after didFinishLaunching + for window in windows { + let count: NSUInteger = msg_send![window, retainCount]; + // make sure the window is still referenced + if count > 1 { + let () = msg_send![window, makeKeyAndVisible]; + } + let () = msg_send![window, release]; + } +} + +// requires main thread +// AppState::did_finish_launching handles the special transition `Init` +pub unsafe fn handle_wakeup_transition() { + let mut this = AppState::get_mut(); + let wakeup_event = match this.wakeup_transition() { + None => return, + Some(wakeup_event) => wakeup_event, + }; + drop(this); + + handle_nonuser_event(wakeup_event) +} + +// requires main thread +pub unsafe fn handle_nonuser_event(event: Event) { + handle_nonuser_events(std::iter::once(event)) +} + +// requires main thread +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() { + UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { + queued_events.extend(events); + return; + } + UserCallbackTransitionResult::Success { + event_handler, + active_control_flow, + processing_redraws, + } => (event_handler, active_control_flow, processing_redraws), + }; + 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 + ); + } + event_handler.handle_nonuser_event(event, &mut control_flow) + } + + loop { + let mut this = AppState::get_mut(); + let queued_events = match this.state_mut() { + &mut AppStateImpl::InUserCallback { + ref mut queued_events, + queued_gpu_redraws: _, + } => mem::replace(queued_events, Vec::new()), + s => bug!("unexpected state {:?}", s), + }; + if queued_events.is_empty() { + let queued_gpu_redraws = match this.take_state() { + AppStateImpl::InUserCallback { + queued_events: _, + queued_gpu_redraws, + } => queued_gpu_redraws, + _ => unreachable!(), + }; + this.app_state = Some(if processing_redraws { + bug_assert!( + queued_gpu_redraws.is_empty(), + "redraw queued while processing redraws" + ); + AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + } + } else { + AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + } + }); + this.control_flow = control_flow; + break; + } + 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 + ); + } + event_handler.handle_nonuser_event(event, &mut control_flow) + } + } +} + +// requires main thread +unsafe fn handle_user_events() { + let mut this = AppState::get_mut(); + let mut control_flow = this.control_flow; + let (mut event_handler, active_control_flow, processing_redraws) = + match this.try_user_callback_transition() { + UserCallbackTransitionResult::ReentrancyPrevented { .. } => { + bug!("unexpected attempted to process an event") + } + UserCallbackTransitionResult::Success { + event_handler, + active_control_flow, + processing_redraws, + } => (event_handler, active_control_flow, processing_redraws), + }; + if processing_redraws { + bug!("user events attempted to be sent out while `ProcessingRedraws`"); + } + drop(this); + + event_handler.handle_user_events(&mut control_flow); + + loop { + let mut this = AppState::get_mut(); + let queued_events = match this.state_mut() { + &mut AppStateImpl::InUserCallback { + ref mut queued_events, + queued_gpu_redraws: _, + } => mem::replace(queued_events, Vec::new()), + s => bug!("unexpected state {:?}", s), + }; + if queued_events.is_empty() { + let queued_gpu_redraws = match this.take_state() { + AppStateImpl::InUserCallback { + queued_events: _, + queued_gpu_redraws, + } => queued_gpu_redraws, + _ => unreachable!(), + }; + this.app_state = Some(AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + }); + this.control_flow = control_flow; + break; + } + drop(this); + + for event in queued_events { + event_handler.handle_nonuser_event(event, &mut control_flow) + } + event_handler.handle_user_events(&mut control_flow); + } +} + +// requires main thread +pub unsafe fn handle_main_events_cleared() { + let mut this = AppState::get_mut(); + if !this.has_launched() { + return; + } + match this.state_mut() { + &mut AppStateImpl::ProcessingEvents { .. } => {} + _ => bug!("`ProcessingRedraws` happened unexpectedly"), + }; + drop(this); + + // User events are always sent out at the end of the "MainEventLoop" + handle_user_events(); + handle_nonuser_event(Event::EventsCleared); + + let mut this = AppState::get_mut(); + let redraw_events = this + .main_events_cleared_transition() + .into_iter() + .map(|window| Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::RedrawRequested, + }); + drop(this); + + handle_nonuser_events(redraw_events); +} + +// requires main thread +pub unsafe fn handle_events_cleared() { + AppState::get_mut().events_cleared_transition(); +} + +// requires main thread +pub unsafe fn terminated() { + let mut this = AppState::get_mut(); + let mut event_handler = this.terminated_transition(); + let mut control_flow = this.control_flow; + drop(this); + + event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow) +} + struct EventLoopWaker { timer: CFRunLoopTimerRef, } diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index b5b56739..b26b1eb6 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -16,7 +16,7 @@ use crate::{ }; use crate::platform_impl::platform::{ - app_state::AppState, + app_state, ffi::{ id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease, @@ -80,7 +80,7 @@ impl EventLoop { `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ Note: `EventLoop::run` calls `UIApplicationMain` on iOS" ); - AppState::will_launch(Box::new(EventLoopHandler { + app_state::will_launch(Box::new(EventLoopHandler { f: event_handler, event_loop: self.window_target, })); @@ -155,7 +155,7 @@ impl EventLoopProxy { let rl = CFRunLoopGetMain(); // we want all the members of context to be zero/null, except one let mut context: CFRunLoopSourceContext = mem::zeroed(); - context.perform = event_loop_proxy_handler; + context.perform = Some(event_loop_proxy_handler); let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context); CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); @@ -188,15 +188,40 @@ fn setup_control_flow_observers() { unsafe { #[allow(non_upper_case_globals)] match activity { - kCFRunLoopAfterWaiting => AppState::handle_wakeup_transition(), + kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(), kCFRunLoopEntry => unimplemented!(), // not expected to ever happen _ => unreachable!(), } } } + // 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 + // 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`. + // + // 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. + // + // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. + extern "C" fn control_flow_main_end_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, + ) { + unsafe { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(), + kCFRunLoopExit => unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } + } + } + // end is queued with the lowest priority to ensure it is processed after other observers - // without that, LoopDestroyed will get sent after EventsCleared extern "C" fn control_flow_end_handler( _: CFRunLoopObserverRef, activity: CFRunLoopActivity, @@ -205,7 +230,7 @@ fn setup_control_flow_observers() { unsafe { #[allow(non_upper_case_globals)] match activity { - kCFRunLoopBeforeWaiting => AppState::handle_events_cleared(), + kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(), kCFRunLoopExit => unimplemented!(), // not expected to ever happen _ => unreachable!(), } @@ -213,6 +238,7 @@ fn setup_control_flow_observers() { } let main_loop = CFRunLoopGetMain(); + let begin_observer = CFRunLoopObserverCreate( ptr::null_mut(), kCFRunLoopEntry | kCFRunLoopAfterWaiting, @@ -222,6 +248,17 @@ fn setup_control_flow_observers() { ptr::null_mut(), ); CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode); + + let main_end_observer = CFRunLoopObserverCreate( + ptr::null_mut(), + kCFRunLoopExit | kCFRunLoopBeforeWaiting, + 1, // repeat = true + 0, // see comment on `control_flow_main_end_handler` + control_flow_main_end_handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode); + let end_observer = CFRunLoopObserverCreate( ptr::null_mut(), kCFRunLoopExit | kCFRunLoopBeforeWaiting, diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index fcaf395e..4ecd47f7 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -331,14 +331,14 @@ pub enum CFRunLoopTimerContext {} pub struct CFRunLoopSourceContext { pub version: CFIndex, pub info: *mut c_void, - pub retain: extern "C" fn(*const c_void) -> *const c_void, - pub release: extern "C" fn(*const c_void), - pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, - pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, - pub hash: extern "C" fn(*const c_void) -> CFHashCode, - pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), - pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), - pub perform: extern "C" fn(*mut c_void), + pub retain: Option *const c_void>, + pub release: Option, + pub copyDescription: Option CFStringRef>, + pub equal: Option Boolean>, + pub hash: Option CFHashCode>, + pub schedule: Option, + pub cancel: Option, + pub perform: Option, } pub trait NSString: Sized { diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index e871ab46..8c6ba1e7 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -9,7 +9,7 @@ use crate::{ event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, platform::ios::MonitorHandleExtIOS, platform_impl::platform::{ - app_state::{self, AppState, OSCapabilities}, + app_state::{self, OSCapabilities}, event_loop, ffi::{ id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, @@ -101,7 +101,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) { unsafe { let window: id = msg_send![object, window]; - AppState::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::RedrawRequested, }); @@ -112,6 +112,9 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { extern "C" fn layout_subviews(object: &Object, _: Sel) { unsafe { + let superclass: &'static Class = msg_send![object, superclass]; + let () = msg_send![super(object, superclass), layoutSubviews]; + let window: id = msg_send![object, window]; let bounds: CGRect = msg_send![window, bounds]; let screen: id = msg_send![window, screen]; @@ -122,12 +125,127 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { width: screen_frame.size.width as _, height: screen_frame.size.height as _, }; - AppState::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::Resized(size), }); + } + } + + extern "C" fn set_content_scale_factor( + object: &mut Object, + _: Sel, + untrusted_hidpi_factor: CGFloat, + ) { + unsafe { let superclass: &'static Class = msg_send![object, superclass]; - let () = msg_send![super(object, superclass), layoutSubviews]; + let () = msg_send![ + super(object, superclass), + setContentScaleFactor: untrusted_hidpi_factor + ]; + + // On launch, iOS sets the contentScaleFactor to 0.0. This is a sentinel value that + // iOS appears to use to "reset" the contentScaleFactor to the device specific + // default value. + // + // The workaround is to not trust the value received by this function, and always + // go through the getter. + let hidpi_factor: CGFloat = msg_send![object, contentScaleFactor]; + assert!( + !hidpi_factor.is_nan() + && hidpi_factor.is_finite() + && hidpi_factor.is_sign_positive() + && hidpi_factor > 0.0, + "invalid hidpi_factor set on UIWindow", + ); + + let window: id = msg_send![object, window]; + + let bounds: CGRect = msg_send![object, 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]; + let size = crate::dpi::LogicalSize { + width: screen_frame.size.width as _, + 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), + })), + ); + } + } + + extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) { + unsafe { + let window: id = msg_send![object, window]; + let uiscreen: id = msg_send![window, screen]; + let touches_enum: id = msg_send![touches, objectEnumerator]; + let mut touch_events = Vec::new(); + let os_supports_force = app_state::os_capabilities().force_touch; + loop { + let touch: id = msg_send![touches_enum, nextObject]; + if touch == nil { + break; + } + let 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]; + let touch_capability: UIForceTouchCapability = + msg_send![trait_collection, forceTouchCapability]; + // Both the OS _and_ the device need to be checked for force touch support. + if touch_capability == UIForceTouchCapability::Available { + let force: CGFloat = msg_send![touch, force]; + let max_possible_force: CGFloat = + msg_send![touch, maximumPossibleForce]; + let altitude_angle: Option = if touch_type == UITouchType::Pencil { + let angle: CGFloat = msg_send![touch, altitudeAngle]; + Some(angle as _) + } else { + None + }; + Some(Force::Calibrated { + force: force as _, + max_possible_force: max_possible_force as _, + altitude_angle, + }) + } else { + None + } + } else { + None + }; + let touch_id = touch as u64; + let phase: UITouchPhase = msg_send![touch, phase]; + let phase = match phase { + UITouchPhase::Began => TouchPhase::Started, + UITouchPhase::Moved => TouchPhase::Moved, + // 2 is UITouchPhase::Stationary and is not expected here + UITouchPhase::Ended => TouchPhase::Ended, + UITouchPhase::Cancelled => TouchPhase::Cancelled, + _ => panic!("unexpected touch phase: {:?}", phase as i32), + }; + + touch_events.push(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(), + force, + phase, + }), + }); + } + app_state::handle_nonuser_events(touch_events); } } @@ -142,6 +260,28 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { sel!(layoutSubviews), layout_subviews as extern "C" fn(&Object, Sel), ); + decl.add_method( + sel!(setContentScaleFactor:), + set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat), + ); + + decl.add_method( + sel!(touchesBegan:withEvent:), + handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), + ); + decl.add_method( + sel!(touchesMoved:withEvent:), + handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), + ); + decl.add_method( + sel!(touchesEnded:withEvent:), + handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), + ); + decl.add_method( + sel!(touchesCancelled:withEvent:), + handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), + ); + decl.register() }) } @@ -223,7 +363,7 @@ unsafe fn get_window_class() -> &'static Class { extern "C" fn become_key_window(object: &Object, _: Sel) { unsafe { - AppState::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(Event::WindowEvent { window_id: RootWindowId(object.into()), event: WindowEvent::Focused(true), }); @@ -233,7 +373,7 @@ unsafe fn get_window_class() -> &'static Class { extern "C" fn resign_key_window(object: &Object, _: Sel) { unsafe { - AppState::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(Event::WindowEvent { window_id: RootWindowId(object.into()), event: WindowEvent::Focused(false), }); @@ -241,102 +381,6 @@ unsafe fn get_window_class() -> &'static Class { } } - extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) { - unsafe { - let uiscreen = msg_send![object, screen]; - let touches_enum: id = msg_send![touches, objectEnumerator]; - let mut touch_events = Vec::new(); - let os_supports_force = app_state::os_capabilities().force_touch; - loop { - let touch: id = msg_send![touches_enum, nextObject]; - if touch == nil { - break; - } - let 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]; - let touch_capability: UIForceTouchCapability = - msg_send![trait_collection, forceTouchCapability]; - // Both the OS _and_ the device need to be checked for force touch support. - if touch_capability == UIForceTouchCapability::Available { - let force: CGFloat = msg_send![touch, force]; - let max_possible_force: CGFloat = - msg_send![touch, maximumPossibleForce]; - let altitude_angle: Option = if touch_type == UITouchType::Pencil { - let angle: CGFloat = msg_send![touch, altitudeAngle]; - Some(angle as _) - } else { - None - }; - Some(Force::Calibrated { - force: force as _, - max_possible_force: max_possible_force as _, - altitude_angle, - }) - } else { - None - } - } else { - None - }; - let touch_id = touch as u64; - let phase: UITouchPhase = msg_send![touch, phase]; - let phase = match phase { - UITouchPhase::Began => TouchPhase::Started, - UITouchPhase::Moved => TouchPhase::Moved, - // 2 is UITouchPhase::Stationary and is not expected here - UITouchPhase::Ended => TouchPhase::Ended, - UITouchPhase::Cancelled => TouchPhase::Cancelled, - _ => panic!("unexpected touch phase: {:?}", phase as i32), - }; - - touch_events.push(Event::WindowEvent { - window_id: RootWindowId(object.into()), - event: WindowEvent::Touch(Touch { - device_id: RootDeviceId(DeviceId { uiscreen }), - id: touch_id, - location: (location.x as f64, location.y as f64).into(), - force, - phase, - }), - }); - } - AppState::handle_nonuser_events(touch_events); - } - } - - extern "C" fn set_content_scale_factor(object: &mut Object, _: Sel, hidpi_factor: CGFloat) { - unsafe { - let () = msg_send![ - super(object, class!(UIWindow)), - setContentScaleFactor: hidpi_factor - ]; - let view_controller: id = msg_send![object, rootViewController]; - let view: id = msg_send![view_controller, view]; - let () = msg_send![view, setContentScaleFactor: hidpi_factor]; - let bounds: CGRect = msg_send![object, bounds]; - let screen: id = msg_send![object, screen]; - let screen_space: id = msg_send![screen, coordinateSpace]; - let screen_frame: CGRect = - msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; - let size = crate::dpi::LogicalSize { - width: screen_frame.size.width as _, - height: screen_frame.size.height as _, - }; - AppState::handle_nonuser_events( - std::iter::once(Event::WindowEvent { - window_id: RootWindowId(object.into()), - event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _), - }) - .chain(std::iter::once(Event::WindowEvent { - window_id: RootWindowId(object.into()), - event: WindowEvent::Resized(size), - })), - ); - } - } - let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class) .expect("Failed to declare class `WinitUIWindow`"); decl.add_method( @@ -348,28 +392,6 @@ unsafe fn get_window_class() -> &'static Class { resign_key_window as extern "C" fn(&Object, Sel), ); - decl.add_method( - sel!(touchesBegan:withEvent:), - handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), - ); - decl.add_method( - sel!(touchesMoved:withEvent:), - handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), - ); - decl.add_method( - sel!(touchesEnded:withEvent:), - handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), - ); - decl.add_method( - sel!(touchesCancelled:withEvent:), - handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), - ); - - decl.add_method( - sel!(setContentScaleFactor:), - set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat), - ); - CLASS = Some(decl.register()); } CLASS.unwrap() @@ -388,6 +410,9 @@ 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]; + } view } @@ -451,7 +476,7 @@ pub unsafe fn create_view_controller( // requires main thread pub unsafe fn create_window( window_attributes: &WindowAttributes, - platform_attributes: &PlatformSpecificWindowBuilderAttributes, + _platform_attributes: &PlatformSpecificWindowBuilderAttributes, frame: CGRect, view_controller: id, ) -> id { @@ -465,9 +490,6 @@ pub unsafe fn create_window( "Failed to initialize `UIWindow` instance" ); let () = msg_send![window, setRootViewController: view_controller]; - if let Some(hidpi_factor) = platform_attributes.hidpi_factor { - let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat]; - } match window_attributes.fullscreen { Some(Fullscreen::Exclusive(ref video_mode)) => { let uiscreen = video_mode.monitor().ui_screen() as id; @@ -486,17 +508,17 @@ pub unsafe fn create_window( pub fn create_delegate_class() { extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL { unsafe { - AppState::did_finish_launching(); + app_state::did_finish_launching(); } YES } extern "C" fn did_become_active(_: &Object, _: Sel, _: id) { - unsafe { AppState::handle_nonuser_event(Event::Resumed) } + unsafe { app_state::handle_nonuser_event(Event::Resumed) } } extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) { - unsafe { AppState::handle_nonuser_event(Event::Suspended) } + unsafe { app_state::handle_nonuser_event(Event::Suspended) } } extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {} @@ -521,8 +543,8 @@ pub fn create_delegate_class() { }); } } - AppState::handle_nonuser_events(events); - AppState::terminated(); + app_state::handle_nonuser_events(events); + app_state::terminated(); } } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 0655add1..0b609c60 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -4,7 +4,7 @@ use std::{ ops::{Deref, DerefMut}, }; -use objc::runtime::{Class, Object, NO, YES}; +use objc::runtime::{Class, Object, BOOL, NO, YES}; use crate::{ dpi::{self, LogicalPosition, LogicalSize}, @@ -13,8 +13,7 @@ use crate::{ monitor::MonitorHandle as RootMonitorHandle, platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations}, platform_impl::platform::{ - app_state::{self, AppState}, - event_loop, + app_state, event_loop, ffi::{ id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, UIRectEdge, UIScreenOverscanCompensation, @@ -28,6 +27,7 @@ pub struct Inner { pub window: id, pub view_controller: id, pub view: id, + gl_or_metal_backed: bool, } impl Drop for Inner { @@ -58,7 +58,19 @@ impl Inner { pub fn request_redraw(&self) { unsafe { - let () = msg_send![self.view, setNeedsDisplay]; + if self.gl_or_metal_backed { + // `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer. + // Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using + // raw or gl/metal for drawing this work is completely avoided. + // + // The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via + // testing. + // + // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc + app_state::queue_gl_or_metal_redraw(self.window); + } else { + let () = msg_send![self.view, setNeedsDisplay]; + } } } @@ -337,6 +349,16 @@ impl Window { }; let view = view::create_view(&window_attributes, &platform_attributes, frame.clone()); + + let gl_or_metal_backed = { + let view_class: id = msg_send![view, class]; + let layer_class: id = msg_send![view_class, layerClass]; + let is_metal: BOOL = + msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)]; + let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)]; + is_metal == YES || is_gl == YES + }; + let view_controller = view::create_view_controller(&window_attributes, &platform_attributes, view); let window = view::create_window( @@ -351,9 +373,10 @@ impl Window { window, view_controller, view, + gl_or_metal_backed, }, }; - AppState::set_key_window(window); + app_state::set_key_window(window); Ok(result) } } From 206c3c246cf97c0e33c9d0607edfcca51ede61ef Mon Sep 17 00:00:00 2001 From: Murarth Date: Sun, 8 Sep 2019 11:43:28 -0700 Subject: [PATCH 44/61] Implement WindowEvent ModifiersChanged for X11 and Wayland (#1132) * Implement WindowEvent ModifiersChanged for X11 and Wayland * Fix modifier key state desync on X11 * Run cargo fmt --- src/event.rs | 3 + src/platform_impl/linux/wayland/keyboard.rs | 12 +- .../linux/x11/event_processor.rs | 105 +++++++++--- src/platform_impl/linux/x11/mod.rs | 7 + src/platform_impl/linux/x11/util/mod.rs | 1 + src/platform_impl/linux/x11/util/modifiers.rs | 149 ++++++++++++++++++ 6 files changed, 258 insertions(+), 19 deletions(-) create mode 100644 src/platform_impl/linux/x11/util/modifiers.rs diff --git a/src/event.rs b/src/event.rs index e226bd70..c4ef5f85 100644 --- a/src/event.rs +++ b/src/event.rs @@ -133,6 +133,9 @@ pub enum WindowEvent { input: KeyboardInput, }, + /// Keyboard modifiers have changed + ModifiersChanged { modifiers: ModifiersState }, + /// The cursor has moved on the window. CursorMoved { device_id: DeviceId, diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index 727062ad..4a236691 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -83,7 +83,17 @@ pub fn init_keyboard( KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ } KbEvent::Modifiers { modifiers: event_modifiers, - } => *modifiers_tracker.lock().unwrap() = event_modifiers.into(), + } => { + let modifiers = event_modifiers.into(); + + *modifiers_tracker.lock().unwrap() = modifiers; + + if let Some(wid) = *target.lock().unwrap() { + my_sink + .send((WindowEvent::ModifiersChanged { modifiers }, wid)) + .unwrap(); + } + } } }, move |repeat_event: KeyRepeatEvent, _| { diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 4dea6d27..d65a0133 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -8,6 +8,8 @@ use super::{ XExtension, }; +use util::modifiers::{ModifierKeyState, ModifierKeymap}; + use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent}, @@ -21,6 +23,9 @@ pub(super) struct EventProcessor { pub(super) devices: RefCell>, pub(super) xi2ext: XExtension, pub(super) target: Rc>, + pub(super) mod_keymap: ModifierKeymap, + pub(super) device_mod_state: ModifierKeyState, + pub(super) window_mod_state: ModifierKeyState, } impl EventProcessor { @@ -112,12 +117,22 @@ impl EventProcessor { let event_type = xev.get_type(); match event_type { ffi::MappingNotify => { - unsafe { - (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); + let mapping: &ffi::XMappingEvent = xev.as_ref(); + + if mapping.request == ffi::MappingModifier + || mapping.request == ffi::MappingKeyboard + { + unsafe { + (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); + } + wt.xconn + .check_errors() + .expect("Failed to call XRefreshKeyboardMapping"); + + self.mod_keymap.reset_from_x_connection(&wt.xconn); + self.device_mod_state.update(&self.mod_keymap); + self.window_mod_state.update(&self.mod_keymap); } - wt.xconn - .check_errors() - .expect("Failed to call XRefreshKeyboardMapping"); } ffi::ClientMessage => { @@ -514,13 +529,6 @@ impl EventProcessor { // 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 modifiers = ModifiersState { - alt: xkev.state & ffi::Mod1Mask != 0, - shift: xkev.state & ffi::ShiftMask != 0, - ctrl: xkev.state & ffi::ControlMask != 0, - logo: xkev.state & ffi::Mod4Mask != 0, - }; - let keysym = unsafe { let mut keysym = 0; (wt.xconn.xlib.XLookupString)( @@ -535,6 +543,8 @@ impl EventProcessor { }; let virtual_keycode = events::keysym_to_element(keysym as c_uint); + let modifiers = self.window_mod_state.modifiers(); + callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { @@ -547,6 +557,27 @@ impl EventProcessor { }, }, }); + + if let Some(modifier) = + self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode) + { + self.window_mod_state.key_event( + state, + xkev.keycode as ffi::KeyCode, + modifier, + ); + + let new_modifiers = self.window_mod_state.modifiers(); + + if modifiers != new_modifiers { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged { + modifiers: new_modifiers, + }, + }); + } + } } if state == Pressed { @@ -859,6 +890,21 @@ impl EventProcessor { event: Focused(true), }); + // When focus is gained, send any existing modifiers + // to the window in a ModifiersChanged event. This is + // done to compensate for modifier keys that may be + // changed while a window is out of focus. + if !self.device_mod_state.is_empty() { + self.window_mod_state = self.device_mod_state.clone(); + + let modifiers = self.window_mod_state.modifiers(); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged { modifiers }, + }); + } + // 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 @@ -890,6 +936,22 @@ impl EventProcessor { .borrow_mut() .unfocus(xev.event) .expect("Failed to unfocus input context"); + + // When focus is lost, send a ModifiersChanged event + // containing no modifiers set. This is done to compensate + // for modifier keys that may be changed while a window + // is out of focus. + if !self.window_mod_state.is_empty() { + self.window_mod_state.clear(); + + callback(Event::WindowEvent { + window_id: mkwid(xev.event), + event: WindowEvent::ModifiersChanged { + modifiers: ModifiersState::default(), + }, + }); + } + callback(Event::WindowEvent { window_id: mkwid(xev.event), event: Focused(false), @@ -1022,18 +1084,25 @@ impl EventProcessor { let virtual_keycode = events::keysym_to_element(keysym as c_uint); + if let Some(modifier) = + self.mod_keymap.get_modifier(keycode as ffi::KeyCode) + { + self.device_mod_state.key_event( + state, + keycode as ffi::KeyCode, + modifier, + ); + } + + let modifiers = self.device_mod_state.modifiers(); + callback(Event::DeviceEvent { device_id: mkdid(device_id), event: DeviceEvent::Key(KeyboardInput { scancode, virtual_keycode, state, - // So, in an ideal world we can use libxkbcommon to get modifiers. - // However, libxkbcommon-x11 isn't as commonly installed as one - // would hope. We can still use the Xkb extension to get - // comprehensive keyboard state updates, but interpreting that - // info manually is going to be involved. - modifiers: ModifiersState::default(), + modifiers, }), }); } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 420dfb2d..5b283e68 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -34,6 +34,7 @@ use self::{ dnd::{Dnd, DndState}, event_processor::EventProcessor, ime::{Ime, ImeCreationError, ImeReceiver, ImeSender}, + util::modifiers::ModifierKeymap, }; use crate::{ error::OsError as RootOsError, @@ -143,6 +144,9 @@ impl EventLoop { xconn.update_cached_wm_info(root); + let mut mod_keymap = ModifierKeymap::new(); + mod_keymap.reset_from_x_connection(&xconn); + let target = Rc::new(RootELW { p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { ime, @@ -186,6 +190,9 @@ impl EventLoop { randr_event_offset, ime_receiver, xi2ext, + mod_keymap, + device_mod_state: Default::default(), + window_mod_state: Default::default(), }; // Register for device hotplug events diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index dba0f9d2..1410da28 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -10,6 +10,7 @@ mod hint; mod icon; mod input; mod memory; +pub mod modifiers; mod randr; mod window_property; mod wm; diff --git a/src/platform_impl/linux/x11/util/modifiers.rs b/src/platform_impl/linux/x11/util/modifiers.rs new file mode 100644 index 00000000..9b8da4b7 --- /dev/null +++ b/src/platform_impl/linux/x11/util/modifiers.rs @@ -0,0 +1,149 @@ +use std::{collections::HashMap, slice}; + +use super::*; + +use crate::event::{ElementState, ModifiersState}; + +// Offsets within XModifierKeymap to each set of keycodes. +// We are only interested in Shift, Control, Alt, and Logo. +// +// There are 8 sets total. The order of keycode sets is: +// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5 +// +// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html +const SHIFT_OFFSET: usize = 0; +const CONTROL_OFFSET: usize = 2; +const ALT_OFFSET: usize = 3; +const LOGO_OFFSET: usize = 6; +const NUM_MODS: usize = 8; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Modifier { + Alt, + Ctrl, + Shift, + Logo, +} + +#[derive(Debug, Default)] +pub struct ModifierKeymap { + // Maps keycodes to modifiers + keys: HashMap, +} + +#[derive(Clone, Debug, Default)] +pub struct ModifierKeyState { + // Contains currently pressed modifier keys and their corresponding modifiers + keys: HashMap, +} + +impl ModifierKeymap { + pub fn new() -> ModifierKeymap { + ModifierKeymap::default() + } + + pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option { + self.keys.get(&keycode).cloned() + } + + pub fn reset_from_x_connection(&mut self, xconn: &XConnection) { + unsafe { + let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display); + + if keymap.is_null() { + panic!("failed to allocate XModifierKeymap"); + } + + self.reset_from_x_keymap(&*keymap); + + (xconn.xlib.XFreeModifiermap)(keymap); + } + } + + pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) { + let keys_per_mod = keymap.max_keypermod as usize; + + let keys = unsafe { + slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS) + }; + + self.keys.clear(); + + self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift); + self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl); + self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt); + self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo); + } + + fn read_x_keys( + &mut self, + keys: &[ffi::KeyCode], + offset: usize, + keys_per_mod: usize, + modifier: Modifier, + ) { + let start = offset * keys_per_mod; + let end = start + keys_per_mod; + + for &keycode in &keys[start..end] { + if keycode != 0 { + self.keys.insert(keycode, modifier); + } + } + } +} + +impl ModifierKeyState { + pub fn clear(&mut self) { + self.keys.clear(); + } + + pub fn is_empty(&self) -> bool { + self.keys.is_empty() + } + + pub fn update(&mut self, mods: &ModifierKeymap) { + self.keys.retain(|k, v| { + if let Some(m) = mods.get_modifier(*k) { + *v = m; + true + } else { + false + } + }); + } + + pub fn modifiers(&self) -> ModifiersState { + let mut state = ModifiersState::default(); + + for &m in self.keys.values() { + set_modifier(&mut state, m); + } + + state + } + + pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) { + match state { + ElementState::Pressed => self.key_press(keycode, modifier), + ElementState::Released => self.key_release(keycode), + } + } + + pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) { + self.keys.insert(keycode, modifier); + } + + pub fn key_release(&mut self, keycode: ffi::KeyCode) { + self.keys.remove(&keycode); + } +} + +fn set_modifier(state: &mut ModifiersState, modifier: Modifier) { + match modifier { + Modifier::Alt => state.alt = true, + Modifier::Ctrl => state.ctrl = true, + Modifier::Shift => state.shift = true, + Modifier::Logo => state.logo = true, + } +} From 3273c14dead3049354511375bf5c9c83ab321816 Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Sun, 8 Sep 2019 12:57:43 -0700 Subject: [PATCH 45/61] Hide `ModifiersChanged` from the docs. (#1152) We wouldn't want users stumbling on it until all platforms implement it. --- src/event.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event.rs b/src/event.rs index c4ef5f85..5b515086 100644 --- a/src/event.rs +++ b/src/event.rs @@ -134,6 +134,7 @@ pub enum WindowEvent { }, /// Keyboard modifiers have changed + #[doc(hidden)] ModifiersChanged { modifiers: ModifiersState }, /// The cursor has moved on the window. From 068d114740f72ea17db69497674d30998d19fb2a Mon Sep 17 00:00:00 2001 From: dam4rus Date: Mon, 9 Sep 2019 20:15:49 +0200 Subject: [PATCH 46/61] Add touch pressure information for touch events on Windows (#1134) * Add touch pressure information for touch events on Windows * Modified CHANGELOG.md and FEATURES.md to reflect changes * Updated documentation of struct Touch to reflect changes * Replaced mem::uninitalized() with mem::MaybeUninit Fixed warnings in platform_impl/windows/dpi.rs --- CHANGELOG.md | 1 + FEATURES.md | 2 +- src/event.rs | 2 +- src/platform_impl/windows/dpi.rs | 2 +- src/platform_impl/windows/event_loop.rs | 66 ++++++++++++++++++++++--- 5 files changed, 63 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dbb42c9..055446a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - On X11, performance is improved when rapidly calling `Window::set_cursor_icon`. - On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized. - On Windows, unset `maximized` when manually changing the window's position or size. +- On Windows, add touch pressure information for touch events. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/FEATURES.md b/FEATURES.md index e8927957..cd3712b9 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -194,7 +194,7 @@ Legend: |Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ | |Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ | |Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | -|Touch pressure |❌ |❌ |❌ |❌ |❌ |✔️ |❌ | +|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ | |Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❌ | |Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | diff --git a/src/event.rs b/src/event.rs index 5b515086..fc894d97 100644 --- a/src/event.rs +++ b/src/event.rs @@ -333,7 +333,7 @@ pub struct Touch { /// /// ## Platform-specific /// - /// - Only available on **iOS** 9.0+. + /// - Only available on **iOS** 9.0+ and **Windows** 8+. pub force: Option, /// Unique identifier of a finger. pub id: u64, diff --git a/src/platform_impl/windows/dpi.rs b/src/platform_impl/windows/dpi.rs index 548c2608..1a5fa13c 100644 --- a/src/platform_impl/windows/dpi.rs +++ b/src/platform_impl/windows/dpi.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case, unused_unsafe)] -use std::{mem, os::raw::c_void, sync::Once}; +use std::sync::Once; use winapi::{ shared::{ diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 749c0d5d..57e51b21 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -46,7 +46,7 @@ use winapi::{ use crate::{ dpi::{LogicalPosition, LogicalSize, PhysicalSize}, - event::{DeviceEvent, Event, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent}, + event::{DeviceEvent, Event, Force, KeyboardInput, StartCause, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, platform_impl::platform::{ dpi::{ @@ -77,6 +77,12 @@ type GetPointerDeviceRects = unsafe extern "system" fn( displayRect: *mut RECT, ) -> BOOL; +type GetPointerTouchInfo = + unsafe extern "system" fn(pointerId: UINT, touchInfo: *mut winuser::POINTER_TOUCH_INFO) -> BOOL; + +type GetPointerPenInfo = + unsafe extern "system" fn(pointId: UINT, penInfo: *mut winuser::POINTER_PEN_INFO) -> BOOL; + lazy_static! { static ref GET_POINTER_FRAME_INFO_HISTORY: Option = get_function!("user32.dll", GetPointerFrameInfoHistory); @@ -84,6 +90,10 @@ lazy_static! { get_function!("user32.dll", SkipPointerFrameMessages); static ref GET_POINTER_DEVICE_RECTS: Option = get_function!("user32.dll", GetPointerDeviceRects); + static ref GET_POINTER_TOUCH_INFO: Option = + get_function!("user32.dll", GetPointerTouchInfo); + static ref GET_POINTER_PEN_INFO: Option = + get_function!("user32.dll", GetPointerPenInfo); } pub(crate) struct SubclassInput { @@ -852,6 +862,13 @@ pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) assert_eq!(subclass_result, 1); } +fn normalize_pointer_pressure(pressure: u32) -> Option { + match pressure { + 1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)), + _ => None, + } +} + /// 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. // @@ -1499,7 +1516,7 @@ unsafe extern "system" fn public_window_callback( continue; }, location, - force: None, // TODO + force: None, // WM_TOUCH doesn't support pressure information id: input.dwID as u64, device_id: DEVICE_ID, }), @@ -1551,18 +1568,21 @@ unsafe extern "system" fn public_window_callback( // The information retrieved appears in reverse chronological order, with the most recent entry in the first // row of the returned array for pointer_info in pointer_infos.iter().rev() { - let mut device_rect: RECT = mem::uninitialized(); - let mut display_rect: RECT = mem::uninitialized(); + let mut device_rect = mem::MaybeUninit::uninit(); + let mut display_rect = mem::MaybeUninit::uninit(); if (GetPointerDeviceRects( pointer_info.sourceDevice, - &mut device_rect as *mut _, - &mut display_rect as *mut _, + device_rect.as_mut_ptr(), + display_rect.as_mut_ptr(), )) == 0 { continue; } + let device_rect = device_rect.assume_init(); + let display_rect = display_rect.assume_init(); + // For the most precise himetric to pixel conversion we calculate the ratio between the resolution // of the display device (pixel) and the touch device (himetric). let himetric_to_pixel_ratio_x = (display_rect.right - display_rect.left) as f64 @@ -1587,6 +1607,38 @@ unsafe extern "system" fn public_window_callback( continue; } + let force = match pointer_info.pointerType { + winuser::PT_TOUCH => { + let mut touch_info = mem::MaybeUninit::uninit(); + GET_POINTER_TOUCH_INFO.and_then(|GetPointerTouchInfo| { + match GetPointerTouchInfo( + pointer_info.pointerId, + touch_info.as_mut_ptr(), + ) { + 0 => None, + _ => normalize_pointer_pressure( + touch_info.assume_init().pressure, + ), + } + }) + } + winuser::PT_PEN => { + let mut pen_info = mem::MaybeUninit::uninit(); + GET_POINTER_PEN_INFO.and_then(|GetPointerPenInfo| { + match GetPointerPenInfo( + pointer_info.pointerId, + pen_info.as_mut_ptr(), + ) { + 0 => None, + _ => { + normalize_pointer_pressure(pen_info.assume_init().pressure) + } + } + }) + } + _ => None, + }; + 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); @@ -1604,7 +1656,7 @@ unsafe extern "system" fn public_window_callback( continue; }, location, - force: None, // TODO + force, id: pointer_info.pointerId as u64, device_id: DEVICE_ID, }), From a3739d6baddfc411b6223d1d4352f1f4f80f2b4c Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 11 Sep 2019 08:28:21 +0200 Subject: [PATCH 47/61] wayland: instantly wake up if events are pending (#1153) Just before starting to poll/wait on calloop(mio), check if there are already events pending in the internal buffer of our wayland event queue. If so, dispatch them and force an instant wakeup from the polling, in order to behave as if we were instantly woken up by incoming wayland events. When using OpenGL, mesa shares our wayland socket, and also reads from it, especially if vsync is enabled as it'll do blocking reads. When doing so, it may enqueue events in the internal buffer of our event queue. As the socket has been read, mio will thus not notify it to calloop as read, and thus calloop will not know it needs to dispatch. In some cases this can lead to some events being delivered much later than they should. Combined with key repetition this can actually cause some flooding of the event queue making this effect event worse. Fixes #1148 --- src/platform_impl/linux/wayland/event_loop.rs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index fa1ef347..af8c8079 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -314,6 +314,25 @@ impl EventLoop { // 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, + _ => unreachable!(), + }; + let dispatched = window_target + .evq + .borrow_mut() + .dispatch_pending() + .expect("Wayland connection lost."); + dispatched > 0 + }; + match control_flow { ControlFlow::Exit => break, ControlFlow::Poll => { @@ -328,7 +347,12 @@ impl EventLoop { ); } ControlFlow::Wait => { - self.inner_loop.dispatch(None, &mut ()).unwrap(); + let timeout = if instant_wakeup { + Some(::std::time::Duration::from_millis(0)) + } else { + None + }; + self.inner_loop.dispatch(timeout, &mut ()).unwrap(); callback( crate::event::Event::NewEvents(crate::event::StartCause::WaitCancelled { start: Instant::now(), @@ -341,7 +365,7 @@ impl EventLoop { ControlFlow::WaitUntil(deadline) => { let start = Instant::now(); // compute the blocking duration - let duration = if deadline > start { + let duration = if deadline > start && !instant_wakeup { deadline - start } else { ::std::time::Duration::from_millis(0) From b6de19e92ec5a923a607435668312a1a7cbb9bb0 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Thu, 12 Sep 2019 00:22:41 +0200 Subject: [PATCH 48/61] Changelog entry for #1153 (#1157) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 055446a5..7c897b9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized. - On Windows, unset `maximized` when manually changing the window's position or size. - On Windows, add touch pressure information for touch events. +- On Wayland, fix event processing sometimes stalling when using OpenGL with vsync. # 0.20.0 Alpha 3 (2019-08-14) From 36f4eccb5c8f00f745eb60a16a71742a0643ec12 Mon Sep 17 00:00:00 2001 From: hafiz Date: Thu, 12 Sep 2019 18:38:44 -0500 Subject: [PATCH 49/61] fix: distinguish grab and grabbing cursors (#1154) On macOS, there is a difference between a "grab" cursor and a "grabbing" cursor, where "grab" is an open-hand cursor used during a hover, and "grabbing" is a closed-hand cursor used on a click. These, and other native MacOS cursors, can be seen at the [NSCursor documentation](https://developer.apple.com/documentation/appkit/nscursor?language=objc). See https://github.com/hecrj/iced/issues/9 for the motivation for this PR. --- CHANGELOG.md | 1 + src/platform_impl/macos/util/cursor.rs | 4 +++- src/window.rs | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c897b9e..90cb469e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - On iOS, fix improper `msg_send` usage that was UB and/or would break if `!` is stabilized. - On Windows, unset `maximized` when manually changing the window's position or size. - On Windows, add touch pressure information for touch events. +- On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`. - On Wayland, fix event processing sometimes stalling when using OpenGL with vsync. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs index e3e3bcf0..7f0b57fe 100644 --- a/src/platform_impl/macos/util/cursor.rs +++ b/src/platform_impl/macos/util/cursor.rs @@ -15,10 +15,12 @@ pub enum Cursor { impl From for Cursor { fn from(cursor: CursorIcon) -> Self { + // See native cursors at https://developer.apple.com/documentation/appkit/nscursor?language=objc. match cursor { CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"), CursorIcon::Hand => Cursor::Native("pointingHandCursor"), - CursorIcon::Grabbing | CursorIcon::Grab => Cursor::Native("closedHandCursor"), + CursorIcon::Grab => Cursor::Native("openHandCursor"), + CursorIcon::Grabbing => Cursor::Native("closedHandCursor"), CursorIcon::Text => Cursor::Native("IBeamCursor"), CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"), CursorIcon::Copy => Cursor::Native("dragCopyCursor"), diff --git a/src/window.rs b/src/window.rs index db86773d..481e9728 100644 --- a/src/window.rs +++ b/src/window.rs @@ -760,7 +760,9 @@ pub enum CursorIcon { Alias, Copy, NoDrop, + /// Indicates something can be grabbed. Grab, + /// Indicates something is grabbed. Grabbing, AllScroll, ZoomIn, From 57a53bda74fa9a46e7e282d0f857d02048e64353 Mon Sep 17 00:00:00 2001 From: Osspial Date: Fri, 13 Sep 2019 19:09:45 -0400 Subject: [PATCH 50/61] Officially remove the Emscripten backend (#1159) --- CHANGELOG.md | 1 + FEATURES.md | 69 +- README.md | 2 +- examples/window_run_return.rs | 2 +- src/platform_impl/emscripten/ffi.rs | 363 -------- src/platform_impl/emscripten/mod.rs | 1246 --------------------------- src/platform_impl/mod.rs | 4 - 7 files changed, 37 insertions(+), 1650 deletions(-) delete mode 100644 src/platform_impl/emscripten/ffi.rs delete mode 100644 src/platform_impl/emscripten/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 90cb469e..de34b407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - On Windows, add touch pressure information for touch events. - On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`. - On Wayland, fix event processing sometimes stalling when using OpenGL with vsync. +- Officially remove the Emscripten backend # 0.20.0 Alpha 3 (2019-08-14) diff --git a/FEATURES.md b/FEATURES.md index cd3712b9..9610b7ba 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -12,7 +12,6 @@ be used to create both games and applications. It supports the main graphical pl - iOS - Android - Web - - via Emscripten - via WASM Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not @@ -162,57 +161,57 @@ Legend: - ❓: Unknown status ### Windowing -|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Emscripten| +|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM | |-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | |Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |❓ | |Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ | -|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A** | -|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|**N/A** | -|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | +|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ | +|Window decorations |✔️ |✔️ |✔️ |▢[#306] |**N/A**|**N/A**|❓ | +|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ | |Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|❓ | -|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❌ | -|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** | -|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❌ | -|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❌ | -|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❌ | -|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | -|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | +|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |❓ | +|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ | +|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ | +|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❓ | +|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ | +|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ | +|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❓ | +|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ | +|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❓ | ### System information -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| -|---------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | -|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A** | -|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❌ | +|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | +|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | +|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |❓ | +|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❓ | ### Input handling -|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| +|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | |----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | -|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ | -|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A** | -|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|✔️ | -|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❌ | -|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ | -|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ | -|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❌ | -|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ | +|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|❓ | +|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|❓ | +|Cursor grab |✔️ |▢[#165] |▢[#242] |❌[#306] |**N/A**|**N/A**|❓ | +|Cursor icon |✔️ |✔️ |✔️ |❌[#306] |**N/A**|**N/A**|❓ | +|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❓ | +|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❓ | +|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |❓ | +|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |❓ | |Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ | -|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❌ | -|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❌ | -|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❌ | +|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ | +|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ | +|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ | ### Pending API Reworks Changes in the API that have been agreed upon but aren't implemented across all platforms. -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| +|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | -|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | -|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❌ | -|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❌ | +|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ | +|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❓ | +|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ | ### Completed API Reworks -|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten| +|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM | |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | [#165]: https://github.com/rust-windowing/winit/issues/165 diff --git a/README.md b/README.md index 3524bfaf..8d685f26 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Winit provides the following features, which can be enabled in your `Cargo.toml` ### Platform-specific usage -#### Emscripten and WebAssembly +#### WebAssembly Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to: diff --git a/examples/window_run_return.rs b/examples/window_run_return.rs index 374703e1..1dd7c5ac 100644 --- a/examples/window_run_return.rs +++ b/examples/window_run_return.rs @@ -50,7 +50,7 @@ fn main() { println!("Okay we're done now for real."); } -#[cfg(any(target_os = "ios", target_os = "android", target_os = "emscripten"))] +#[cfg(any(target_os = "ios", target_os = "android"))] fn main() { println!("This platform doesn't support run_return."); } diff --git a/src/platform_impl/emscripten/ffi.rs b/src/platform_impl/emscripten/ffi.rs deleted file mode 100644 index 6eb9d743..00000000 --- a/src/platform_impl/emscripten/ffi.rs +++ /dev/null @@ -1,363 +0,0 @@ -#![allow(dead_code, non_camel_case_types, non_snake_case)] - -#[cfg(test)] -use std::mem; -use std::os::raw::{c_char, c_double, c_int, c_long, c_ulong, c_ushort, c_void}; - -pub type EM_BOOL = c_int; -pub type EM_UTF8 = c_char; -pub type EMSCRIPTEN_RESULT = c_int; - -pub const EM_TRUE: EM_BOOL = 1; -pub const EM_FALSE: EM_BOOL = 0; - -// values for EMSCRIPTEN_RESULT -pub const EMSCRIPTEN_RESULT_SUCCESS: c_int = 0; -pub const EMSCRIPTEN_RESULT_DEFERRED: c_int = 1; -pub const EMSCRIPTEN_RESULT_NOT_SUPPORTED: c_int = -1; -pub const EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED: c_int = -2; -pub const EMSCRIPTEN_RESULT_INVALID_TARGET: c_int = -3; -pub const EMSCRIPTEN_RESULT_UNKNOWN_TARGET: c_int = -4; -pub const EMSCRIPTEN_RESULT_INVALID_PARAM: c_int = -5; -pub const EMSCRIPTEN_RESULT_FAILED: c_int = -6; -pub const EMSCRIPTEN_RESULT_NO_DATA: c_int = -7; - -// values for EMSCRIPTEN EVENT -pub const EMSCRIPTEN_EVENT_KEYPRESS: c_int = 1; -pub const EMSCRIPTEN_EVENT_KEYDOWN: c_int = 2; -pub const EMSCRIPTEN_EVENT_KEYUP: c_int = 3; -pub const EMSCRIPTEN_EVENT_CLICK: c_int = 4; -pub const EMSCRIPTEN_EVENT_MOUSEDOWN: c_int = 5; -pub const EMSCRIPTEN_EVENT_MOUSEUP: c_int = 6; -pub const EMSCRIPTEN_EVENT_DBLCLICK: c_int = 7; -pub const EMSCRIPTEN_EVENT_MOUSEMOVE: c_int = 8; -pub const EMSCRIPTEN_EVENT_WHEEL: c_int = 9; -pub const EMSCRIPTEN_EVENT_RESIZE: c_int = 10; -pub const EMSCRIPTEN_EVENT_SCROLL: c_int = 11; -pub const EMSCRIPTEN_EVENT_BLUR: c_int = 12; -pub const EMSCRIPTEN_EVENT_FOCUS: c_int = 13; -pub const EMSCRIPTEN_EVENT_FOCUSIN: c_int = 14; -pub const EMSCRIPTEN_EVENT_FOCUSOUT: c_int = 15; -pub const EMSCRIPTEN_EVENT_DEVICEORIENTATION: c_int = 16; -pub const EMSCRIPTEN_EVENT_DEVICEMOTION: c_int = 17; -pub const EMSCRIPTEN_EVENT_ORIENTATIONCHANGE: c_int = 18; -pub const EMSCRIPTEN_EVENT_FULLSCREENCHANGE: c_int = 19; -pub const EMSCRIPTEN_EVENT_POINTERLOCKCHANGE: c_int = 20; -pub const EMSCRIPTEN_EVENT_VISIBILITYCHANGE: c_int = 21; -pub const EMSCRIPTEN_EVENT_TOUCHSTART: c_int = 22; -pub const EMSCRIPTEN_EVENT_TOUCHEND: c_int = 23; -pub const EMSCRIPTEN_EVENT_TOUCHMOVE: c_int = 24; -pub const EMSCRIPTEN_EVENT_TOUCHCANCEL: c_int = 25; -pub const EMSCRIPTEN_EVENT_GAMEPADCONNECTED: c_int = 26; -pub const EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED: c_int = 27; -pub const EMSCRIPTEN_EVENT_BEFOREUNLOAD: c_int = 28; -pub const EMSCRIPTEN_EVENT_BATTERYCHARGINGCHANGE: c_int = 29; -pub const EMSCRIPTEN_EVENT_BATTERYLEVELCHANGE: c_int = 30; -pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST: c_int = 31; -pub const EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED: c_int = 32; -pub const EMSCRIPTEN_EVENT_MOUSEENTER: c_int = 33; -pub const EMSCRIPTEN_EVENT_MOUSELEAVE: c_int = 34; -pub const EMSCRIPTEN_EVENT_MOUSEOVER: c_int = 35; -pub const EMSCRIPTEN_EVENT_MOUSEOUT: c_int = 36; -pub const EMSCRIPTEN_EVENT_CANVASRESIZED: c_int = 37; -pub const EMSCRIPTEN_EVENT_POINTERLOCKERROR: c_int = 38; - -pub const EM_HTML5_SHORT_STRING_LEN_BYTES: usize = 32; - -pub const DOM_KEY_LOCATION_STANDARD: c_ulong = 0x00; -pub const DOM_KEY_LOCATION_LEFT: c_ulong = 0x01; -pub const DOM_KEY_LOCATION_RIGHT: c_ulong = 0x02; -pub const DOM_KEY_LOCATION_NUMPAD: c_ulong = 0x03; - -pub type em_callback_func = Option; - -pub type em_key_callback_func = Option< - unsafe extern "C" fn( - eventType: c_int, - keyEvent: *const EmscriptenKeyboardEvent, - userData: *mut c_void, - ) -> EM_BOOL, ->; - -pub type em_mouse_callback_func = Option< - unsafe extern "C" fn( - eventType: c_int, - mouseEvent: *const EmscriptenMouseEvent, - userData: *mut c_void, - ) -> EM_BOOL, ->; - -pub type em_pointerlockchange_callback_func = Option< - unsafe extern "C" fn( - eventType: c_int, - pointerlockChangeEvent: *const EmscriptenPointerlockChangeEvent, - userData: *mut c_void, - ) -> EM_BOOL, ->; - -pub type em_fullscreenchange_callback_func = Option< - unsafe extern "C" fn( - eventType: c_int, - fullscreenChangeEvent: *const EmscriptenFullscreenChangeEvent, - userData: *mut c_void, - ) -> EM_BOOL, ->; - -pub type em_touch_callback_func = Option< - unsafe extern "C" fn( - eventType: c_int, - touchEvent: *const EmscriptenTouchEvent, - userData: *mut c_void, - ) -> EM_BOOL, ->; - -#[repr(C)] -pub struct EmscriptenFullscreenChangeEvent { - pub isFullscreen: c_int, - pub fullscreenEnabled: c_int, - pub nodeName: [c_char; 128usize], - pub id: [c_char; 128usize], - pub elementWidth: c_int, - pub elementHeight: c_int, - pub screenWidth: c_int, - pub screenHeight: c_int, -} -#[test] -fn bindgen_test_layout_EmscriptenFullscreenChangeEvent() { - assert_eq!(mem::size_of::(), 280usize); - assert_eq!(mem::align_of::(), 4usize); -} - -#[repr(C)] -#[derive(Debug, Copy)] -pub struct EmscriptenKeyboardEvent { - pub key: [c_char; 32usize], - pub code: [c_char; 32usize], - pub location: c_ulong, - pub ctrlKey: c_int, - pub shiftKey: c_int, - pub altKey: c_int, - pub metaKey: c_int, - pub repeat: c_int, - pub locale: [c_char; 32usize], - pub charValue: [c_char; 32usize], - pub charCode: c_ulong, - pub keyCode: c_ulong, - pub which: c_ulong, -} -#[test] -fn bindgen_test_layout_EmscriptenKeyboardEvent() { - assert_eq!(mem::size_of::(), 184usize); - assert_eq!(mem::align_of::(), 8usize); -} -impl Clone for EmscriptenKeyboardEvent { - fn clone(&self) -> Self { - *self - } -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct EmscriptenMouseEvent { - pub timestamp: f64, - pub screenX: c_long, - pub screenY: c_long, - pub clientX: c_long, - pub clientY: c_long, - pub ctrlKey: c_int, - pub shiftKey: c_int, - pub altKey: c_int, - pub metaKey: c_int, - pub button: c_ushort, - pub buttons: c_ushort, - pub movementX: c_long, - pub movementY: c_long, - pub targetX: c_long, - pub targetY: c_long, - pub canvasX: c_long, - pub canvasY: c_long, - pub padding: c_long, -} -#[test] -fn bindgen_test_layout_EmscriptenMouseEvent() { - assert_eq!(mem::size_of::(), 120usize); - assert_eq!(mem::align_of::(), 8usize); -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct EmscriptenTouchPoint { - pub identifier: c_long, - pub screenX: c_long, - pub screenY: c_long, - pub clientX: c_long, - pub clientY: c_long, - pub pageX: c_long, - pub pageY: c_long, - pub isChanged: c_int, - pub onTarget: c_int, - pub targetX: c_long, - pub targetY: c_long, - pub canvasX: c_long, - pub canvasY: c_long, -} -#[test] -fn bindgen_test_layout_EmscriptenTouchPoint() { - assert_eq!(mem::size_of::(), 96usize); - assert_eq!(mem::align_of::(), 8usize); -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct EmscriptenTouchEvent { - pub numTouches: c_int, - pub ctrlKey: c_int, - pub shiftKey: c_int, - pub altKey: c_int, - pub metaKey: c_int, - pub touches: [EmscriptenTouchPoint; 32usize], -} -#[test] -fn bindgen_test_layout_EmscriptenTouchEvent() { - assert_eq!(mem::size_of::(), 3096usize); - assert_eq!(mem::align_of::(), 8usize); -} - -#[repr(C)] -pub struct EmscriptenPointerlockChangeEvent { - pub isActive: c_int, - pub nodeName: [c_char; 128usize], - pub id: [c_char; 128usize], -} -#[test] -fn bindgen_test_layout_EmscriptenPointerlockChangeEvent() { - assert_eq!(mem::size_of::(), 260usize); - assert_eq!(mem::align_of::(), 4usize); -} - -extern "C" { - pub fn emscripten_set_canvas_size(width: c_int, height: c_int) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_get_canvas_size( - width: *mut c_int, - height: *mut c_int, - is_fullscreen: *mut c_int, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_element_css_size( - target: *const c_char, - width: c_double, - height: c_double, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_get_element_css_size( - target: *const c_char, - width: *mut c_double, - height: *mut c_double, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_request_pointerlock( - target: *const c_char, - deferUntilInEventHandler: EM_BOOL, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_exit_pointerlock() -> EMSCRIPTEN_RESULT; - - pub fn emscripten_request_fullscreen( - target: *const c_char, - deferUntilInEventHandler: EM_BOOL, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_exit_fullscreen() -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_keydown_callback( - target: *const c_char, - userData: *mut c_void, - useCapture: EM_BOOL, - callback: em_key_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_keyup_callback( - target: *const c_char, - userData: *mut c_void, - useCapture: EM_BOOL, - callback: em_key_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_mousemove_callback( - target: *const c_char, - user_data: *mut c_void, - use_capture: EM_BOOL, - callback: em_mouse_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_mousedown_callback( - target: *const c_char, - user_data: *mut c_void, - use_capture: EM_BOOL, - callback: em_mouse_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_mouseup_callback( - target: *const c_char, - user_data: *mut c_void, - use_capture: EM_BOOL, - callback: em_mouse_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_hide_mouse(); - - pub fn emscripten_get_device_pixel_ratio() -> f64; - - pub fn emscripten_set_pointerlockchange_callback( - target: *const c_char, - userData: *mut c_void, - useCapture: EM_BOOL, - callback: em_pointerlockchange_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_fullscreenchange_callback( - target: *const c_char, - userData: *mut c_void, - useCapture: EM_BOOL, - callback: em_fullscreenchange_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_asm_const(code: *const c_char); - - pub fn emscripten_set_main_loop( - func: em_callback_func, - fps: c_int, - simulate_infinite_loop: EM_BOOL, - ); - - pub fn emscripten_cancel_main_loop(); - - pub fn emscripten_set_touchstart_callback( - target: *const c_char, - userData: *mut c_void, - useCapture: c_int, - callback: em_touch_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_touchend_callback( - target: *const c_char, - userData: *mut c_void, - useCapture: c_int, - callback: em_touch_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_touchmove_callback( - target: *const c_char, - userData: *mut c_void, - useCapture: c_int, - callback: em_touch_callback_func, - ) -> EMSCRIPTEN_RESULT; - - pub fn emscripten_set_touchcancel_callback( - target: *const c_char, - userData: *mut c_void, - useCapture: c_int, - callback: em_touch_callback_func, - ) -> EMSCRIPTEN_RESULT; -} diff --git a/src/platform_impl/emscripten/mod.rs b/src/platform_impl/emscripten/mod.rs deleted file mode 100644 index 2e9c9709..00000000 --- a/src/platform_impl/emscripten/mod.rs +++ /dev/null @@ -1,1246 +0,0 @@ -#![cfg(target_os = "emscripten")] - -mod ffi; - -use std::{ - cell::RefCell, - collections::VecDeque, - mem, - os::raw::{c_char, c_double, c_int, c_ulong, c_void}, - ptr, str, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, - }, -}; - -use crate::{ - dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, - error::{ExternalError, NotSupportedError}, - window::MonitorHandle as RootMonitorHandle, -}; - -const DOCUMENT_NAME: &'static str = "#document\0"; - -fn hidpi_factor() -> f64 { - unsafe { ffi::emscripten_get_device_pixel_ratio() as f64 } -} - -#[derive(Clone, Default)] -pub struct PlatformSpecificWindowBuilderAttributes; - -unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} -unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} - -pub type OsError = std::io::Error; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId; - -impl DeviceId { - pub unsafe fn dummy() -> Self { - DeviceId - } -} - -#[derive(Clone, Default)] -pub struct PlatformSpecificHeadlessBuilderAttributes; - -#[derive(Debug, Clone)] -pub struct MonitorHandle; - -impl MonitorHandle { - #[inline] - pub fn name(&self) -> Option { - Some("Canvas".to_owned()) - } - - #[inline] - pub fn outer_position(&self) -> PhysicalPosition { - unimplemented!() - } - - #[inline] - pub fn size(&self) -> PhysicalSize { - (0, 0).into() - } - - #[inline] - pub fn hidpi_factor(&self) -> f64 { - hidpi_factor() - } -} - -// Used to assign a callback to emscripten main loop -thread_local!(static MAIN_LOOP_CALLBACK: RefCell<*mut c_void> = RefCell::new(ptr::null_mut())); - -// Used to assign a callback to emscripten main loop -pub fn set_main_loop_callback(callback: F) -where - F: FnMut(), -{ - MAIN_LOOP_CALLBACK.with(|log| { - *log.borrow_mut() = &callback as *const _ as *mut c_void; - }); - - unsafe { - ffi::emscripten_set_main_loop(Some(wrapper::), 0, 1); - } - - unsafe extern "C" fn wrapper() - where - F: FnMut(), - { - MAIN_LOOP_CALLBACK.with(|z| { - let closure = *z.borrow_mut() as *mut F; - (*closure)(); - }); - } -} - -#[derive(Clone)] -pub struct EventLoopProxy; - -impl EventLoopProxy { - pub fn wakeup(&self) -> Result<(), ::EventLoopClosed> { - unimplemented!() - } -} - -pub struct EventLoop { - window: Mutex>>, - interrupted: AtomicBool, -} - -impl EventLoop { - pub fn new() -> EventLoop { - EventLoop { - window: Mutex::new(None), - interrupted: AtomicBool::new(false), - } - } - - #[inline] - pub fn interrupt(&self) { - self.interrupted.store(true, Ordering::Relaxed); - } - - #[inline] - pub fn create_proxy(&self) -> EventLoopProxy { - unimplemented!() - } - - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut list = VecDeque::with_capacity(1); - list.push_back(MonitorHandle); - list - } - - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } - - pub fn poll_events(&self, mut callback: F) - where - F: FnMut(::Event), - { - let ref mut window = *self.window.lock().unwrap(); - if let &mut Some(ref mut window) = window { - while let Some(event) = window.events.lock().unwrap().pop_front() { - callback(event) - } - } - } - - pub fn run_forever(&self, mut callback: F) - where - F: FnMut(::Event) -> ::ControlFlow, - { - self.interrupted.store(false, Ordering::Relaxed); - - // TODO: handle control flow - - set_main_loop_callback(|| { - self.poll_events(|e| { - callback(e); - }); - ::std::thread::sleep(::std::time::Duration::from_millis(5)); - if self.interrupted.load(Ordering::Relaxed) { - unsafe { - ffi::emscripten_cancel_main_loop(); - } - } - }); - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(usize); - -impl WindowId { - pub unsafe fn dummy() -> Self { - WindowId(0) - } -} - -pub struct Window2 { - cursor_grabbed: Mutex, - cursor_visible: Mutex, - is_fullscreen: bool, - events: Box>>, -} - -pub struct Window { - window: Arc, -} - -fn show_mouse() { - // Hide mouse hasn't show mouse equivalent. - // There is a pull request on emscripten that hasn't been merged #4616 - // that contains: - // - // var styleSheet = document.styleSheets[0]; - // var rules = styleSheet.cssRules; - // for (var i = 0; i < rules.length; i++) { - // if (rules[i].cssText.substr(0, 6) == 'canvas') { - // styleSheet.deleteRule(i); - // i--; - // } - // } - // styleSheet.insertRule('canvas.emscripten { border: none; cursor: auto; }', 0); - unsafe { - ffi::emscripten_asm_const(b"var styleSheet = document.styleSheets[0]; var rules = styleSheet.cssRules; for (var i = 0; i < rules.length; i++) { if (rules[i].cssText.substr(0, 6) == 'canvas') { styleSheet.deleteRule(i); i--; } } styleSheet.insertRule('canvas.emscripten { border: none; cursor: auto; }', 0);\0".as_ptr() as *const c_char); - } -} - -extern "C" fn mouse_callback( - event_type: c_int, - event: *const ffi::EmscriptenMouseEvent, - event_queue: *mut c_void, -) -> ffi::EM_BOOL { - unsafe { - let queue: &Mutex> = mem::transmute(event_queue); - - let modifiers = ::ModifiersState { - shift: (*event).shiftKey == ffi::EM_TRUE, - ctrl: (*event).ctrlKey == ffi::EM_TRUE, - alt: (*event).altKey == ffi::EM_TRUE, - logo: (*event).metaKey == ffi::EM_TRUE, - }; - - match event_type { - ffi::EMSCRIPTEN_EVENT_MOUSEMOVE => { - let dpi_factor = hidpi_factor(); - let position = LogicalPosition::from_physical( - ((*event).canvasX as f64, (*event).canvasY as f64), - dpi_factor, - ); - queue.lock().unwrap().push_back(::Event::WindowEvent { - window_id: ::WindowId(WindowId(0)), - event: ::WindowEvent::CursorMoved { - device_id: ::DeviceId(DeviceId), - position, - modifiers, - }, - }); - queue.lock().unwrap().push_back(::Event::DeviceEvent { - device_id: ::DeviceId(DeviceId), - event: ::DeviceEvent::MouseMotion { - delta: ((*event).movementX as f64, (*event).movementY as f64), - }, - }); - } - mouse_input @ ffi::EMSCRIPTEN_EVENT_MOUSEDOWN - | mouse_input @ ffi::EMSCRIPTEN_EVENT_MOUSEUP => { - let button = match (*event).button { - 0 => ::MouseButton::Left, - 1 => ::MouseButton::Middle, - 2 => ::MouseButton::Right, - other => ::MouseButton::Other(other as u8), - }; - let state = match mouse_input { - ffi::EMSCRIPTEN_EVENT_MOUSEDOWN => ::ElementState::Pressed, - ffi::EMSCRIPTEN_EVENT_MOUSEUP => ::ElementState::Released, - _ => unreachable!(), - }; - queue.lock().unwrap().push_back(::Event::WindowEvent { - window_id: ::WindowId(WindowId(0)), - event: ::WindowEvent::MouseInput { - device_id: ::DeviceId(DeviceId), - state, - button, - modifiers, - }, - }) - } - _ => {} - } - } - ffi::EM_FALSE -} - -extern "C" fn keyboard_callback( - event_type: c_int, - event: *const ffi::EmscriptenKeyboardEvent, - event_queue: *mut c_void, -) -> ffi::EM_BOOL { - unsafe { - let queue: &Mutex> = mem::transmute(event_queue); - - let modifiers = ::ModifiersState { - shift: (*event).shiftKey == ffi::EM_TRUE, - ctrl: (*event).ctrlKey == ffi::EM_TRUE, - alt: (*event).altKey == ffi::EM_TRUE, - logo: (*event).metaKey == ffi::EM_TRUE, - }; - - match event_type { - ffi::EMSCRIPTEN_EVENT_KEYDOWN => { - queue.lock().unwrap().push_back(::Event::WindowEvent { - window_id: ::WindowId(WindowId(0)), - event: ::WindowEvent::KeyboardInput { - device_id: ::DeviceId(DeviceId), - input: ::KeyboardInput { - scancode: key_translate((*event).key) as u32, - state: ::ElementState::Pressed, - virtual_keycode: key_translate_virt((*event).key, (*event).location), - modifiers, - }, - }, - }); - } - ffi::EMSCRIPTEN_EVENT_KEYUP => { - queue.lock().unwrap().push_back(::Event::WindowEvent { - window_id: ::WindowId(WindowId(0)), - event: ::WindowEvent::KeyboardInput { - device_id: ::DeviceId(DeviceId), - input: ::KeyboardInput { - scancode: key_translate((*event).key) as u32, - state: ::ElementState::Released, - virtual_keycode: key_translate_virt((*event).key, (*event).location), - modifiers, - }, - }, - }); - } - _ => {} - } - } - ffi::EM_FALSE -} - -extern "C" fn touch_callback( - event_type: c_int, - event: *const ffi::EmscriptenTouchEvent, - event_queue: *mut c_void, -) -> ffi::EM_BOOL { - unsafe { - let queue: &Mutex> = mem::transmute(event_queue); - - let phase = match event_type { - ffi::EMSCRIPTEN_EVENT_TOUCHSTART => ::TouchPhase::Started, - ffi::EMSCRIPTEN_EVENT_TOUCHEND => ::TouchPhase::Ended, - ffi::EMSCRIPTEN_EVENT_TOUCHMOVE => ::TouchPhase::Moved, - ffi::EMSCRIPTEN_EVENT_TOUCHCANCEL => ::TouchPhase::Cancelled, - _ => return ffi::EM_FALSE, - }; - - for touch in 0..(*event).numTouches as usize { - let touch = (*event).touches[touch]; - if touch.isChanged == ffi::EM_TRUE { - let dpi_factor = hidpi_factor(); - let location = LogicalPosition::from_physical( - (touch.canvasX as f64, touch.canvasY as f64), - dpi_factor, - ); - queue.lock().unwrap().push_back(::Event::WindowEvent { - window_id: ::WindowId(WindowId(0)), - event: ::WindowEvent::Touch(::Touch { - device_id: ::DeviceId(DeviceId), - phase, - id: touch.identifier as u64, - location, - force: None, // TODO - }), - }); - } - } - } - ffi::EM_FALSE -} - -// In case of fullscreen window this method will request fullscreen on change -#[allow(non_snake_case)] -unsafe extern "C" fn fullscreen_callback( - _eventType: c_int, - _fullscreenChangeEvent: *const ffi::EmscriptenFullscreenChangeEvent, - _userData: *mut c_void, -) -> ffi::EM_BOOL { - ffi::emscripten_request_fullscreen(ptr::null(), ffi::EM_TRUE); - ffi::EM_FALSE -} - -// In case of pointer grabbed this method will request pointer lock on change -#[allow(non_snake_case)] -unsafe extern "C" fn pointerlockchange_callback( - _eventType: c_int, - _pointerlockChangeEvent: *const ffi::EmscriptenPointerlockChangeEvent, - _userData: *mut c_void, -) -> ffi::EM_BOOL { - ffi::emscripten_request_pointerlock(ptr::null(), ffi::EM_TRUE); - ffi::EM_FALSE -} - -fn em_try(res: ffi::EMSCRIPTEN_RESULT) -> Result<(), String> { - match res { - ffi::EMSCRIPTEN_RESULT_SUCCESS | ffi::EMSCRIPTEN_RESULT_DEFERRED => Ok(()), - r @ _ => Err(error_to_str(r).to_string()), - } -} - -impl Window { - pub fn new( - event_loop: &EventLoop, - attribs: ::WindowAttributes, - _pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result { - if event_loop.window.lock().unwrap().is_some() { - return Err(::CreationError::OsError( - "Cannot create another window".to_owned(), - )); - } - - let w = Window2 { - cursor_grabbed: Mutex::new(false), - cursor_visible: Mutex::new(true), - events: Default::default(), - is_fullscreen: attribs.fullscreen.is_some(), - }; - - let window = Window { - window: Arc::new(w), - }; - - // TODO: set up more event callbacks - unsafe { - em_try(ffi::emscripten_set_mousemove_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - mem::transmute(&*window.window.events), - ffi::EM_FALSE, - Some(mouse_callback), - )) - .map_err(|e| ::CreationError::OsError(format!("emscripten error: {}", e)))?; - em_try(ffi::emscripten_set_mousedown_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - mem::transmute(&*window.window.events), - ffi::EM_FALSE, - Some(mouse_callback), - )) - .map_err(|e| ::CreationError::OsError(format!("emscripten error: {}", e)))?; - em_try(ffi::emscripten_set_mouseup_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - mem::transmute(&*window.window.events), - ffi::EM_FALSE, - Some(mouse_callback), - )) - .map_err(|e| ::CreationError::OsError(format!("emscripten error: {}", e)))?; - em_try(ffi::emscripten_set_keydown_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - mem::transmute(&*window.window.events), - ffi::EM_FALSE, - Some(keyboard_callback), - )) - .map_err(|e| ::CreationError::OsError(format!("emscripten error: {}", e)))?; - em_try(ffi::emscripten_set_keyup_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - mem::transmute(&*window.window.events), - ffi::EM_FALSE, - Some(keyboard_callback), - )) - .map_err(|e| ::CreationError::OsError(format!("emscripten error: {}", e)))?; - em_try(ffi::emscripten_set_touchstart_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - mem::transmute(&*window.window.events), - ffi::EM_FALSE, - Some(touch_callback), - )) - .map_err(|e| ::CreationError::OsError(format!("emscripten error: {}", e)))?; - em_try(ffi::emscripten_set_touchend_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - mem::transmute(&*window.window.events), - ffi::EM_FALSE, - Some(touch_callback), - )) - .map_err(|e| ::CreationError::OsError(format!("emscripten error: {}", e)))?; - em_try(ffi::emscripten_set_touchmove_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - mem::transmute(&*window.window.events), - ffi::EM_FALSE, - Some(touch_callback), - )) - .map_err(|e| ::CreationError::OsError(format!("emscripten error: {}", e)))?; - em_try(ffi::emscripten_set_touchcancel_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - mem::transmute(&*window.window.events), - ffi::EM_FALSE, - Some(touch_callback), - )) - .map_err(|e| ::CreationError::OsError(format!("emscripten error: {}", e)))?; - } - - if attribs.fullscreen.is_some() { - unsafe { - em_try(ffi::emscripten_request_fullscreen( - ptr::null(), - ffi::EM_TRUE, - )) - .map_err(|e| ::CreationError::OsError(e))?; - em_try(ffi::emscripten_set_fullscreenchange_callback( - ptr::null(), - 0 as *mut c_void, - ffi::EM_FALSE, - Some(fullscreen_callback), - )) - .map_err(|e| ::CreationError::OsError(e))?; - } - } else if let Some(size) = attribs.inner_size { - window.set_inner_size(size); - } - - *event_loop.window.lock().unwrap() = Some(window.window.clone()); - Ok(window) - } - - #[inline] - pub fn id(&self) -> WindowId { - WindowId(0) - } - - #[inline] - pub fn set_title(&self, _title: &str) {} - - #[inline] - pub fn outer_position(&self) -> Option { - Some((0, 0).into()) - } - - #[inline] - pub fn inner_position(&self) -> Option { - Some((0, 0).into()) - } - - #[inline] - pub fn set_outer_position(&self, _: LogicalPosition) {} - - #[inline] - pub fn inner_size(&self) -> Option { - unsafe { - let mut width = 0; - let mut height = 0; - let mut fullscreen = 0; - - if ffi::emscripten_get_canvas_size(&mut width, &mut height, &mut fullscreen) - != ffi::EMSCRIPTEN_RESULT_SUCCESS - { - None - } else { - let dpi_factor = self.hidpi_factor(); - let logical = LogicalSize::from_physical((width as u32, height as u32), dpi_factor); - Some(logical) - } - } - } - - #[inline] - pub fn outer_size(&self) -> Option { - self.inner_size() - } - - #[inline] - pub fn set_inner_size(&self, size: LogicalSize) { - unsafe { - let dpi_factor = self.hidpi_factor(); - let physical = PhysicalSize::from_logical(size, dpi_factor); - let (width, height): (u32, u32) = physical.into(); - ffi::emscripten_set_element_css_size( - ptr::null(), - width as c_double, - height as c_double, - ); - } - } - - #[inline] - pub fn set_min_inner_size(&self, _dimensions: Option) { - // N/A - } - - #[inline] - pub fn set_max_inner_size(&self, _dimensions: Option) { - // N/A - } - - #[inline] - pub fn set_resizable(&self, _resizable: bool) { - // N/A - } - - #[inline] - pub fn show(&self) { - // N/A - } - - #[inline] - pub fn hide(&self) { - // N/A - } - - #[inline] - pub fn set_cursor_icon(&self, _cursor: ::CursorIcon) { - // N/A - } - - #[inline] - pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - let mut grabbed_lock = self.window.cursor_grabbed.lock().unwrap(); - if grab == *grabbed_lock { - return Ok(()); - } - unsafe { - if grab { - em_try(ffi::emscripten_set_pointerlockchange_callback( - ptr::null(), - 0 as *mut c_void, - ffi::EM_FALSE, - Some(pointerlockchange_callback), - ))?; - em_try(ffi::emscripten_request_pointerlock( - ptr::null(), - ffi::EM_TRUE, - ))?; - } else { - em_try(ffi::emscripten_set_pointerlockchange_callback( - ptr::null(), - 0 as *mut c_void, - ffi::EM_FALSE, - None, - ))?; - em_try(ffi::emscripten_exit_pointerlock())?; - } - } - *grabbed_lock = grab; - Ok(()) - } - - #[inline] - pub fn set_cursor_visible(&self, visible: bool) { - let mut visible_lock = self.window.cursor_visible.lock().unwrap(); - if visible == *visible_lock { - return; - } - if visible { - show_mouse(); - } else { - unsafe { ffi::emscripten_hide_mouse() }; - } - *visible_lock = visible; - } - - #[inline] - pub fn hidpi_factor(&self) -> f64 { - hidpi_factor() - } - - #[inline] - pub fn set_cursor_position(&self, _position: LogicalPosition) -> Result<(), ExternalError> { - Err("Setting cursor position is not possible on Emscripten.".to_owned()) - } - - #[inline] - pub fn set_maximized(&self, _maximized: bool) { - // iOS has single screen maximized apps so nothing to do - } - - #[inline] - pub fn fullscreen(&self) -> Option<::MonitorHandle> { - None - } - - #[inline] - pub fn set_fullscreen(&self, _monitor: Option<::MonitorHandle>) { - // iOS has single screen maximized apps so nothing to do - } - - #[inline] - pub fn set_decorations(&self, _decorations: bool) { - // N/A - } - - #[inline] - pub fn set_always_on_top(&self, _always_on_top: bool) { - // N/A - } - - #[inline] - pub fn set_window_icon(&self, _icon: Option<::Icon>) { - // N/A - } - - #[inline] - pub fn set_ime_position(&self, _logical_spot: LogicalPosition) { - // N/A - } - - #[inline] - pub fn current_monitor(&self) -> RootMonitorHandle { - RootMonitorHandle { - inner: MonitorHandle, - } - } - - #[inline] - pub fn available_monitors(&self) -> VecDeque { - let mut list = VecDeque::with_capacity(1); - list.push_back(MonitorHandle); - list - } - - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - MonitorHandle - } -} - -impl Drop for Window { - fn drop(&mut self) { - // Delete window from event_loop - // TODO: ? - /*if let Some(ev) = self.event_loop.upgrade() { - let _ = ev.window.lock().unwrap().take().unwrap(); - }*/ - - unsafe { - // Return back to normal cursor state - self.hide_cursor(false); - self.set_cursor_grab(false); - - // Exit fullscreen if on - if self.window.is_fullscreen { - ffi::emscripten_set_fullscreenchange_callback( - ptr::null(), - 0 as *mut c_void, - ffi::EM_FALSE, - None, - ); - ffi::emscripten_exit_fullscreen(); - } - - // Delete callbacks - ffi::emscripten_set_keydown_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - 0 as *mut c_void, - ffi::EM_FALSE, - None, - ); - ffi::emscripten_set_keyup_callback( - DOCUMENT_NAME.as_ptr() as *const c_char, - 0 as *mut c_void, - ffi::EM_FALSE, - None, - ); - } - } -} - -fn error_to_str(code: ffi::EMSCRIPTEN_RESULT) -> &'static str { - match code { - ffi::EMSCRIPTEN_RESULT_SUCCESS | ffi::EMSCRIPTEN_RESULT_DEFERRED => { - "Internal error in the library (success detected as failure)" - } - - ffi::EMSCRIPTEN_RESULT_NOT_SUPPORTED => "Not supported", - ffi::EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED => "Failed not deferred", - ffi::EMSCRIPTEN_RESULT_INVALID_TARGET => "Invalid target", - ffi::EMSCRIPTEN_RESULT_UNKNOWN_TARGET => "Unknown target", - ffi::EMSCRIPTEN_RESULT_INVALID_PARAM => "Invalid parameter", - ffi::EMSCRIPTEN_RESULT_FAILED => "Failed", - ffi::EMSCRIPTEN_RESULT_NO_DATA => "No data", - - _ => "Undocumented error", - } -} - -fn key_translate(input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES]) -> u8 { - let slice = &input[0..input.iter().take_while(|x| **x != 0).count()]; - let maybe_key = unsafe { str::from_utf8(mem::transmute::<_, &[u8]>(slice)) }; - let key = match maybe_key { - Ok(key) => key, - Err(_) => { - return 0; - } - }; - if key.chars().count() == 1 { - key.as_bytes()[0] - } else { - 0 - } -} - -fn key_translate_virt( - input: [ffi::EM_UTF8; ffi::EM_HTML5_SHORT_STRING_LEN_BYTES], - location: c_ulong, -) -> Option<::VirtualKeyCode> { - let slice = &input[0..input.iter().take_while(|x| **x != 0).count()]; - let maybe_key = unsafe { str::from_utf8(mem::transmute::<_, &[u8]>(slice)) }; - let key = match maybe_key { - Ok(key) => key, - Err(_) => { - return None; - } - }; - use VirtualKeyCode::*; - match key { - "Alt" => match location { - ffi::DOM_KEY_LOCATION_LEFT => Some(LAlt), - ffi::DOM_KEY_LOCATION_RIGHT => Some(RAlt), - _ => None, - }, - "AltGraph" => None, - "CapsLock" => None, - "Control" => match location { - ffi::DOM_KEY_LOCATION_LEFT => Some(LControl), - ffi::DOM_KEY_LOCATION_RIGHT => Some(RControl), - _ => None, - }, - "Fn" => None, - "FnLock" => None, - "Hyper" => None, - "Meta" => None, - "NumLock" => Some(Numlock), - "ScrollLock" => Some(Scroll), - "Shift" => match location { - ffi::DOM_KEY_LOCATION_LEFT => Some(LShift), - ffi::DOM_KEY_LOCATION_RIGHT => Some(RShift), - _ => None, - }, - "Super" => None, - "Symbol" => None, - "SymbolLock" => None, - - "Enter" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(NumpadEnter), - _ => Some(Return), - }, - "Tab" => Some(Tab), - " " => Some(Space), - - "ArrowDown" => Some(Down), - "ArrowLeft" => Some(Left), - "ArrowRight" => Some(Right), - "ArrowUp" => Some(Up), - "End" => None, - "Home" => None, - "PageDown" => None, - "PageUp" => None, - - "Backspace" => Some(Back), - "Clear" => None, - "Copy" => None, - "CrSel" => None, - "Cut" => None, - "Delete" => None, - "EraseEof" => None, - "ExSel" => None, - "Insert" => Some(Insert), - "Paste" => None, - "Redo" => None, - "Undo" => None, - - "Accept" => None, - "Again" => None, - "Attn" => None, - "Cancel" => None, - "ContextMenu" => None, - "Escape" => Some(Escape), - "Execute" => None, - "Find" => None, - "Finish" => None, - "Help" => None, - "Pause" => Some(Pause), - "Play" => None, - "Props" => None, - "Select" => None, - "ZoomIn" => None, - "ZoomOut" => None, - - "BrightnessDown" => None, - "BrightnessUp" => None, - "Eject" => None, - "LogOff" => None, - "Power" => Some(Power), - "PowerOff" => None, - "PrintScreen" => Some(Snapshot), - "Hibernate" => None, - "Standby" => Some(Sleep), - "WakeUp" => Some(Wake), - - "AllCandidates" => None, - "Alphanumeric" => None, - "CodeInput" => None, - "Compose" => Some(Compose), - "Convert" => Some(Convert), - "Dead" => None, - "FinalMode" => None, - "GroupFirst" => None, - "GroupLast" => None, - "GroupNext" => None, - "GroupPrevious" => None, - "ModeChange" => None, - "NextCandidate" => None, - "NonConvert" => None, - "PreviousCandidate" => None, - "Process" => None, - "SingleCandidate" => None, - - "HangulMode" => None, - "HanjaMode" => None, - "JunjaMode" => None, - - "Eisu" => None, - "Hankaku" => None, - "Hiragana" => None, - "HiraganaKatakana" => None, - "KanaMode" => Some(Kana), - "KanjiMode" => Some(Kanji), - "Romaji" => None, - "Zenkaku" => None, - "ZenkakuHanaku" => None, - - "F1" => Some(F1), - "F2" => Some(F2), - "F3" => Some(F3), - "F4" => Some(F4), - "F5" => Some(F5), - "F6" => Some(F6), - "F7" => Some(F7), - "F8" => Some(F8), - "F9" => Some(F9), - "F10" => Some(F10), - "F11" => Some(F11), - "F12" => Some(F12), - "F13" => Some(F13), - "F14" => Some(F14), - "F15" => Some(F15), - "F16" => Some(F16), - "F17" => Some(F17), - "F18" => Some(F18), - "F19" => Some(F19), - "F20" => Some(F20), - "F21" => Some(F21), - "F22" => Some(F22), - "F23" => Some(F23), - "F24" => Some(F24), - "Soft1" => None, - "Soft2" => None, - "Soft3" => None, - "Soft4" => None, - - "AppSwitch" => None, - "Call" => None, - "Camera" => None, - "CameraFocus" => None, - "EndCall" => None, - "GoBack" => None, - "GoHome" => None, - "HeadsetHook" => None, - "LastNumberRedial" => None, - "Notification" => None, - "MannerMode" => None, - "VoiceDial" => None, - - "ChannelDown" => None, - "ChannelUp" => None, - "MediaFastForward" => None, - "MediaPause" => None, - "MediaPlay" => None, - "MediaPlayPause" => Some(PlayPause), - "MediaRecord" => None, - "MediaRewind" => None, - "MediaStop" => Some(MediaStop), - "MediaTrackNext" => Some(NextTrack), - "MediaTrackPrevious" => Some(PrevTrack), - - "AudioBalanceLeft" => None, - "AudioBalanceRight" => None, - "AudioBassDown" => None, - "AudioBassBoostDown" => None, - "AudioBassBoostToggle" => None, - "AudioBassBoostUp" => None, - "AudioBassUp" => None, - "AudioFaderFront" => None, - "AudioFaderRear" => None, - "AudioSurroundModeNext" => None, - "AudioTrebleDown" => None, - "AudioTrebleUp" => None, - "AudioVolumeDown" => Some(VolumeDown), - "AudioVolumeMute" => Some(Mute), - "AudioVolumeUp" => Some(VolumeUp), - "MicrophoneToggle" => None, - "MicrophoneVolumeDown" => None, - "MicrophoneVolumeMute" => None, - "MicrophoneVolumeUp" => None, - - "TV" => None, - "TV3DMode" => None, - "TVAntennaCable" => None, - "TVAudioDescription" => None, - "TVAudioDescriptionMixDown" => None, - "TVAudioDescriptionMixUp" => None, - "TVContentsMenu" => None, - "TVDataService" => None, - "TVInput" => None, - "TVInputComponent1" => None, - "TVInputComponent2" => None, - "TVInputComposite1" => None, - "TVInputComposite2" => None, - "TVInputHDM1" => None, - "TVInputHDM2" => None, - "TVInputHDM3" => None, - "TVInputHDM4" => None, - "TVInputVGA1" => None, - "TVMediaContext" => None, - "TVNetwork" => None, - "TVNumberEntry" => None, - "TVPower" => None, - "TVRadioService" => None, - "TVSatellite" => None, - "TVSatelliteBS" => None, - "TVSatelliteCS" => None, - "TVSatelliteToggle" => None, - "TVTerrestrialAnalog" => None, - "TVTerrestrialDigital" => None, - "TVTimer" => None, - - "AVRInput" => None, - "AVRPower" => None, - "ColorF0Red" => None, - "ColorF1Green" => None, - "ColorF2Yellow" => None, - "ColorF3Blue" => None, - "ColorF4Grey" => None, - "ColorF5Brown" => None, - "ClosedCaptionToggle" => None, - "Dimmer" => None, - "DisplaySwap" => None, - "DVR" => None, - "Exit" => None, - "FavoriteClear0" => None, - "FavoriteClear1" => None, - "FavoriteClear2" => None, - "FavoriteClear3" => None, - "FavoriteRecall0" => None, - "FavoriteRecall1" => None, - "FavoriteRecall2" => None, - "FavoriteRecall3" => None, - "FavoriteStore0" => None, - "FavoriteStore1" => None, - "FavoriteStore2" => None, - "FavoriteStore3" => None, - "FavoriteStore4" => None, - "Guide" => None, - "GuideNextDay" => None, - "GuidePreviousDay" => None, - "Info" => None, - "InstantReplay" => None, - "Link" => None, - "ListProgram" => None, - "LiveContent" => None, - "Lock" => None, - "MediaApps" => None, - "MediaAudioTrack" => None, - "MediaLast" => None, - "MediaSkipBackward" => None, - "MediaSkipForward" => None, - "MediaStepBackward" => None, - "MediaStepForward" => None, - "MediaTopMenu" => None, - "NavigateIn" => None, - "NavigateNext" => None, - "NavigateOut" => None, - "NavigatePrevious" => None, - "NextFavoriteChannel" => None, - "NextUserProfile" => None, - "OnDemand" => None, - "Pairing" => None, - "PinPDown" => None, - "PinPMove" => None, - "PinPToggle" => None, - "PinPUp" => None, - "PlaySpeedDown" => None, - "PlaySpeedReset" => None, - "PlaySpeedUp" => None, - "RandomToggle" => None, - "RcLowBattery" => None, - "RecordSpeedNext" => None, - "RfBypass" => None, - "ScanChannelsToggle" => None, - "ScreenModeNext" => None, - "Settings" => None, - "SplitScreenToggle" => None, - "STBInput" => None, - "STBPower" => None, - "Subtitle" => None, - "Teletext" => None, - "VideoModeNext" => None, - "Wink" => None, - "ZoomToggle" => None, - - "SpeechCorrectionList" => None, - "SpeechInputToggle" => None, - - "Close" => None, - "New" => None, - "Open" => None, - "Print" => None, - "Save" => None, - "SpellCheck" => None, - "MailForward" => None, - "MailReply" => None, - "MailSend" => None, - - "LaunchCalculator" => Some(Calculator), - "LaunchCalendar" => None, - "LaunchContacts" => None, - "LaunchMail" => Some(Mail), - "LaunchMediaPlayer" => None, - "LaunchMusicPlayer" => None, - "LaunchMyComputer" => Some(MyComputer), - "LaunchPhone" => None, - "LaunchScreenSaver" => None, - "LaunchSpreadsheet" => None, - "LaunchWebCam" => None, - "LaunchWordProcessor" => None, - "LaunchApplication1" => None, - "LaunchApplication2" => None, - "LaunchApplication3" => None, - "LaunchApplication4" => None, - "LaunchApplication5" => None, - "LaunchApplication6" => None, - "LaunchApplication7" => None, - "LaunchApplication8" => None, - "LaunchApplication9" => None, - "LaunchApplication10" => None, - "LaunchApplication11" => None, - "LaunchApplication12" => None, - "LaunchApplication13" => None, - "LaunchApplication14" => None, - "LaunchApplication15" => None, - "LaunchApplication16" => None, - - "BrowserBack" => Some(WebBack), - "BrowserFavorites" => Some(WebFavorites), - "BrowserForward" => Some(WebForward), - "BrowserHome" => Some(WebHome), - "BrowserRefresh" => Some(WebRefresh), - "BrowserSearch" => Some(WebSearch), - "BrowserStop" => Some(WebStop), - - "Decimal" => Some(Decimal), - "Key11" => None, - "Key12" => None, - "Multiply" | "*" => Some(Multiply), - "Add" | "+" => Some(Add), - // "Clear" => None, - "Divide" => Some(Divide), - "Subtract" | "-" => Some(Subtract), - "Separator" => None, - "0" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad0), - _ => Some(Key0), - }, - "1" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad1), - _ => Some(Key1), - }, - "2" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad2), - _ => Some(Key2), - }, - "3" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad3), - _ => Some(Key3), - }, - "4" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad4), - _ => Some(Key4), - }, - "5" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad5), - _ => Some(Key5), - }, - "6" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad6), - _ => Some(Key6), - }, - "7" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad7), - _ => Some(Key7), - }, - "8" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad8), - _ => Some(Key8), - }, - "9" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(Numpad9), - _ => Some(Key9), - }, - - "A" | "a" => Some(A), - "B" | "b" => Some(B), - "C" | "c" => Some(C), - "D" | "d" => Some(D), - "E" | "e" => Some(E), - "F" | "f" => Some(F), - "G" | "g" => Some(G), - "H" | "h" => Some(H), - "I" | "i" => Some(I), - "J" | "j" => Some(J), - "K" | "k" => Some(K), - "L" | "l" => Some(L), - "M" | "m" => Some(M), - "N" | "n" => Some(N), - "O" | "o" => Some(O), - "P" | "p" => Some(P), - "Q" | "q" => Some(Q), - "R" | "r" => Some(R), - "S" | "s" => Some(S), - "T" | "t" => Some(T), - "U" | "u" => Some(U), - "V" | "v" => Some(V), - "W" | "w" => Some(W), - "X" | "x" => Some(X), - "Y" | "y" => Some(Y), - "Z" | "z" => Some(Z), - - "'" => Some(Apostrophe), - "\\" => Some(Backslash), - ":" => Some(Colon), - "," => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(NumpadComma), - _ => Some(Comma), - }, - "=" => match location { - ffi::DOM_KEY_LOCATION_NUMPAD => Some(NumpadEquals), - _ => Some(Equals), - }, - "{" => Some(LBracket), - "." => Some(Period), - "}" => Some(RBracket), - ";" => Some(Semicolon), - "/" => Some(Slash), - - _ => None, - } -} diff --git a/src/platform_impl/mod.rs b/src/platform_impl/mod.rs index 3b815208..bc8fc2c4 100644 --- a/src/platform_impl/mod.rs +++ b/src/platform_impl/mod.rs @@ -21,9 +21,6 @@ mod platform; #[cfg(target_os = "ios")] #[path = "ios/mod.rs"] mod platform; -#[cfg(target_os = "emscripten")] -#[path = "emscripten/mod.rs"] -mod platform; #[cfg(all( not(target_os = "ios"), @@ -35,6 +32,5 @@ mod platform; not(target_os = "freebsd"), not(target_os = "netbsd"), not(target_os = "openbsd"), - not(target_os = "emscripten") ))] compile_error!("The platform you're compiling for is not supported by winit"); From 28a5feef28ba143a043aa1944ad05d615c63a82d Mon Sep 17 00:00:00 2001 From: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Date: Mon, 16 Sep 2019 02:59:37 +0300 Subject: [PATCH 51/61] Fix freeze upon exiting exclusive fullscreen on macOS 10.15 (#1127) --- CHANGELOG.md | 1 + src/platform_impl/macos/util/async.rs | 15 +++++ src/platform_impl/macos/window.rs | 84 ++++++++++++--------------- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de34b407..24b6f47e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`. - On Wayland, fix event processing sometimes stalling when using OpenGL with vsync. - Officially remove the Emscripten backend +- On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index d7fe9592..ad1a891b 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -231,6 +231,21 @@ pub unsafe fn toggle_full_screen_async( ); } +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, diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 75adf8a3..4c9e3f9b 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -617,25 +617,6 @@ impl UnownedWindow { self.set_maximized(maximized); } - fn restore_display_mode(&self) { - trace!("Locked shared state in `restore_display_mode`"); - let shared_state_lock = self.shared_state.lock().unwrap(); - - if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) = - shared_state_lock.fullscreen - { - unsafe { - ffi::CGRestorePermanentDisplayConfiguration(); - assert_eq!( - ffi::CGDisplayRelease(video_mode.monitor().inner.native_identifier()), - ffi::kCGErrorSuccess - ); - } - } - - trace!("Unlocked shared state in `restore_display_mode`"); - } - #[inline] pub fn set_maximized(&self, maximized: bool) { let is_zoomed = self.is_zoomed(); @@ -763,7 +744,39 @@ impl UnownedWindow { } } + trace!("Locked shared state in `set_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.fullscreen = fullscreen.clone(); + trace!("Unlocked shared state in `set_fullscreen`"); + match (&old_fullscreen, &fullscreen) { + (&None, &Some(_)) => unsafe { + util::toggle_full_screen_async( + *self.ns_window, + *self.ns_view, + old_fullscreen.is_none(), + Arc::downgrade(&self.shared_state), + ); + }, + (&Some(Fullscreen::Borderless(_)), &None) => unsafe { + // State is restored by `window_did_exit_fullscreen` + util::toggle_full_screen_async( + *self.ns_window, + *self.ns_view, + old_fullscreen.is_none(), + Arc::downgrade(&self.shared_state), + ); + }, + (&Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), &None) => unsafe { + util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); + // Rest of the state is restored by `window_did_exit_fullscreen` + util::toggle_full_screen_async( + *self.ns_window, + *self.ns_view, + old_fullscreen.is_none(), + Arc::downgrade(&self.shared_state), + ); + }, (&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe { // If we're already in fullscreen mode, calling // `CGDisplayCapture` will place the shielding window on top of @@ -775,37 +788,14 @@ impl UnownedWindow { // delegate in `window:willUseFullScreenPresentationOptions:`. msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1]; }, - (&Some(Fullscreen::Exclusive(_)), &None) => unsafe { - self.restore_display_mode(); - - util::toggle_full_screen_async( - *self.ns_window, - *self.ns_view, - old_fullscreen.is_none(), - Arc::downgrade(&self.shared_state), - ); - }, - (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { - self.restore_display_mode(); - } - (&None, &Some(Fullscreen::Exclusive(_))) - | (&None, &Some(Fullscreen::Borderless(_))) - | (&Some(Fullscreen::Borderless(_)), &None) => unsafe { - // Wish it were this simple for all cases - util::toggle_full_screen_async( - *self.ns_window, - *self.ns_view, - old_fullscreen.is_none(), - Arc::downgrade(&self.shared_state), - ); + ( + &Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })), + &Some(Fullscreen::Borderless(_)), + ) => unsafe { + util::restore_display_mode_async(video_mode.monitor().inner.native_identifier()); }, _ => (), } - - trace!("Locked shared state in `set_fullscreen`"); - let mut shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.fullscreen = fullscreen.clone(); - trace!("Unlocked shared state in `set_fullscreen`"); } #[inline] From c03ef852a4d77158ecfe73aa122723c252819ce4 Mon Sep 17 00:00:00 2001 From: Osspial Date: Sun, 15 Sep 2019 22:09:08 -0400 Subject: [PATCH 52/61] Fix surrogate pair handling on Windows (#1165) * Fix surrogate pair handling on Windows * Change high_surrogate to Option * Format --- CHANGELOG.md | 3 ++- src/platform_impl/windows/event_loop.rs | 28 +++++++++++++++++++---- src/platform_impl/windows/window_state.rs | 2 ++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b6f47e..f4cd72ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ - On Windows, add touch pressure information for touch events. - On macOS, differentiate between `CursorIcon::Grab` and `CursorIcon::Grabbing`. - On Wayland, fix event processing sometimes stalling when using OpenGL with vsync. -- Officially remove the Emscripten backend +- Officially remove the Emscripten backend. +- On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 57e51b21..f6e3f503 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1031,11 +1031,29 @@ unsafe extern "system" fn public_window_callback( winuser::WM_CHAR => { use crate::event::WindowEvent::ReceivedCharacter; - let chr: char = mem::transmute(wparam as u32); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ReceivedCharacter(chr), - }); + let high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF; + let low_surrogate = 0xDC00 <= wparam && wparam <= 0xDFFF; + + if high_surrogate { + subclass_input.window_state.lock().high_surrogate = Some(wparam as u16); + } else if low_surrogate { + let mut window_state = subclass_input.window_state.lock(); + if let Some(high_surrogate) = window_state.high_surrogate.take() { + let pair = [high_surrogate, wparam as u16]; + if let Some(Ok(chr)) = std::char::decode_utf16(pair.iter().copied()).next() { + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ReceivedCharacter(chr), + }); + } + } + } else { + let chr: char = mem::transmute(wparam as u32); + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ReceivedCharacter(chr), + }); + } 0 } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 5979f777..51bf45b8 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 high_surrogate: Option, window_flags: WindowFlags, } @@ -114,6 +115,7 @@ impl WindowState { fullscreen: None, queued_out_of_band_redraw: false, + high_surrogate: None, window_flags: WindowFlags::empty(), } } From 3716f13d8e1c2929eff3478c3c46a50e9fa68e97 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 16 Sep 2019 14:26:56 -0400 Subject: [PATCH 53/61] Flush high surrogate if not followed by low surrogate (#1166) * Flush high surrogate if not followed by low surrogate * Remove transmute from WM_CHAR handler * Fix window_state being locked while dispatching ReceivedCharacter for surrogate codepoints. * Format --- src/platform_impl/windows/event_loop.rs | 29 +++++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index f6e3f503..94f7255e 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1031,16 +1031,18 @@ unsafe extern "system" fn public_window_callback( winuser::WM_CHAR => { use crate::event::WindowEvent::ReceivedCharacter; - let high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF; - let low_surrogate = 0xDC00 <= wparam && wparam <= 0xDFFF; + use std::char; + let is_high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF; + let is_low_surrogate = 0xDC00 <= wparam && wparam <= 0xDFFF; - if high_surrogate { + if is_high_surrogate { subclass_input.window_state.lock().high_surrogate = Some(wparam as u16); - } else if low_surrogate { - let mut window_state = subclass_input.window_state.lock(); - if let Some(high_surrogate) = window_state.high_surrogate.take() { + } else if is_low_surrogate { + let high_surrogate = subclass_input.window_state.lock().high_surrogate.take(); + + if let Some(high_surrogate) = high_surrogate { let pair = [high_surrogate, wparam as u16]; - if let Some(Ok(chr)) = std::char::decode_utf16(pair.iter().copied()).next() { + if let Some(Ok(chr)) = char::decode_utf16(pair.iter().copied()).next() { subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: ReceivedCharacter(chr), @@ -1048,11 +1050,14 @@ unsafe extern "system" fn public_window_callback( } } } else { - let chr: char = mem::transmute(wparam as u32); - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ReceivedCharacter(chr), - }); + subclass_input.window_state.lock().high_surrogate = None; + + if let Some(chr) = char::from_u32(wparam as u32) { + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ReceivedCharacter(chr), + }); + } } 0 } From 95581ab92f8bd22d444a4ad32c5bf1cc024663d0 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Date: Mon, 16 Sep 2019 21:27:46 +0300 Subject: [PATCH 54/61] Fix null window on initial `HiDpiFactorChanged` event on iOS (#1167) --- CHANGELOG.md | 1 + src/platform_impl/ios/view.rs | 24 ++++++++++++++---------- src/platform_impl/ios/window.rs | 29 ++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4cd72ed..173f17c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Officially remove the Emscripten backend. - On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. +- On iOS, fix null window on initial `HiDpiFactorChanged` event. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 8c6ba1e7..5868059b 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -101,6 +101,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) { 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, @@ -116,6 +117,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { let () = msg_send![super(object, superclass), layoutSubviews]; let window: id = msg_send![object, window]; + assert!(!window.is_null()); let bounds: CGRect = msg_send![window, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; @@ -144,23 +146,24 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { setContentScaleFactor: untrusted_hidpi_factor ]; - // On launch, iOS sets the contentScaleFactor to 0.0. This is a sentinel value that - // iOS appears to use to "reset" the contentScaleFactor to the device specific - // default value. - // - // The workaround is to not trust the value received by this function, and always - // go through the getter. + let window: id = msg_send![object, window]; + // `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow + // makeKeyAndVisible]` at window creation time (either manually or internally by + // UIKit when the `UIView` is first created), in which case we send no events here + if window.is_null() { + return; + } + // `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]; assert!( !hidpi_factor.is_nan() && hidpi_factor.is_finite() && hidpi_factor.is_sign_positive() && hidpi_factor > 0.0, - "invalid hidpi_factor set on UIWindow", + "invalid hidpi_factor set on UIView", ); - - let window: id = msg_send![object, window]; - let bounds: CGRect = msg_send![object, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; @@ -186,6 +189,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) { unsafe { let window: id = msg_send![object, window]; + assert!(!window.is_null()); let uiscreen: id = msg_send![window, screen]; let touches_enum: id = msg_send![touches, objectEnumerator]; let mut touch_events = Vec::new(); diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 0b609c60..eea0c304 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -9,6 +9,7 @@ use objc::runtime::{Class, Object, BOOL, NO, YES}; use crate::{ dpi::{self, LogicalPosition, LogicalSize}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, + event::{Event, WindowEvent}, icon::Icon, monitor::MonitorHandle as RootMonitorHandle, platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations}, @@ -20,7 +21,7 @@ use crate::{ }, monitor, view, EventLoopWindowTarget, MonitorHandle, }, - window::{CursorIcon, Fullscreen, WindowAttributes}, + window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId}, }; pub struct Inner { @@ -377,6 +378,32 @@ impl Window { }, }; app_state::set_key_window(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]; + if hidpi_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]; + let screen_frame: CGRect = + msg_send![view, convertRect:bounds toCoordinateSpace:screen_space]; + let size = crate::dpi::LogicalSize { + width: screen_frame.size.width as _, + 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), + })), + ); + } + Ok(result) } } From d35ee0d58081576339e448c8b56ffbe8990761ef Mon Sep 17 00:00:00 2001 From: Osspial Date: Tue, 17 Sep 2019 11:34:48 -0400 Subject: [PATCH 55/61] Fix hovering the mouse over the active window creating an endless stream of CursorMoved events (#1170) * Fix hovering the mouse over the active window creating an endless stream of CursorMoved events * Format --- CHANGELOG.md | 1 + src/platform_impl/windows/util.rs | 23 +++++++++++++++++++++++ src/platform_impl/windows/window_state.rs | 23 +++++++++++++++++++---- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 173f17c5..1fad9364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. - On iOS, fix null window on initial `HiDpiFactorChanged` event. +- On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 4b82cefa..96347849 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -141,6 +141,16 @@ pub fn set_cursor_hidden(hidden: bool) { } } +pub fn get_cursor_clip() -> Result { + unsafe { + let mut rect: RECT = mem::zeroed(); + win_to_err(|| winuser::GetClipCursor(&mut rect)).map(|_| rect) + } +} + +/// Sets the cursor's clip rect. +/// +/// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event. pub fn set_cursor_clip(rect: Option) -> Result<(), io::Error> { unsafe { let rect_ptr = rect @@ -151,6 +161,19 @@ pub fn set_cursor_clip(rect: Option) -> Result<(), io::Error> { } } +pub fn get_desktop_rect() -> RECT { + unsafe { + let left = winuser::GetSystemMetrics(winuser::SM_XVIRTUALSCREEN); + let top = winuser::GetSystemMetrics(winuser::SM_YVIRTUALSCREEN); + RECT { + left, + top, + right: left + winuser::GetSystemMetrics(winuser::SM_CXVIRTUALSCREEN), + bottom: top + winuser::GetSystemMetrics(winuser::SM_CYVIRTUALSCREEN), + } + } +} + pub fn is_focused(window: HWND) -> bool { window == unsafe { winuser::GetActiveWindow() } } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 51bf45b8..105d71e6 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -307,10 +307,25 @@ impl CursorFlags { let client_rect = util::get_client_rect(window)?; if util::is_focused(window) { - if self.contains(CursorFlags::GRABBED) { - util::set_cursor_clip(Some(client_rect))?; - } else { - util::set_cursor_clip(None)?; + let cursor_clip = match self.contains(CursorFlags::GRABBED) { + true => Some(client_rect), + false => None, + }; + + let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom); + let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?); + let desktop_rect = rect_to_tuple(util::get_desktop_rect()); + + let active_cursor_clip = match desktop_rect == active_cursor_clip { + true => None, + false => Some(active_cursor_clip), + }; + + // We do this check because calling `set_cursor_clip` incessantly will flood the event + // loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by `set_cursor_flags` + // which at times gets called once every iteration of the eventloop. + if active_cursor_clip != cursor_clip.map(rect_to_tuple) { + util::set_cursor_clip(cursor_clip)?; } } From 695547f4ca6ab6dc032ccce1a9fed2baa10e0c01 Mon Sep 17 00:00:00 2001 From: Aleksi Juvani <3168386+aleksijuvani@users.noreply.github.com> Date: Wed, 18 Sep 2019 05:49:29 +0300 Subject: [PATCH 56/61] Fix events not being emitted during modal loops on macOS (#1173) --- CHANGELOG.md | 2 ++ src/platform_impl/macos/app_state.rs | 23 ---------------------- src/platform_impl/macos/observer.rs | 3 +-- src/platform_impl/macos/window_delegate.rs | 6 +----- 4 files changed, 4 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fad9364..bfba47c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. - On iOS, fix null window on initial `HiDpiFactorChanged` event. +- On macOS, fix events not being emitted during modal loops, such as when windows are being resized + by the user. - On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 4a18ed83..d063fe84 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -84,7 +84,6 @@ struct Handler { start_time: Mutex>, callback: Mutex>>, pending_events: Mutex>>, - deferred_events: Mutex>>, pending_redraw: Mutex>, waker: Mutex, } @@ -97,10 +96,6 @@ impl Handler { self.pending_events.lock().unwrap() } - fn deferred<'a>(&'a self) -> MutexGuard<'a, VecDeque>> { - self.deferred_events.lock().unwrap() - } - fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec> { self.pending_redraw.lock().unwrap() } @@ -145,10 +140,6 @@ impl Handler { mem::replace(&mut *self.events(), Default::default()) } - fn take_deferred(&self) -> VecDeque> { - mem::replace(&mut *self.deferred(), Default::default()) - } - fn should_redraw(&self) -> Vec { mem::replace(&mut *self.redraw(), Default::default()) } @@ -264,20 +255,6 @@ impl AppState { HANDLER.events().append(&mut events); } - pub fn send_event_immediately(event: Event) { - if !unsafe { msg_send![class!(NSThread), isMainThread] } { - panic!("Event sent from different thread: {:#?}", event); - } - HANDLER.deferred().push_back(event); - if !HANDLER.get_in_callback() { - HANDLER.set_in_callback(true); - for event in HANDLER.take_deferred() { - HANDLER.handle_nonuser_event(event); - } - HANDLER.set_in_callback(false); - } - } - pub fn cleared() { if !HANDLER.is_ready() { return; diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs index 661ec82e..10e78e0e 100644 --- a/src/platform_impl/macos/observer.rs +++ b/src/platform_impl/macos/observer.rs @@ -4,7 +4,6 @@ use crate::platform_impl::platform::{app_state::AppState, ffi}; #[link(name = "CoreFoundation", kind = "framework")] extern "C" { - pub static kCFRunLoopDefaultMode: CFRunLoopMode; pub static kCFRunLoopCommonModes: CFRunLoopMode; pub fn CFRunLoopGetMain() -> CFRunLoopRef; @@ -162,7 +161,7 @@ impl RunLoop { handler, ptr::null_mut(), ); - CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode); + CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes); } } diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 9bce9ce7..041edf61 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -84,11 +84,7 @@ impl WindowDelegateState { 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 event = Event::WindowEvent { - window_id: WindowId(get_window_id(*self.ns_window)), - event: WindowEvent::Resized(size), - }; - AppState::send_event_immediately(event); + self.emit_event(WindowEvent::Resized(size)); } fn emit_move_event(&mut self) { From eb20612d7716b78df9df72966d9416131ddd863c Mon Sep 17 00:00:00 2001 From: Murarth Date: Thu, 19 Sep 2019 08:47:51 -0700 Subject: [PATCH 57/61] Prevent stealing focus on new windows (#1176) --- CHANGELOG.md | 2 + src/platform_impl/linux/x11/window.rs | 79 ++++++++++++++++++--------- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfba47c3..d1386c0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - On macOS, fix events not being emitted during modal loops, such as when windows are being resized by the user. - On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events. +- On X11, prevent stealing input focus when creating a new window. + Only steal input focus when entering fullscreen mode. # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 73e53987..078cc4d5 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -56,12 +56,14 @@ pub struct SharedState { pub frame_extents: Option, pub min_inner_size: Option, pub max_inner_size: Option, + pub is_visible: bool, } impl SharedState { - fn new(dpi_factor: f64) -> Mutex { + fn new(dpi_factor: f64, is_visible: bool) -> Mutex { let mut shared_state = SharedState::default(); shared_state.guessed_dpi = Some(dpi_factor); + shared_state.is_visible = is_visible; Mutex::new(shared_state) } } @@ -230,7 +232,7 @@ impl UnownedWindow { cursor_grabbed: Mutex::new(false), cursor_visible: Mutex::new(true), ime_sender: Mutex::new(event_loop.ime_sender.clone()), - shared_state: SharedState::new(dpi_factor), + shared_state: SharedState::new(dpi_factor, window_attrs.visible), pending_redraws: event_loop.pending_redraws.clone(), }; @@ -355,6 +357,8 @@ impl UnownedWindow { unsafe { (xconn.xlib.XMapRaised)(xconn.display, window.xwindow); } //.queue(); + + window.wait_for_visibility_notify(); } // Attempt to make keyboard input repeat detectable @@ -420,27 +424,6 @@ impl UnownedWindow { .set_always_on_top_inner(window_attrs.always_on_top) .queue(); } - - if window_attrs.visible { - unsafe { - // XSetInputFocus generates an error if the window is not visible, so we wait - // until we receive VisibilityNotify. - let mut event = MaybeUninit::uninit(); - (xconn.xlib.XIfEvent)( - // This will flush the request buffer IF it blocks. - xconn.display, - event.as_mut_ptr(), - Some(visibility_predicate), - window.xwindow as _, - ); - (xconn.xlib.XSetInputFocus)( - xconn.display, - window.xwindow, - ffi::RevertToParent, - ffi::CurrentTime, - ); - } - } } // We never want to give the user a broken window, since by then, it's too late to handle. @@ -566,11 +549,32 @@ impl UnownedWindow { fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher<'_> { let fullscreen_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") }; - self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0)) + let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0)); + + if fullscreen { + // Ensure that the fullscreen window receives input focus to prevent + // locking up the user's display. + unsafe { + (self.xconn.xlib.XSetInputFocus)( + self.xconn.display, + self.xwindow, + ffi::RevertToParent, + ffi::CurrentTime, + ); + } + } + + flusher } fn set_fullscreen_inner(&self, fullscreen: Option) -> Option> { let mut shared_state_lock = self.shared_state.lock(); + + if !shared_state_lock.is_visible { + // Setting fullscreen on a window that is not visible will generate an error. + return None; + } + let old_fullscreen = shared_state_lock.fullscreen.clone(); if old_fullscreen == fullscreen { return None; @@ -681,7 +685,7 @@ impl UnownedWindow { pub fn set_fullscreen(&self, fullscreen: Option) { if let Some(flusher) = self.set_fullscreen_inner(fullscreen) { flusher - .flush() + .sync() .expect("Failed to change window fullscreen state"); self.invalidate_cached_frame_extents(); } @@ -837,12 +841,22 @@ impl UnownedWindow { #[inline] pub fn set_visible(&self, visible: bool) { + let is_visible = self.shared_state.lock().is_visible; + + if visible == is_visible { + return; + } + match visible { true => unsafe { (self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow); self.xconn .flush_requests() .expect("Failed to call XMapRaised"); + + // Some X requests may generate an error if the window is not + // visible, so we must wait until the window becomes visible. + self.wait_for_visibility_notify(); }, false => unsafe { (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); @@ -851,6 +865,21 @@ impl UnownedWindow { .expect("Failed to call XUnmapWindow"); }, } + + self.shared_state.lock().is_visible = visible; + } + + fn wait_for_visibility_notify(&self) { + unsafe { + let mut event = MaybeUninit::uninit(); + + (self.xconn.xlib.XIfEvent)( + self.xconn.display, + event.as_mut_ptr(), + Some(visibility_predicate), + self.xwindow as _, + ); + } } fn update_cached_frame_extents(&self) { From 2ef39651eb52e9036c9fd301455411505a86f0f6 Mon Sep 17 00:00:00 2001 From: Osspial Date: Thu, 19 Sep 2019 11:48:20 -0400 Subject: [PATCH 58/61] Fix fullscreen window shrinking upon getting restored to a normal window (#1172) --- CHANGELOG.md | 1 + src/platform_impl/windows/window.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1386c0d..7b28a177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. - On iOS, fix null window on initial `HiDpiFactorChanged` event. +- On Windows, fix fullscreen window shrinking upon getting restored to a normal window. - On macOS, fix events not being emitted during modal loops, such as when windows are being resized by the user. - On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events. diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 5b55c7c8..cb32c2ac 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -599,6 +599,7 @@ impl Window { { window_state_lock.dpi_factor = dpi_factor; drop(window_state_lock); + let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap(); unsafe { winuser::SetWindowPos( From c0a79003410d55f2a25da62857ce25444f649420 Mon Sep 17 00:00:00 2001 From: Michael Palmos Date: Tue, 24 Sep 2019 00:10:33 +1000 Subject: [PATCH 59/61] Allow using multiple `XWindowType`s on X11 (#1140) (#1147) * Allow using multiple `XWindowType`s on X11 (#1140) * Update documentation to make combining window types clearer * Update build flags because X11 runs on more than just Linux * Revert "Update build flags because X11 runs on more than just Linux" This reverts commit 882b9100462a5ee0cf89dcd42891ebd0f709964f. * Revert "Update documentation to make combining window types clearer" This reverts commit da00ad391a8ce42cea08b577b216316b013f9e36. * Revert "Allow using multiple `XWindowType`s on X11 (#1140)" This reverts commit a23033345697463400286c4d297f5c1552369fc2. * Allow using multiple `XWindowType`s on X11 (slice variant) (#1140) * Multiple `XWindowType`s, with non-static lifetime. * Multiple `XWindowType`s (#1140) (`Vec` variant) * Append change to changelog. * Fix formatting. --- CHANGELOG.md | 1 + src/platform/unix.rs | 74 ++++++++++++------------ src/platform_impl/linux/mod.rs | 24 +++++++- src/platform_impl/linux/x11/util/hint.rs | 30 +++++----- src/platform_impl/linux/x11/window.rs | 14 +++-- src/window.rs | 27 ++++----- 6 files changed, 96 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b28a177..34af8f51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Officially remove the Emscripten backend. - On Windows, fix handling of surrogate pairs when dispatching `ReceivedCharacter`. - On macOS 10.15, fix freeze upon exiting exclusive fullscreen mode. +- On X11, allow setting mulitple `XWindowType`s. - On iOS, fix null window on initial `HiDpiFactorChanged` event. - On Windows, fix fullscreen window shrinking upon getting restored to a normal window. - On macOS, fix events not being emitted during modal loops, such as when windows are being resized diff --git a/src/platform/unix.rs b/src/platform/unix.rs index eb9675e4..a72b1d33 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -267,17 +267,17 @@ impl WindowExtUnix for Window { } #[inline] - fn xcb_connection(&self) -> Option<*mut raw::c_void> { - match self.window { - LinuxWindow::X(ref w) => Some(w.xcb_connection()), - _ => None, + fn set_urgent(&self, is_urgent: bool) { + if let LinuxWindow::X(ref w) = self.window { + w.set_urgent(is_urgent); } } #[inline] - fn set_urgent(&self, is_urgent: bool) { - if let LinuxWindow::X(ref w) = self.window { - w.set_urgent(is_urgent); + fn xcb_connection(&self) -> Option<*mut raw::c_void> { + match self.window { + LinuxWindow::X(ref w) => Some(w.xcb_connection()), + _ => None, } } @@ -313,82 +313,82 @@ impl WindowExtUnix for Window { /// Additional methods on `WindowBuilder` that are specific to Unix. pub trait WindowBuilderExtUnix { - fn with_x11_visual(self, visual_infos: *const T) -> WindowBuilder; - fn with_x11_screen(self, screen_id: i32) -> WindowBuilder; + fn with_x11_visual(self, visual_infos: *const T) -> Self; + 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. - fn with_class(self, class: String, instance: String) -> WindowBuilder; + fn with_class(self, class: String, instance: String) -> Self; /// Build window with override-redirect flag; defaults to false. Only relevant on X11. - fn with_override_redirect(self, override_redirect: bool) -> WindowBuilder; - /// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11. - fn with_x11_window_type(self, x11_window_type: XWindowType) -> WindowBuilder; + fn with_override_redirect(self, override_redirect: bool) -> Self; + /// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on 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. - fn with_gtk_theme_variant(self, variant: String) -> WindowBuilder; + 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) -> WindowBuilder; + 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) -> WindowBuilder; + 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. /// /// 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) - fn with_app_id(self, app_id: String) -> WindowBuilder; + fn with_app_id(self, app_id: String) -> Self; } impl WindowBuilderExtUnix for WindowBuilder { #[inline] - fn with_x11_visual(mut self, visual_infos: *const T) -> WindowBuilder { + 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 } #[inline] - fn with_x11_screen(mut self, screen_id: i32) -> WindowBuilder { + fn with_x11_screen(mut self, screen_id: i32) -> Self { self.platform_specific.screen_id = Some(screen_id); self } #[inline] - fn with_class(mut self, instance: String, class: String) -> WindowBuilder { + fn with_class(mut self, instance: String, class: String) -> Self { self.platform_specific.class = Some((instance, class)); self } #[inline] - fn with_override_redirect(mut self, override_redirect: bool) -> WindowBuilder { + fn with_override_redirect(mut self, override_redirect: bool) -> Self { self.platform_specific.override_redirect = override_redirect; self } #[inline] - fn with_x11_window_type(mut self, x11_window_type: XWindowType) -> WindowBuilder { - self.platform_specific.x11_window_type = x11_window_type; + fn with_x11_window_type(mut self, x11_window_types: Vec) -> Self { + self.platform_specific.x11_window_types = x11_window_types; self } #[inline] - fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder { - self.platform_specific.resize_increments = Some(increments.into()); - self - } - - #[inline] - fn with_base_size(mut self, base_size: LogicalSize) -> WindowBuilder { - self.platform_specific.base_size = Some(base_size.into()); - self - } - - #[inline] - fn with_gtk_theme_variant(mut self, variant: String) -> WindowBuilder { + fn with_gtk_theme_variant(mut self, variant: String) -> Self { self.platform_specific.gtk_theme_variant = Some(variant); self } #[inline] - fn with_app_id(mut self, app_id: String) -> WindowBuilder { + 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 { + self.platform_specific.base_size = Some(base_size.into()); + self + } + + #[inline] + fn with_app_id(mut self, app_id: String) -> Self { self.platform_specific.app_id = Some(app_id); self } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index bc960190..60430a2c 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -7,7 +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, XConnection, XError}; +use self::x11::{ + ffi::XVisualInfo, get_xtarget, util::WindowType as XWindowType, XConnection, XError, +}; use crate::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, @@ -30,7 +32,7 @@ pub mod x11; /// If this variable is set with any other value, winit will panic. const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; -#[derive(Clone, Default)] +#[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub visual_infos: Option, pub screen_id: Option, @@ -38,11 +40,27 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub base_size: Option<(u32, u32)>, pub class: Option<(String, String)>, pub override_redirect: bool, - pub x11_window_type: x11::util::WindowType, + pub x11_window_types: Vec, pub gtk_theme_variant: Option, pub app_id: Option, } +impl Default for PlatformSpecificWindowBuilderAttributes { + fn default() -> Self { + Self { + visual_infos: None, + screen_id: None, + resize_increments: None, + base_size: None, + class: None, + override_redirect: false, + x11_window_types: vec![XWindowType::Normal], + gtk_theme_variant: None, + app_id: None, + } + } +} + lazy_static! { pub static ref X11_BACKEND: Mutex, XNotSupported>> = { Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) }; diff --git a/src/platform_impl/linux/x11/util/hint.rs b/src/platform_impl/linux/x11/util/hint.rs index 28086460..94c7e33a 100644 --- a/src/platform_impl/linux/x11/util/hint.rs +++ b/src/platform_impl/linux/x11/util/hint.rs @@ -72,21 +72,21 @@ impl Default for WindowType { impl WindowType { pub(crate) fn as_atom(&self, xconn: &Arc) -> ffi::Atom { use self::WindowType::*; - let atom_name: &[u8] = match self { - &Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0", - &Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0", - &Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0", - &Menu => b"_NET_WM_WINDOW_TYPE_MENU\0", - &Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0", - &Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0", - &Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0", - &DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0", - &PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0", - &Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0", - &Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0", - &Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0", - &Dnd => b"_NET_WM_WINDOW_TYPE_DND\0", - &Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0", + let atom_name: &[u8] = match *self { + Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0", + Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0", + Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0", + Menu => b"_NET_WM_WINDOW_TYPE_MENU\0", + Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0", + Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0", + Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0", + DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0", + PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0", + Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0", + Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0", + Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0", + Dnd => b"_NET_WM_WINDOW_TYPE_DND\0", + Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0", }; unsafe { xconn.get_atom_unchecked(atom_name) } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 078cc4d5..335e529f 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -298,9 +298,7 @@ impl UnownedWindow { window.set_pid().map(|flusher| flusher.queue()); - if pl_attribs.x11_window_type != Default::default() { - window.set_window_type(pl_attribs.x11_window_type).queue(); - } + window.set_window_types(pl_attribs.x11_window_types).queue(); if let Some(variant) = pl_attribs.gtk_theme_variant { window.set_gtk_theme_variant(variant).queue(); @@ -483,15 +481,19 @@ impl UnownedWindow { } } - fn set_window_type(&self, window_type: util::WindowType) -> util::Flusher<'_> { + fn set_window_types(&self, window_types: Vec) -> util::Flusher<'_> { let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") }; - let window_type_atom = window_type.as_atom(&self.xconn); + let atoms: Vec<_> = window_types + .iter() + .map(|t| t.as_atom(&self.xconn)) + .collect(); + self.xconn.change_property( self.xwindow, hint_atom, ffi::XA_ATOM, util::PropMode::Replace, - &[window_type_atom], + &atoms, ) } diff --git a/src/window.rs b/src/window.rs index 481e9728..1eaeec47 100644 --- a/src/window.rs +++ b/src/window.rs @@ -180,10 +180,11 @@ impl Default for WindowAttributes { } } } + impl WindowBuilder { /// Initializes a new `WindowBuilder` with default values. #[inline] - pub fn new() -> WindowBuilder { + pub fn new() -> Self { WindowBuilder { window: Default::default(), platform_specific: Default::default(), @@ -192,21 +193,21 @@ impl WindowBuilder { /// Requests the window to be of specific dimensions. #[inline] - pub fn with_inner_size(mut self, size: LogicalSize) -> WindowBuilder { + pub fn with_inner_size(mut self, size: LogicalSize) -> Self { self.window.inner_size = Some(size); self } /// Sets a minimum dimension size for the window #[inline] - pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> WindowBuilder { + pub fn with_min_inner_size(mut self, min_size: LogicalSize) -> Self { self.window.min_inner_size = Some(min_size); self } /// Sets a maximum dimension size for the window #[inline] - pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> WindowBuilder { + pub fn with_max_inner_size(mut self, max_size: LogicalSize) -> Self { self.window.max_inner_size = Some(max_size); self } @@ -222,14 +223,14 @@ impl WindowBuilder { /// /// Due to a bug in XFCE, this has no effect on Xfwm. #[inline] - pub fn with_resizable(mut self, resizable: bool) -> WindowBuilder { + pub fn with_resizable(mut self, resizable: bool) -> Self { self.window.resizable = resizable; self } /// Requests a specific title for the window. #[inline] - pub fn with_title>(mut self, title: T) -> WindowBuilder { + pub fn with_title>(mut self, title: T) -> Self { self.window.title = title.into(); self } @@ -241,42 +242,42 @@ impl WindowBuilder { /// /// - **Windows:** Screen saver is disabled in fullscreen mode. #[inline] - pub fn with_fullscreen(mut self, monitor: Option) -> WindowBuilder { + pub fn with_fullscreen(mut self, monitor: Option) -> Self { self.window.fullscreen = monitor; self } /// Requests maximized mode. #[inline] - pub fn with_maximized(mut self, maximized: bool) -> WindowBuilder { + pub fn with_maximized(mut self, maximized: bool) -> Self { self.window.maximized = maximized; self } /// Sets whether the window will be initially hidden or visible. #[inline] - pub fn with_visible(mut self, visible: bool) -> WindowBuilder { + pub fn with_visible(mut self, visible: bool) -> Self { self.window.visible = visible; self } /// Sets whether the background of the window should be transparent. #[inline] - pub fn with_transparent(mut self, transparent: bool) -> WindowBuilder { + pub fn with_transparent(mut self, transparent: bool) -> Self { self.window.transparent = transparent; self } /// Sets whether the window should have a border, a title bar, etc. #[inline] - pub fn with_decorations(mut self, decorations: bool) -> WindowBuilder { + pub fn with_decorations(mut self, decorations: bool) -> Self { self.window.decorations = decorations; self } /// Sets whether or not the window will always be on top of other windows. #[inline] - pub fn with_always_on_top(mut self, always_on_top: bool) -> WindowBuilder { + pub fn with_always_on_top(mut self, always_on_top: bool) -> Self { self.window.always_on_top = always_on_top; self } @@ -294,7 +295,7 @@ impl WindowBuilder { /// X11 has no universal guidelines for icon sizes, so you're at the whims of the WM. That /// said, it's usually in the same ballpark as on Windows. #[inline] - pub fn with_window_icon(mut self, window_icon: Option) -> WindowBuilder { + pub fn with_window_icon(mut self, window_icon: Option) -> Self { self.window.window_icon = window_icon; self } From 472eddcc1bf93e30953da692f43b37e6814199eb Mon Sep 17 00:00:00 2001 From: Murarth Date: Mon, 23 Sep 2019 11:45:29 -0700 Subject: [PATCH 60/61] X11: Fix panic when no monitors are available (#1158) * X11: Fix panic when no monitors are available * Set dummy monitor's dimensions to `(1, 1)` * X11: Avoid panicking when there are no monitors in Window::new --- CHANGELOG.md | 1 + .../linux/x11/event_processor.rs | 6 ++++- src/platform_impl/linux/x11/monitor.rs | 25 ++++++++++++++++--- src/platform_impl/linux/x11/window.rs | 17 ++++++++----- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34af8f51..adc5a793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - On macOS, fix events not being emitted during modal loops, such as when windows are being resized by the user. - On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events. +- 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. diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index d65a0133..3ca469df 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -389,7 +389,11 @@ impl EventProcessor { 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; - shared_state_lock.last_monitor = Some(monitor.clone()); + + // Avoid caching an invalid dummy monitor handle + if monitor.id != 0 { + shared_state_lock.last_monitor = Some(monitor.clone()); + } new_hidpi_factor }; if last_hidpi_factor != new_hidpi_factor { diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 33a694e0..bb75e806 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -130,6 +130,19 @@ impl MonitorHandle { }) } + fn dummy() -> Self { + MonitorHandle { + id: 0, + name: "".into(), + hidpi_factor: 1.0, + dimensions: (1, 1), + position: (0, 0), + primary: true, + rect: util::AaRect::new((0, 0), (1, 1)), + video_modes: Vec::new(), + } + } + pub fn name(&self) -> Option { Some(self.name.clone()) } @@ -167,9 +180,13 @@ impl MonitorHandle { impl XConnection { pub fn get_monitor_for_window(&self, window_rect: Option) -> MonitorHandle { let monitors = self.available_monitors(); - let default = monitors - .get(0) - .expect("[winit] Failed to find any monitors using XRandR."); + + if monitors.is_empty() { + // Return a dummy monitor to avoid panicking + return MonitorHandle::dummy(); + } + + let default = monitors.get(0).unwrap(); let window_rect = match window_rect { Some(rect) => rect, @@ -259,7 +276,7 @@ impl XConnection { self.available_monitors() .into_iter() .find(|monitor| monitor.primary) - .expect("[winit] Failed to find any monitors using XRandR.") + .unwrap_or_else(MonitorHandle::dummy) } pub fn select_xrandr_input(&self, root: Window) -> Result { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 335e529f..cf7759ec 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -119,7 +119,7 @@ impl UnownedWindow { .unwrap_or(1.0) }) } else { - return Err(os_error!(OsError::XMisc("No monitors were detected."))); + 1.0 }; info!("Guessed window DPI factor: {}", dpi_factor); @@ -637,6 +637,11 @@ impl UnownedWindow { _ => unreachable!(), }; + // Don't set fullscreen on an invalid dummy monitor handle + if monitor.id == 0 { + return None; + } + if let Some(video_mode) = video_mode { // FIXME: this is actually not correct if we're setting the // video mode to a resolution higher than the current @@ -704,11 +709,11 @@ impl UnownedWindow { pub fn current_monitor(&self) -> X11MonitorHandle { let monitor = self.shared_state.lock().last_monitor.as_ref().cloned(); monitor.unwrap_or_else(|| { - let monitor = self - .xconn - .get_monitor_for_window(Some(self.get_rect())) - .to_owned(); - self.shared_state.lock().last_monitor = Some(monitor.clone()); + let monitor = self.xconn.get_monitor_for_window(Some(self.get_rect())); + // Avoid caching an invalid dummy monitor handle + if monitor.id != 0 { + self.shared_state.lock().last_monitor = Some(monitor.clone()); + } monitor }) } From 7df040f45115d3cfd85b2a8d58be4c8a488b6ebb Mon Sep 17 00:00:00 2001 From: andersrein Date: Mon, 23 Sep 2019 20:50:06 +0200 Subject: [PATCH 61/61] Wayland: Switched to using a reference to relative_pointer_manager_proxy when creating SeatData (#1179) * Fixed relative_pointer not being set up when the "zwp_relative_pointer_manager_v1" callback comes after the "wl_seat" callback * Ran cargo fmt * Updated changelog * Improved CHANGELOG * Switched to using Rc instead of Arc since all accesses to the relative_pointer_manager_proxy will happen on the same thread. * Forgot to run cargo fmt --- CHANGELOG.md | 1 + src/platform_impl/linux/wayland/event_loop.rs | 49 ++++++++++--------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adc5a793..5be8107c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,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 # 0.20.0 Alpha 3 (2019-08-14) diff --git a/src/platform_impl/linux/wayland/event_loop.rs b/src/platform_impl/linux/wayland/event_loop.rs index af8c8079..272fc832 100644 --- a/src/platform_impl/linux/wayland/event_loop.rs +++ b/src/platform_impl/linux/wayland/event_loop.rs @@ -148,7 +148,7 @@ impl EventLoop { let mut seat_manager = SeatManager { sink: sink.clone(), - relative_pointer_manager_proxy: None, + relative_pointer_manager_proxy: Rc::new(RefCell::new(None)), store: store.clone(), seats: seats.clone(), kbd_sender, @@ -164,13 +164,16 @@ impl EventLoop { version, } => { if interface == "zwp_relative_pointer_manager_v1" { - seat_manager.relative_pointer_manager_proxy = Some( - registry - .bind(version, id, move |pointer_manager| { - pointer_manager.implement_closure(|_, _| (), ()) - }) - .unwrap(), - ) + 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 == "wl_seat" { seat_manager.add_seat(id, version, registry) @@ -493,7 +496,7 @@ struct SeatManager { store: Arc>, seats: Arc>>, kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>, - relative_pointer_manager_proxy: Option, + relative_pointer_manager_proxy: Rc>>, } impl SeatManager { @@ -505,7 +508,7 @@ impl SeatManager { store: self.store.clone(), pointer: None, relative_pointer: None, - relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.as_ref().cloned(), + relative_pointer_manager_proxy: self.relative_pointer_manager_proxy.clone(), keyboard: None, touch: None, kbd_sender: self.kbd_sender.clone(), @@ -537,7 +540,7 @@ struct SeatData { kbd_sender: ::calloop::channel::Sender<(crate::event::WindowEvent, super::WindowId)>, pointer: Option, relative_pointer: Option, - relative_pointer_manager_proxy: Option, + relative_pointer_manager_proxy: Rc>>, keyboard: Option, touch: Option, modifiers_tracker: Arc>, @@ -557,17 +560,19 @@ impl SeatData { self.modifiers_tracker.clone(), )); - self.relative_pointer = - self.relative_pointer_manager_proxy - .as_ref() - .and_then(|manager| { - super::pointer::implement_relative_pointer( - self.sink.clone(), - self.pointer.as_ref().unwrap(), - manager, - ) - .ok() - }) + 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) {