From f7d7acb3c54965fe99c6c9a12ef5b13f55a65c45 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Sun, 7 Apr 2019 15:58:47 +0200 Subject: [PATCH 01/13] Cleanup some previous merge errors --- src/platform/windows/events_loop.rs | 1258 ----------------- src/platform_impl/macos/util.rs | 38 - .../macos/util/cursor.rs | 0 .../macos/util/into_option.rs | 0 .../macos/util/mod.rs | 4 +- 5 files changed, 2 insertions(+), 1298 deletions(-) delete mode 100644 src/platform/windows/events_loop.rs delete mode 100644 src/platform_impl/macos/util.rs rename src/{platform => platform_impl}/macos/util/cursor.rs (100%) rename src/{platform => platform_impl}/macos/util/into_option.rs (100%) rename src/{platform => platform_impl}/macos/util/mod.rs (96%) diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs deleted file mode 100644 index a888233908..0000000000 --- a/src/platform/windows/events_loop.rs +++ /dev/null @@ -1,1258 +0,0 @@ -//! An events loop on Win32 is a background thread. -//! -//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop. -//! Destroying the events loop stops the thread. -//! -//! You can use the `execute_in_thread` method to execute some code in the background thread. -//! Since Win32 requires you to create a window in the right thread, you must use this method -//! to create a window. -//! -//! If you create a window whose class is set to `callback`, the window's events will be -//! propagated with `run_forever` and `poll_events`. -//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to -//! add a `WindowState` entry to a list of window to be used by the callback. - -use std::{mem, panic, ptr, thread}; -use std::any::Any; -use std::cell::RefCell; -use std::collections::HashMap; -use std::os::windows::io::AsRawHandle; -use std::sync::{Arc, mpsc, Mutex}; - -use backtrace::Backtrace; -use winapi::ctypes::c_int; -use winapi::shared::minwindef::{ - BOOL, - DWORD, - HIWORD, - INT, - LOWORD, - LPARAM, - LRESULT, - UINT, - WPARAM, -}; -use winapi::shared::windef::{HWND, POINT, RECT}; -use winapi::shared::windowsx; -use winapi::shared::winerror::S_OK; -use winapi::um::{libloaderapi, processthreadsapi, ole2, winuser}; -use winapi::um::oleidl::LPDROPTARGET; -use winapi::um::winnt::{LONG, LPCSTR, SHORT}; - -use { - ControlFlow, - Event, - EventsLoopClosed, - KeyboardInput, - LogicalPosition, - LogicalSize, - PhysicalSize, - WindowEvent, - WindowId as SuperWindowId, -}; -use events::{DeviceEvent, Touch, TouchPhase}; -use platform::platform::{event, WindowId, DEVICE_ID, wrap_device_id, util}; -use platform::platform::dpi::{ - become_dpi_aware, - dpi_to_scale_factor, - enable_non_client_dpi_scaling, - get_hwnd_scale_factor, -}; -use platform::platform::drop_handler::FileDropHandler; -use platform::platform::event::{handle_extended_keys, process_key_params, vkey_to_winit_vkey}; -use platform::platform::raw_input::{get_raw_input_data, get_raw_mouse_button_state}; -use platform::platform::window::adjust_size; -use platform::platform::window_state::{CursorFlags, WindowFlags, WindowState}; - -/// Dummy object that allows inserting a window's state. -// We store a pointer in order to !impl Send and Sync. -pub struct Inserter(*mut u8); - -impl Inserter { - /// Inserts a window's state for the callback to use. The state is removed automatically if the - /// callback receives a `WM_CLOSE` message for the window. - pub fn insert(&self, window: HWND, state: Arc>) { - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - let was_in = context_stash.as_mut().unwrap().windows.insert(window, state); - assert!(was_in.is_none()); - }); - } -} - -pub struct EventsLoop { - thread_msg_target: HWND, - // Id of the background thread from the Win32 API. - thread_id: DWORD, - // Receiver for the events. The sender is in the background thread. - receiver: mpsc::Receiver, - // Sender instance that's paired with the receiver. Used to construct an `EventsLoopProxy`. - sender: mpsc::Sender, -} - -enum EventsLoopEvent { - WinitEvent(Event), - Panic(PanicError), -} - -impl EventsLoop { - pub fn new() -> EventsLoop { - Self::with_dpi_awareness(true) - } - - pub fn with_dpi_awareness(dpi_aware: bool) -> EventsLoop { - struct InitData { - thread_msg_target: HWND, - } - unsafe impl Send for InitData {} - - become_dpi_aware(dpi_aware); - - // The main events transfer channel. - let (tx, rx) = mpsc::channel(); - - // Channel to send initialization data created on the event loop thread back to the main - // thread. - let (init_tx, init_rx) = mpsc::sync_channel(0); - - let thread_sender = tx.clone(); - let panic_sender = tx.clone(); - let thread = thread::spawn(move || { - let tx = thread_sender; - let thread_msg_target = thread_event_target_window(); - - CONTEXT_STASH.with(|context_stash| { - *context_stash.borrow_mut() = Some(ThreadLocalData { - sender: tx, - windows: HashMap::with_capacity(4), - file_drop_handlers: HashMap::with_capacity(4), - mouse_buttons_down: 0, - panic_error: None, - }); - }); - - unsafe { - // Calling `PostThreadMessageA` on a thread that does not have an events queue yet - // will fail. In order to avoid this situation, we call `IsGuiThread` to initialize - // it. - winuser::IsGUIThread(1); - // Then only we unblock the `new()` function. We are sure that we don't call - // `PostThreadMessageA()` before `new()` returns. - init_tx.send(InitData{ thread_msg_target }).ok(); - drop(init_tx); - - let mut msg = mem::uninitialized(); - - loop { - if winuser::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 { - // If a panic occurred in the child callback, forward the panic information - // to the parent thread. - let panic_payload_opt = CONTEXT_STASH.with(|stash| - stash.borrow_mut().as_mut() - .and_then(|s| s.panic_error.take()) - ); - if let Some(panic_payload) = panic_payload_opt { - panic_sender.send(EventsLoopEvent::Panic(panic_payload)).unwrap(); - }; - - // Only happens if the message is `WM_QUIT`. - debug_assert_eq!(msg.message, winuser::WM_QUIT); - break; - } - - // Calls `callback` below. - winuser::TranslateMessage(&msg); - winuser::DispatchMessageW(&msg); - } - } - }); - - // Blocks this function until the background thread has an events loop. See other comments. - let InitData { thread_msg_target } = init_rx.recv().unwrap(); - - let thread_id = unsafe { - let handle = mem::transmute(thread.as_raw_handle()); - processthreadsapi::GetThreadId(handle) - }; - - EventsLoop { - thread_msg_target, - thread_id, - receiver: rx, - sender: tx, - } - } - - pub fn poll_events(&mut self, mut callback: F) - where F: FnMut(Event) - { - loop { - let event = match self.receiver.try_recv() { - Ok(EventsLoopEvent::WinitEvent(e)) => e, - Ok(EventsLoopEvent::Panic(panic)) => { - eprintln!("resuming child thread unwind at: {:?}", Backtrace::new()); - panic::resume_unwind(panic) - }, - Err(_) => break, - }; - - callback(event); - } - } - - pub fn run_forever(&mut self, mut callback: F) - where F: FnMut(Event) -> ControlFlow - { - loop { - let event = match self.receiver.recv() { - Ok(EventsLoopEvent::WinitEvent(e)) => e, - Ok(EventsLoopEvent::Panic(panic)) => { - eprintln!("resuming child thread unwind at: {:?}", Backtrace::new()); - panic::resume_unwind(panic) - }, - Err(_) => break, - }; - - let flow = callback(event); - match flow { - ControlFlow::Continue => continue, - ControlFlow::Break => break, - } - } - } - - pub fn create_proxy(&self) -> EventsLoopProxy { - EventsLoopProxy { - thread_id: self.thread_id, - thread_msg_target: self.thread_msg_target, - sender: self.sender.clone(), - } - } - - /// Executes a function in the background thread. - /// - /// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent - /// to the unstable FnBox. - /// - /// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is - /// removed automatically if the callback receives a `WM_CLOSE` message for the window. - pub(super) fn execute_in_thread(&self, function: F) - where F: FnMut(Inserter) + Send + 'static - { - self.create_proxy().execute_in_thread(function) - } -} - -impl Drop for EventsLoop { - fn drop(&mut self) { - unsafe { - // Posting `WM_QUIT` will cause `GetMessage` to stop. - winuser::PostThreadMessageA(self.thread_id, winuser::WM_QUIT, 0, 0); - } - } -} - -#[derive(Clone)] -pub struct EventsLoopProxy { - thread_id: DWORD, - thread_msg_target: HWND, - sender: mpsc::Sender, -} - -unsafe impl Send for EventsLoopProxy {} -unsafe impl Sync for EventsLoopProxy {} - -impl EventsLoopProxy { - pub fn wakeup(&self) -> Result<(), EventsLoopClosed> { - self.sender.send(EventsLoopEvent::WinitEvent(Event::Awakened)).map_err(|_| EventsLoopClosed) - } - - /// Executes a function in the background thread. - /// - /// Note that we use FnMut instead of FnOnce because boxing FnOnce won't work on stable Rust - /// until 2030 when the design of Box is finally complete. - /// https://github.com/rust-lang/rust/issues/28796 - /// - /// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is - /// removed automatically if the callback receives a `WM_CLOSE` message for the window. - /// - /// Note that if you are using this to change some property of a window and updating - /// `WindowState` then you should call this within the lock of `WindowState`. Otherwise the - /// events may be sent to the other thread in different order to the one in which you set - /// `WindowState`, leaving them out of sync. - pub fn execute_in_thread(&self, mut function: F) - where - F: FnMut(Inserter) + Send + 'static, - { - if unsafe{ processthreadsapi::GetCurrentThreadId() } == self.thread_id { - function(Inserter(ptr::null_mut())); - } else { - // We are using double-boxing here because it make casting back much easier - let double_box: ThreadExecFn = Box::new(Box::new(function) as Box); - let raw = Box::into_raw(double_box); - - let res = unsafe { - winuser::PostMessageW( - self.thread_msg_target, - *EXEC_MSG_ID, - raw as *mut () as usize as WPARAM, - 0, - ) - }; - assert!(res != 0, "PostMessage failed; is the messages queue full?"); - } - } -} - -type ThreadExecFn = Box>; - -lazy_static! { - // Message sent when we want to execute a closure in the thread. - // WPARAM contains a Box> that must be retrieved with `Box::from_raw`, - // and LPARAM is unused. - static ref EXEC_MSG_ID: u32 = { - unsafe { - winuser::RegisterWindowMessageA("Winit::ExecMsg\0".as_ptr() as LPCSTR) - } - }; - // Message sent by a `Window` when it wants to be destroyed by the main thread. - // WPARAM and LPARAM are unused. - pub static ref DESTROY_MSG_ID: u32 = { - unsafe { - winuser::RegisterWindowMessageA("Winit::DestroyMsg\0".as_ptr() as LPCSTR) - } - }; - // Message sent by a `Window` after creation if it has a DPI != 96. - // WPARAM is the the DPI (u32). LOWORD of LPARAM is width, and HIWORD is height. - pub static ref INITIAL_DPI_MSG_ID: u32 = { - unsafe { - winuser::RegisterWindowMessageA("Winit::InitialDpiMsg\0".as_ptr() as LPCSTR) - } - }; - // WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the - // documentation in the `window_state` module for more information. - pub static ref SET_RETAIN_STATE_ON_SIZE_MSG_ID: u32 = unsafe { - winuser::RegisterWindowMessageA("Winit::SetRetainMaximized\0".as_ptr() as LPCSTR) - }; - static ref THREAD_EVENT_TARGET_WINDOW_CLASS: Vec = unsafe { - use std::ffi::OsStr; - use std::os::windows::ffi::OsStrExt; - - let class_name: Vec<_> = OsStr::new("Winit Thread Event Target") - .encode_wide() - .chain(Some(0).into_iter()) - .collect(); - - let class = winuser::WNDCLASSEXW { - cbSize: mem::size_of::() as UINT, - style: 0, - lpfnWndProc: Some(thread_event_target_callback), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: libloaderapi::GetModuleHandleW(ptr::null()), - hIcon: ptr::null_mut(), - hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly - hbrBackground: ptr::null_mut(), - lpszMenuName: ptr::null(), - lpszClassName: class_name.as_ptr(), - hIconSm: ptr::null_mut(), - }; - - winuser::RegisterClassExW(&class); - - class_name - }; -} - -fn thread_event_target_window() -> HWND { - unsafe { - let window = winuser::CreateWindowExW( - winuser::WS_EX_NOACTIVATE | winuser::WS_EX_TRANSPARENT | winuser::WS_EX_LAYERED, - THREAD_EVENT_TARGET_WINDOW_CLASS.as_ptr(), - ptr::null_mut(), - 0, - 0, 0, - 0, 0, - ptr::null_mut(), - ptr::null_mut(), - libloaderapi::GetModuleHandleW(ptr::null()), - ptr::null_mut(), - ); - winuser::SetWindowLongPtrW( - window, - winuser::GWL_STYLE, - (winuser::WS_VISIBLE | winuser::WS_POPUP) as _ - ); - - window - } -} - -// There's no parameters passed to the callback function, so it needs to get its context stashed -// in a thread-local variable. -thread_local!(static CONTEXT_STASH: RefCell> = RefCell::new(None)); -struct ThreadLocalData { - sender: mpsc::Sender, - windows: HashMap>>, - file_drop_handlers: HashMap, // Each window has its own drop handler. - mouse_buttons_down: u32, - panic_error: Option, -} -type PanicError = Box; - -// Utility function that dispatches an event on the current thread. -pub fn send_event(event: Event) { - CONTEXT_STASH.with(|context_stash| { - let context_stash = context_stash.borrow(); - - let _ = context_stash.as_ref().unwrap().sender.send(EventsLoopEvent::WinitEvent(event)); // Ignoring if closed - }); -} - -/// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of -/// the window. -unsafe fn capture_mouse(window: HWND) { - let set_capture = CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - context_stash.mouse_buttons_down += 1; - true - } else { - false - } - }); - if set_capture { - winuser::SetCapture(window); - } -} - -/// Release mouse input, stopping windows on this thread from receiving mouse input when the cursor -/// is outside the window. -unsafe fn release_mouse() { - let release_capture = CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - context_stash.mouse_buttons_down = context_stash.mouse_buttons_down.saturating_sub(1); - if context_stash.mouse_buttons_down == 0 { - return true; - } - } - false - }); - if release_capture { - winuser::ReleaseCapture(); - } -} - -pub unsafe fn run_catch_panic(error: R, f: F) -> R - where F: panic::UnwindSafe + FnOnce() -> R -{ - // If a panic has been triggered, cancel all future operations in the function. - if CONTEXT_STASH.with(|stash| stash.borrow().as_ref().map(|s| s.panic_error.is_some()).unwrap_or(false)) { - return error; - } - - let callback_result = panic::catch_unwind(f); - match callback_result { - Ok(lresult) => lresult, - Err(err) => CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - context_stash.panic_error = Some(err); - winuser::PostQuitMessage(-1); - } - error - }) - } -} - -/// 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. -// -// This is the callback that is called by `DispatchMessage` in the events loop. -// -// Returning 0 tells the Win32 API that the message has been processed. -// FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary -pub unsafe extern "system" fn callback( - window: HWND, - msg: UINT, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - // Unwinding into foreign code is undefined behavior. So we catch any panics that occur in our - // code, and if a panic happens we cancel any future operations. - run_catch_panic(-1, || callback_inner(window, msg, wparam, lparam)) -} - -unsafe fn callback_inner( - window: HWND, - msg: UINT, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - match msg { - winuser::WM_CREATE => { - use winapi::shared::winerror::{OLE_E_WRONGCOMPOBJ, RPC_E_CHANGED_MODE}; - let ole_init_result = ole2::OleInitialize(ptr::null_mut()); - // It is ok if the initialize result is `S_FALSE` because it might happen that - // multiple windows are created on the same thread. - if ole_init_result == OLE_E_WRONGCOMPOBJ { - panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`"); - } else if ole_init_result == RPC_E_CHANGED_MODE { - panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`"); - } - - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - - let drop_handlers = &mut context_stash.as_mut().unwrap().file_drop_handlers; - let new_handler = FileDropHandler::new(window); - let handler_interface_ptr = &mut (*new_handler.data).interface as LPDROPTARGET; - drop_handlers.insert(window, new_handler); - - assert_eq!(ole2::RegisterDragDrop(window, handler_interface_ptr), S_OK); - }); - 0 - }, - - winuser::WM_NCCREATE => { - enable_non_client_dpi_scaling(window); - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - winuser::WM_CLOSE => { - use events::WindowEvent::CloseRequested; - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CloseRequested - }); - 0 - }, - - winuser::WM_DESTROY => { - use events::WindowEvent::Destroyed; - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - ole2::RevokeDragDrop(window); - let context_stash_mut = context_stash.as_mut().unwrap(); - context_stash_mut.file_drop_handlers.remove(&window); - context_stash_mut.windows.remove(&window); - }); - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Destroyed - }); - 0 - }, - - winuser::WM_PAINT => { - use events::WindowEvent::Refresh; - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Refresh, - }); - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - // WM_MOVE supplies client area positions, so we send Moved here instead. - winuser::WM_WINDOWPOSCHANGED => { - use events::WindowEvent::Moved; - - let windowpos = lparam as *const winuser::WINDOWPOS; - if (*windowpos).flags & winuser::SWP_NOMOVE != winuser::SWP_NOMOVE { - let dpi_factor = get_hwnd_scale_factor(window); - let logical_position = LogicalPosition::from_physical( - ((*windowpos).x, (*windowpos).y), - dpi_factor, - ); - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Moved(logical_position), - }); - } - - // This is necessary for us to still get sent WM_SIZE. - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - winuser::WM_SIZE => { - use events::WindowEvent::Resized; - let w = LOWORD(lparam as DWORD) as u32; - let h = HIWORD(lparam as DWORD) as u32; - - let dpi_factor = get_hwnd_scale_factor(window); - let logical_size = LogicalSize::from_physical((w, h), dpi_factor); - let event = Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Resized(logical_size), - }; - - // Wait for the parent thread to process the resize event before returning from the - // callback. - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - let cstash = context_stash.as_mut().unwrap(); - - if let Some(w) = cstash.windows.get_mut(&window) { - let mut w = w.lock().unwrap(); - - // See WindowFlags::MARKER_RETAIN_STATE_ON_SIZE docs for info on why this `if` check exists. - if !w.window_flags().contains(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE) { - let maximized = wparam == winuser::SIZE_MAXIMIZED; - w.set_window_flags_in_place(|f| f.set(WindowFlags::MAXIMIZED, maximized)); - } - } - - cstash.sender.send(EventsLoopEvent::WinitEvent(event)).ok(); - }); - 0 - }, - - winuser::WM_CHAR => { - use std::mem; - use events::WindowEvent::ReceivedCharacter; - let chr: char = mem::transmute(wparam as u32); - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: ReceivedCharacter(chr), - }); - 0 - }, - - // Prevents default windows menu hotkeys playing unwanted - // "ding" sounds. Alternatively could check for WM_SYSCOMMAND - // with wparam being SC_KEYMENU, but this may prevent some - // other unwanted default hotkeys as well. - winuser::WM_SYSCHAR => { - 0 - } - - winuser::WM_MOUSEMOVE => { - use events::WindowEvent::{CursorEntered, CursorMoved}; - let x = windowsx::GET_X_LPARAM(lparam); - let y = windowsx::GET_Y_LPARAM(lparam); - - let mouse_was_outside_window = CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - if let Some(w) = context_stash.windows.get_mut(&window) { - let mut w = w.lock().unwrap(); - - let was_outside_window = !w.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW); - w.mouse.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, true)).ok(); - return was_outside_window; - } - } - - false - }); - - - if mouse_was_outside_window { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CursorEntered { device_id: DEVICE_ID }, - }); - - // Calling TrackMouseEvent in order to receive mouse leave events. - winuser::TrackMouseEvent(&mut winuser::TRACKMOUSEEVENT { - cbSize: mem::size_of::() as DWORD, - dwFlags: winuser::TME_LEAVE, - hwndTrack: window, - dwHoverTime: winuser::HOVER_DEFAULT, - }); - } - - let dpi_factor = get_hwnd_scale_factor(window); - let position = LogicalPosition::from_physical((x as f64, y as f64), dpi_factor); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CursorMoved { device_id: DEVICE_ID, position, modifiers: event::get_key_mods() }, - }); - - 0 - }, - - winuser::WM_MOUSELEAVE => { - use events::WindowEvent::CursorLeft; - - CONTEXT_STASH.with(|context_stash| { - let mut context_stash = context_stash.borrow_mut(); - if let Some(context_stash) = context_stash.as_mut() { - if let Some(w) = context_stash.windows.get_mut(&window) { - let mut w = w.lock().unwrap(); - w.mouse.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)).ok(); - } - } - }); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CursorLeft { device_id: DEVICE_ID } - }); - - 0 - }, - - winuser::WM_MOUSEWHEEL => { - use events::MouseScrollDelta::LineDelta; - use events::TouchPhase; - - let value = (wparam >> 16) as i16; - let value = value as i32; - let value = value as f32 / winuser::WHEEL_DELTA as f32; - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, modifiers: event::get_key_mods() }, - }); - - 0 - }, - - winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => { - use events::ElementState::Pressed; - use events::VirtualKeyCode; - if msg == winuser::WM_SYSKEYDOWN && wparam as i32 == winuser::VK_F4 { - winuser::DefWindowProcW(window, msg, wparam, lparam) - } else { - if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Pressed, - scancode: scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - } - } - }); - // Windows doesn't emit a delete character by default, but in order to make it - // consistent with the other platforms we'll emit a delete character here. - if vkey == Some(VirtualKeyCode::Delete) { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::ReceivedCharacter('\u{7F}'), - }); - } - } - 0 - } - }, - - winuser::WM_KEYUP | winuser::WM_SYSKEYUP => { - use events::ElementState::Released; - if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Released, - scancode: scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - } - }); - } - 0 - }, - - winuser::WM_LBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Left; - use events::ElementState::Pressed; - - capture_mouse(window); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_LBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Left; - use events::ElementState::Released; - - release_mouse(); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Left, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_RBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Right; - use events::ElementState::Pressed; - - capture_mouse(window); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_RBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Right; - use events::ElementState::Released; - - release_mouse(); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Right, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_MBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Middle; - use events::ElementState::Pressed; - - capture_mouse(window); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_MBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Middle; - use events::ElementState::Released; - - release_mouse(); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Middle, modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_XBUTTONDOWN => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Other; - use events::ElementState::Pressed; - let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); - - capture_mouse(window); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Other(xbutton as u8), modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_XBUTTONUP => { - use events::WindowEvent::MouseInput; - use events::MouseButton::Other; - use events::ElementState::Released; - let xbutton = winuser::GET_XBUTTON_WPARAM(wparam); - - release_mouse(); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: MouseInput { device_id: DEVICE_ID, state: Released, button: Other(xbutton as u8), modifiers: event::get_key_mods() } - }); - 0 - }, - - winuser::WM_INPUT_DEVICE_CHANGE => { - let event = match wparam as _ { - winuser::GIDC_ARRIVAL => DeviceEvent::Added, - winuser::GIDC_REMOVAL => DeviceEvent::Removed, - _ => unreachable!(), - }; - - send_event(Event::DeviceEvent { - device_id: wrap_device_id(lparam as _), - event, - }); - - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - winuser::WM_INPUT => { - use events::DeviceEvent::{Motion, MouseMotion, MouseWheel, Button, Key}; - use events::MouseScrollDelta::LineDelta; - use events::ElementState::{Pressed, Released}; - - if let Some(data) = get_raw_input_data(lparam as _) { - let device_id = wrap_device_id(data.header.hDevice as _); - - if data.header.dwType == winuser::RIM_TYPEMOUSE { - let mouse = data.data.mouse(); - - if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { - let x = mouse.lLastX as f64; - let y = mouse.lLastY as f64; - - if x != 0.0 { - send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x } - }); - } - - if y != 0.0 { - send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y } - }); - } - - if x != 0.0 || y != 0.0 { - send_event(Event::DeviceEvent { - device_id, - event: MouseMotion { delta: (x, y) } - }); - } - } - - if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { - let delta = mouse.usButtonData as SHORT / winuser::WHEEL_DELTA; - send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { delta: LineDelta(0.0, delta as f32) } - }); - } - - let button_state = get_raw_mouse_button_state(mouse.usButtonFlags); - // Left, middle, and right, respectively. - for (index, state) in button_state.iter().enumerate() { - if let Some(state) = *state { - // This gives us consistency with X11, since there doesn't - // seem to be anything else reasonable to do for a mouse - // button ID. - let button = (index + 1) as _; - send_event(Event::DeviceEvent { - device_id, - event: Button { - button, - state, - } - }); - } - } - } else if data.header.dwType == winuser::RIM_TYPEKEYBOARD { - let keyboard = data.data.keyboard(); - - let pressed = keyboard.Message == winuser::WM_KEYDOWN - || keyboard.Message == winuser::WM_SYSKEYDOWN; - let released = keyboard.Message == winuser::WM_KEYUP - || keyboard.Message == winuser::WM_SYSKEYUP; - - if pressed || released { - let state = if pressed { - Pressed - } else { - Released - }; - - let scancode = keyboard.MakeCode as _; - let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) - | util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _); - if let Some((vkey, scancode)) = handle_extended_keys( - keyboard.VKey as _, - scancode, - extended, - ) { - let virtual_keycode = vkey_to_winit_vkey(vkey); - - send_event(Event::DeviceEvent { - device_id, - event: Key(KeyboardInput { - scancode, - state, - virtual_keycode, - modifiers: event::get_key_mods(), - }), - }); - } - } - } - } - - winuser::DefWindowProcW(window, msg, wparam, lparam) - }, - - winuser::WM_TOUCH => { - let pcount = LOWORD( wparam as DWORD ) as usize; - let mut inputs = Vec::with_capacity( pcount ); - inputs.set_len( pcount ); - let htouch = lparam as winuser::HTOUCHINPUT; - if winuser::GetTouchInputInfo( - htouch, - pcount as UINT, - inputs.as_mut_ptr(), - mem::size_of::() as INT, - ) > 0 { - let dpi_factor = get_hwnd_scale_factor(window); - for input in &inputs { - let x = (input.x as f64) / 100f64; - let y = (input.y as f64) / 100f64; - let location = LogicalPosition::from_physical((x, y), dpi_factor); - send_event( Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: WindowEvent::Touch(Touch { - phase: - if input.dwFlags & winuser::TOUCHEVENTF_DOWN != 0 { - TouchPhase::Started - } else if input.dwFlags & winuser::TOUCHEVENTF_UP != 0 { - TouchPhase::Ended - } else if input.dwFlags & winuser::TOUCHEVENTF_MOVE != 0 { - TouchPhase::Moved - } else { - continue; - }, - location, - id: input.dwID as u64, - device_id: DEVICE_ID, - }) - }); - } - } - winuser::CloseTouchInputHandle( htouch ); - 0 - } - - winuser::WM_SETFOCUS => { - use events::WindowEvent::{Focused, CursorMoved}; - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Focused(true) - }); - - let x = windowsx::GET_X_LPARAM(lparam) as f64; - let y = windowsx::GET_Y_LPARAM(lparam) as f64; - let dpi_factor = get_hwnd_scale_factor(window); - let position = LogicalPosition::from_physical((x, y), dpi_factor); - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CursorMoved { device_id: DEVICE_ID, position, modifiers: event::get_key_mods() }, - }); - - 0 - }, - - winuser::WM_KILLFOCUS => { - use events::WindowEvent::Focused; - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Focused(false) - }); - 0 - }, - - winuser::WM_SETCURSOR => { - let set_cursor_to = CONTEXT_STASH.with(|context_stash| { - context_stash - .borrow() - .as_ref() - .and_then(|cstash| cstash.windows.get(&window)) - .and_then(|window_state_mutex| { - let window_state = window_state_mutex.lock().unwrap(); - if window_state.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW) { - Some(window_state.mouse.cursor) - } else { - None - } - }) - }); - - match set_cursor_to { - Some(cursor) => { - let cursor = winuser::LoadCursorW( - ptr::null_mut(), - cursor.to_windows_cursor(), - ); - winuser::SetCursor(cursor); - 0 - }, - None => winuser::DefWindowProcW(window, msg, wparam, lparam) - } - }, - - winuser::WM_DROPFILES => { - // See `FileDropHandler` for implementation. - 0 - }, - - winuser::WM_GETMINMAXINFO => { - let mmi = lparam as *mut winuser::MINMAXINFO; - //(*mmi).max_position = winapi::shared::windef::POINT { x: -8, y: -8 }; // The upper left corner of the window if it were maximized on the primary monitor. - //(*mmi).max_size = winapi::shared::windef::POINT { x: .., y: .. }; // The dimensions of the primary monitor. - - CONTEXT_STASH.with(|context_stash| { - if let Some(cstash) = context_stash.borrow().as_ref() { - if let Some(wstash) = cstash.windows.get(&window) { - let window_state = wstash.lock().unwrap(); - - if window_state.min_size.is_some() || window_state.max_size.is_some() { - let style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD; - let ex_style = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD; - if let Some(min_size) = window_state.min_size { - let min_size = min_size.to_physical(window_state.dpi_factor); - let (width, height) = adjust_size(min_size, style, ex_style); - (*mmi).ptMinTrackSize = POINT { x: width as i32, y: height as i32 }; - } - if let Some(max_size) = window_state.max_size { - let max_size = max_size.to_physical(window_state.dpi_factor); - let (width, height) = adjust_size(max_size, style, ex_style); - (*mmi).ptMaxTrackSize = POINT { x: width as i32, y: height as i32 }; - } - } - } - } - }); - - 0 - }, - - // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change - // DPI, therefore all applications are closed while DPI is changing. - winuser::WM_DPICHANGED => { - use events::WindowEvent::HiDpiFactorChanged; - - // This message actually provides two DPI values - x and y. However MSDN says that - // "you only need to use either the X-axis or the Y-axis value when scaling your - // application since they are the same". - // https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx - let new_dpi_x = u32::from(LOWORD(wparam as DWORD)); - let new_dpi_factor = dpi_to_scale_factor(new_dpi_x); - - let allow_resize = CONTEXT_STASH.with(|context_stash| { - if let Some(wstash) = context_stash.borrow().as_ref().and_then(|cstash| cstash.windows.get(&window)) { - let mut window_state = wstash.lock().unwrap(); - let old_dpi_factor = window_state.dpi_factor; - window_state.dpi_factor = new_dpi_factor; - - new_dpi_factor != old_dpi_factor && window_state.fullscreen.is_none() - } else { - true - } - }); - - // This prevents us from re-applying DPI adjustment to the restored size after exiting - // fullscreen (the restored size is already DPI adjusted). - if allow_resize { - // Resize window to the size suggested by Windows. - let rect = &*(lparam as *const RECT); - winuser::SetWindowPos( - window, - ptr::null_mut(), - rect.left, - rect.top, - rect.right - rect.left, - rect.bottom - rect.top, - winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE, - ); - } - - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: HiDpiFactorChanged(new_dpi_factor), - }); - - 0 - }, - - _ => { - if msg == *DESTROY_MSG_ID { - winuser::DestroyWindow(window); - 0 - } else if msg == *INITIAL_DPI_MSG_ID { - use events::WindowEvent::HiDpiFactorChanged; - let scale_factor = dpi_to_scale_factor(wparam as u32); - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: HiDpiFactorChanged(scale_factor), - }); - // Automatically resize for actual DPI - let width = LOWORD(lparam as DWORD) as u32; - let height = HIWORD(lparam as DWORD) as u32; - let (adjusted_width, adjusted_height): (u32, u32) = PhysicalSize::from_logical( - (width, height), - scale_factor, - ).into(); - // We're not done yet! `SetWindowPos` needs the window size, not the client area size. - let mut rect = RECT { - top: 0, - left: 0, - bottom: adjusted_height as LONG, - right: adjusted_width as LONG, - }; - let dw_style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD; - let b_menu = !winuser::GetMenu(window).is_null() as BOOL; - let dw_style_ex = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD; - winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); - let outer_x = (rect.right - rect.left).abs() as c_int; - let outer_y = (rect.top - rect.bottom).abs() as c_int; - winuser::SetWindowPos( - window, - ptr::null_mut(), - 0, - 0, - outer_x, - outer_y, - winuser::SWP_NOMOVE - | winuser::SWP_NOREPOSITION - | winuser::SWP_NOZORDER - | winuser::SWP_NOACTIVATE, - ); - 0 - } else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID { - CONTEXT_STASH.with(|context_stash| { - if let Some(cstash) = context_stash.borrow().as_ref() { - if let Some(wstash) = cstash.windows.get(&window) { - let mut window_state = wstash.lock().unwrap(); - window_state.set_window_flags_in_place(|f| f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam != 0)); - } - } - }); - 0 - } else { - winuser::DefWindowProcW(window, msg, wparam, lparam) - } - } - } -} - -pub unsafe extern "system" fn thread_event_target_callback( - window: HWND, - msg: UINT, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - // See `callback` comment. - run_catch_panic(-1, || { - match msg { - _ if msg == *EXEC_MSG_ID => { - let mut function: ThreadExecFn = Box::from_raw(wparam as usize as *mut _); - function(Inserter(ptr::null_mut())); - 0 - }, - _ => winuser::DefWindowProcW(window, msg, wparam, lparam) - } - }) -} diff --git a/src/platform_impl/macos/util.rs b/src/platform_impl/macos/util.rs deleted file mode 100644 index c4c348f036..0000000000 --- a/src/platform_impl/macos/util.rs +++ /dev/null @@ -1,38 +0,0 @@ -use cocoa::appkit::NSWindowStyleMask; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSRect, NSUInteger}; -use core_graphics::display::CGDisplay; - -use platform_impl::platform::ffi; -use platform_impl::platform::window::IdRef; - -pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange { - location: ffi::NSNotFound as NSUInteger, - length: 0, -}; - -// For consistency with other platforms, this will... -// 1. translate the bottom-left window corner into the top-left window corner -// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one -pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { - CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) -} - -pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) { - use cocoa::appkit::NSWindow; - window.setStyleMask_(mask); - // If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly! - window.makeFirstResponder_(view); -} - -pub unsafe fn create_input_context(view: id) -> IdRef { - let input_context: id = msg_send![class!(NSTextInputContext), alloc]; - let input_context: id = msg_send![input_context, initWithClient:view]; - IdRef::new(input_context) -} - -#[allow(dead_code)] -pub unsafe fn open_emoji_picker() { - let app: id = msg_send![class!(NSApplication), sharedApplication]; - let _: () = msg_send![app, orderFrontCharacterPalette:nil]; -} diff --git a/src/platform/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs similarity index 100% rename from src/platform/macos/util/cursor.rs rename to src/platform_impl/macos/util/cursor.rs diff --git a/src/platform/macos/util/into_option.rs b/src/platform_impl/macos/util/into_option.rs similarity index 100% rename from src/platform/macos/util/into_option.rs rename to src/platform_impl/macos/util/into_option.rs diff --git a/src/platform/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs similarity index 96% rename from src/platform/macos/util/mod.rs rename to src/platform_impl/macos/util/mod.rs index baa0e6e0e0..627f667430 100644 --- a/src/platform/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -9,8 +9,8 @@ use cocoa::foundation::{NSRect, NSUInteger}; use core_graphics::display::CGDisplay; use objc::runtime::{Class, Object}; -use platform::platform::ffi; -use platform::platform::window::IdRef; +use platform_impl::platform::ffi; +use platform_impl::platform::window::IdRef; pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange { location: ffi::NSNotFound as NSUInteger, From 3fea477bfd591def2dd64559174c24e02aee0be4 Mon Sep 17 00:00:00 2001 From: trimental Date: Fri, 22 Feb 2019 22:30:59 +0800 Subject: [PATCH 02/13] On wayland, fix `with_title()` not setting the windows title (#770) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5c282b46..828fb1fabc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased + - Changes below are considered **breaking**. - Change all occurrences of `EventsLoop` to `EventLoop`. - Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules. @@ -36,6 +37,7 @@ - `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit. - Rename `MonitorId` to `MonitorHandle`. - Removed `serde` implementations from `ControlFlow`. +- On Wayland, fix `with_title()` not setting the windows title - On Wayland, add `set_wayland_theme()` to control client decoration color theme - Added serde serialization to `os::unix::XWindowType`. - **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency. From dad8de82fac930db4cd1a8ed8e3984860e10d8a5 Mon Sep 17 00:00:00 2001 From: Torkel Danielsson Date: Fri, 22 Feb 2019 15:31:16 +0100 Subject: [PATCH 03/13] Handle horizontal wheel input (Windows) (#792) * add handler for horizontal wheel input * add changlelog message re now handling horiz scroll on windows --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 828fb1fabc..7c8bfd6818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - Refactored Windows state/flag-setting code. - On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on. - On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area. +- On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled. # Version 0.18.1 (2018-12-30) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 1d7a4aa1a9..ad4f39976d 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -994,6 +994,22 @@ unsafe extern "system" fn public_window_callback( 0 }, + winuser::WM_MOUSEHWHEEL => { + use event::MouseScrollDelta::LineDelta; + use event::TouchPhase; + + let value = (wparam >> 16) as i16; + let value = value as i32; + let value = value as f32 / winuser::WHEEL_DELTA as f32; + + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, modifiers: event::get_key_mods() }, + }); + + 0 + }, + winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => { use event::ElementState::Pressed; use event::VirtualKeyCode; From f000b82d74c9288cdb0e8f37868c539046b63c1f Mon Sep 17 00:00:00 2001 From: Michael Palmos Date: Sun, 24 Feb 2019 06:41:55 +1000 Subject: [PATCH 04/13] Fix incorrect keycodes when using a non-US keyboard layout. (#755) * Fix incorrect keycodes when using a non-US keyboard layout. This commit fixes the issue described in #752, and uses the advised method to fix it. * Style fixes Co-Authored-By: Toqozz * Refactoring of macOS `virtualkeycode` fix (#752) * Applies requested changes as per pull request discussion (#755). --- CHANGELOG.md | 1 + src/platform_impl/macos/event_loop.rs | 103 +++++++++++++++++++++----- src/platform_impl/macos/view.rs | 85 ++++++++++++--------- 3 files changed, 137 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c8bfd6818..3979c0cc3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit. - Rename `MonitorId` to `MonitorHandle`. - Removed `serde` implementations from `ControlFlow`. +- On macOS, fix keycodes being incorrect when using a non-US keyboard layout. - On Wayland, fix `with_title()` not setting the windows title - On Wayland, add `set_wayland_theme()` to control client decoration color theme - Added serde serialization to `os::unix::XWindowType`. diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index d5da8faf8c..f7a1642e8d 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -544,7 +544,67 @@ impl Proxy { } } -pub fn to_virtual_key_code(code: c_ushort) -> Option { +pub fn char_to_keycode(c: char) -> Option { + // We only translate keys that are affected by keyboard layout. + // + // Note that since keys are translated in a somewhat "dumb" way (reading character) + // there is a concern that some combination, i.e. Cmd+char, causes the wrong + // letter to be received, and so we receive the wrong key. + // + // Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626 + Some(match c { + 'a' | 'A' => events::VirtualKeyCode::A, + 'b' | 'B' => events::VirtualKeyCode::B, + 'c' | 'C' => events::VirtualKeyCode::C, + 'd' | 'D' => events::VirtualKeyCode::D, + 'e' | 'E' => events::VirtualKeyCode::E, + 'f' | 'F' => events::VirtualKeyCode::F, + 'g' | 'G' => events::VirtualKeyCode::G, + 'h' | 'H' => events::VirtualKeyCode::H, + 'i' | 'I' => events::VirtualKeyCode::I, + 'j' | 'J' => events::VirtualKeyCode::J, + 'k' | 'K' => events::VirtualKeyCode::K, + 'l' | 'L' => events::VirtualKeyCode::L, + 'm' | 'M' => events::VirtualKeyCode::M, + 'n' | 'N' => events::VirtualKeyCode::N, + 'o' | 'O' => events::VirtualKeyCode::O, + 'p' | 'P' => events::VirtualKeyCode::P, + 'q' | 'Q' => events::VirtualKeyCode::Q, + 'r' | 'R' => events::VirtualKeyCode::R, + 's' | 'S' => events::VirtualKeyCode::S, + 't' | 'T' => events::VirtualKeyCode::T, + 'u' | 'U' => events::VirtualKeyCode::U, + 'v' | 'V' => events::VirtualKeyCode::V, + 'w' | 'W' => events::VirtualKeyCode::W, + 'x' | 'X' => events::VirtualKeyCode::X, + 'y' | 'Y' => events::VirtualKeyCode::Y, + 'z' | 'Z' => events::VirtualKeyCode::Z, + '1' | '!' => events::VirtualKeyCode::Key1, + '2' | '@' => events::VirtualKeyCode::Key2, + '3' | '#' => events::VirtualKeyCode::Key3, + '4' | '$' => events::VirtualKeyCode::Key4, + '5' | '%' => events::VirtualKeyCode::Key5, + '6' | '^' => events::VirtualKeyCode::Key6, + '7' | '&' => events::VirtualKeyCode::Key7, + '8' | '*' => events::VirtualKeyCode::Key8, + '9' | '(' => events::VirtualKeyCode::Key9, + '0' | ')' => events::VirtualKeyCode::Key0, + '=' | '+' => events::VirtualKeyCode::Equals, + '-' | '_' => events::VirtualKeyCode::Minus, + ']' | '}' => events::VirtualKeyCode::RBracket, + '[' | '{' => events::VirtualKeyCode::LBracket, + '\''| '"' => events::VirtualKeyCode::Apostrophe, + ';' | ':' => events::VirtualKeyCode::Semicolon, + '\\'| '|' => events::VirtualKeyCode::Backslash, + ',' | '<' => events::VirtualKeyCode::Comma, + '/' | '?' => events::VirtualKeyCode::Slash, + '.' | '>' => events::VirtualKeyCode::Period, + '`' | '~' => events::VirtualKeyCode::Grave, + _ => return None, + }) +} + +pub fn scancode_to_keycode(code: c_ushort) -> Option { Some(match code { 0x00 => events::VirtualKeyCode::A, 0x01 => events::VirtualKeyCode::S, @@ -680,20 +740,19 @@ pub fn to_virtual_key_code(code: c_ushort) -> Option { }) } -pub fn check_additional_virtual_key_codes( - s: &Option +pub fn check_function_keys( + s: &String ) -> Option { - if let &Some(ref s) = s { - if let Some(ch) = s.encode_utf16().next() { - return Some(match ch { - 0xf718 => events::VirtualKeyCode::F21, - 0xf719 => events::VirtualKeyCode::F22, - 0xf71a => events::VirtualKeyCode::F23, - 0xf71b => events::VirtualKeyCode::F24, - _ => return None, - }) - } + if let Some(ch) = s.encode_utf16().next() { + return Some(match ch { + 0xf718 => events::VirtualKeyCode::F21, + 0xf719 => events::VirtualKeyCode::F22, + 0xf71a => events::VirtualKeyCode::F23, + 0xf71b => events::VirtualKeyCode::F24, + _ => return None, + }) } + None } @@ -709,6 +768,16 @@ pub fn event_mods(event: cocoa::base::id) -> ModifiersState { } } +pub fn get_scancode(event: cocoa::base::id) -> c_ushort { + // In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character, + // and there is no easy way to navtively retrieve the layout-dependent character. + // In winit, we use keycode to refer to the key's character, and so this function aligns + // AppKit's terminology with ours. + unsafe { + msg_send![event, keyCode] + } +} + unsafe fn modifier_event( ns_event: cocoa::base::id, keymask: NSEventModifierFlags, @@ -721,14 +790,14 @@ unsafe fn modifier_event( } else { ElementState::Pressed }; - let keycode = NSEvent::keyCode(ns_event); - let scancode = keycode as u32; - let virtual_keycode = to_virtual_key_code(keycode); + + let scancode = get_scancode(ns_event); + let virtual_keycode = scancode_to_keycode(scancode); Some(WindowEvent::KeyboardInput { device_id: DEVICE_ID, input: KeyboardInput { state, - scancode, + scancode: scancode as u32, virtual_keycode, modifiers: event_mods(ns_event), }, diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 757982c516..d6e7333110 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -14,10 +14,11 @@ use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Protocol, Sel, BOOL, YES}; use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId}; -use platform_impl::platform::event_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code, check_additional_virtual_key_codes}; +use platform_impl::platform::event_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code, check_additional_virtual_key_codes, get_scancode}; use platform_impl::platform::util; use platform_impl::platform::ffi::*; use platform_impl::platform::window::{get_window_id, IdRef}; +use event; struct ViewState { window: id, @@ -391,16 +392,54 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { } } -fn get_characters(event: id) -> Option { +fn get_characters(event: id, ignore_modifiers: bool) -> String { unsafe { - let characters: id = msg_send![event, characters]; + let characters: id = if ignore_modifiers { + msg_send![event, charactersIgnoringModifiers] + } else { + msg_send![event, characters] + }; + + assert_ne!(characters, nil); let slice = slice::from_raw_parts( characters.UTF8String() as *const c_uchar, characters.len(), ); + let string = str::from_utf8_unchecked(slice); - Some(string.to_owned()) + + string.to_owned() + } +} + +// Retrieves a layout-independent keycode given an event. +fn retrieve_keycode(event: id) -> Option { + #[inline] + fn get_code(ev: id, raw: bool) -> Option { + let characters = get_characters(ev, raw); + characters.chars().next().map_or(None, |c| char_to_keycode(c)) } + + // Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first. + // If we don't get a match, then we fall back to unmodified characters. + let code = get_code(event, false) + .or_else(|| { + get_code(event, true) + }); + + // We've checked all layout related keys, so fall through to scancode. + // Reaching this code means that the key is layout-independent (e.g. Backspace, Return). + // + // We're additionally checking here for F21-F24 keys, since their keycode + // can vary, but we know that they are encoded + // in characters property. + code.or_else(|| { + let scancode = get_scancode(event); + scancode_to_keycode(scancode) + .or_else(|| { + check_function_keys(&get_characters(event, true)) + }) + }) } extern fn key_down(this: &Object, _sel: Sel, event: id) { @@ -409,18 +448,12 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); let window_id = WindowId(get_window_id(state.window)); + let characters = get_characters(event, false); - state.raw_characters = get_characters(event); + state.raw_characters = Some(characters.clone()); - let keycode: c_ushort = msg_send![event, keyCode]; - // We are checking here for F21-F24 keys, since their keycode - // can vary, but we know that they are encoded - // in characters property. - let virtual_keycode = to_virtual_key_code(keycode) - .or_else(|| { - check_additional_virtual_key_codes(&state.raw_characters) - }); - let scancode = keycode as u32; + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); let is_repeat = msg_send![event, isARepeat]; let window_event = Event::WindowEvent { @@ -436,17 +469,6 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { }, }; - let characters: id = msg_send![event, characters]; - let slice = slice::from_raw_parts( - characters.UTF8String() as *const c_uchar, - characters.len(), - ); - let string = str::from_utf8_unchecked(slice); - - state.raw_characters = { - Some(string.to_owned()) - }; - if let Some(shared) = state.shared.upgrade() { shared.pending_events .lock() @@ -454,7 +476,7 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { .push_back(window_event); // Emit `ReceivedCharacter` for key repeats if is_repeat && state.is_key_down{ - for character in string.chars() { + for character in characters.chars() { let window_event = Event::WindowEvent { window_id, event: WindowEvent::ReceivedCharacter(character), @@ -483,16 +505,9 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) { state.is_key_down = false; - // We need characters here to check for additional keys such as - // F21-F24. - let characters = get_characters(event); + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); - let keycode: c_ushort = msg_send![event, keyCode]; - let virtual_keycode = to_virtual_key_code(keycode) - .or_else(|| { - check_additional_virtual_key_codes(&characters) - }); - let scancode = keycode as u32; let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.window)), event: WindowEvent::KeyboardInput { From ab0a34012f12d3ac3f524fc4cc6458f80e0fb883 Mon Sep 17 00:00:00 2001 From: Riku Salminen Date: Mon, 25 Feb 2019 01:02:55 +0200 Subject: [PATCH 05/13] x11: thread safe replacement for XNextEvent (#782) XNextEvent will block for input while holding the global Xlib mutex. This will cause a deadlock in even the most trivial multi-threaded application because OpenGL functions will need to hold the Xlib mutex too. Add EventsLoop::poll_one_event and EventsLoop::wait_for_input to provide thread-safe functions to poll and wait events from the X11 event queue using unix select(2) and XCheckIfEvent. This is a somewhat ugly workaround to an ugly problem. Fixes #779 --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/mod.rs | 78 +++++++++++++++++++++++-- src/platform_impl/linux/x11/xdisplay.rs | 8 +++ 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3979c0cc3f..2c64e3e6bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - On Wayland, add `set_wayland_theme()` to control client decoration color theme - Added serde serialization to `os::unix::XWindowType`. - **Breaking:** Remove the `icon_loading` feature and the associated `image` dependency. +- On X11, make event loop thread safe by replacing XNextEvent with select(2) and XCheckIfEvent - On Windows, fix malformed function pointer typecast that could invoke undefined behavior. - Refactored Windows state/flag-setting code. - On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on. diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 1e2d5930a5..a98556c107 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -19,6 +19,7 @@ use std::collections::HashMap; use std::ffi::CStr; use std::ops::Deref; use std::os::raw::*; +use libc::{select, fd_set, FD_SET, FD_ZERO, FD_ISSET, EINTR, EINVAL, ENOMEM, EBADF, __errno_location}; use std::sync::{Arc, mpsc, Weak}; use std::sync::atomic::{self, AtomicBool}; @@ -185,6 +186,70 @@ impl EventLoop { } } + unsafe fn poll_one_event(&mut self, event_ptr : *mut ffi::XEvent) -> bool { + // This function is used to poll and remove a single event + // from the Xlib event queue in a non-blocking, atomic way. + // XCheckIfEvent is non-blocking and removes events from queue. + // XNextEvent can't be used because it blocks while holding the + // global Xlib mutex. + // XPeekEvent does not remove events from the queue. + unsafe extern "C" fn predicate( + _display: *mut ffi::Display, + _event: *mut ffi::XEvent, + _arg : *mut c_char) -> c_int { + // This predicate always returns "true" (1) to accept all events + 1 + } + + let result = (self.xconn.xlib.XCheckIfEvent)( + self.xconn.display, + event_ptr, + Some(predicate), + std::ptr::null_mut()); + + result != 0 + } + + unsafe fn wait_for_input(&mut self) { + // XNextEvent can not be used in multi-threaded applications + // because it is blocking for input while holding the global + // Xlib mutex. + // To work around this issue, first flush the X11 display, then + // use select(2) to wait for input to arrive + loop { + // First use XFlush to flush any buffered x11 requests + (self.xconn.xlib.XFlush)(self.xconn.display); + + // Then use select(2) to wait for input data + let mut fds : fd_set = mem::uninitialized(); + FD_ZERO(&mut fds); + FD_SET(self.xconn.x11_fd, &mut fds); + let err = select( + self.xconn.x11_fd + 1, + &mut fds, // read fds + std::ptr::null_mut(), // write fds + std::ptr::null_mut(), // except fds (could be used to detect errors) + std::ptr::null_mut()); // timeout + + if err < 0 { + let errno_ptr = __errno_location(); + let errno = *errno_ptr; + + if errno == EINTR { + // try again if errno is EINTR + continue; + } + + assert!(errno == EBADF || errno == EINVAL || errno == ENOMEM); + panic!("select(2) returned fatal error condition"); + } + + if FD_ISSET(self.xconn.x11_fd, &mut fds) { + break; + } + } + } + pub fn poll_events(&mut self, mut callback: F) where F: FnMut(Event) { @@ -192,13 +257,9 @@ impl EventLoop { loop { // Get next event unsafe { - // Ensure XNextEvent won't block - let count = (self.xconn.xlib.XPending)(self.xconn.display); - if count == 0 { + if !self.poll_one_event(&mut xev) { break; } - - (self.xconn.xlib.XNextEvent)(self.xconn.display, &mut xev); } self.process_event(&mut xev, &mut callback); } @@ -210,7 +271,12 @@ impl EventLoop { let mut xev = unsafe { mem::uninitialized() }; loop { - unsafe { (self.xconn.xlib.XNextEvent)(self.xconn.display, &mut xev) }; // Blocks as necessary + unsafe { + while !self.poll_one_event(&mut xev) { + // block until input is available + self.wait_for_input(); + } + }; let mut control_flow = ControlFlow::Continue; diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index c0f1e7d215..cceaa8c755 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -1,6 +1,7 @@ use std::ptr; use std::fmt; use std::error::Error; +use std::os::raw::c_int; use libc; use parking_lot::Mutex; @@ -18,6 +19,7 @@ pub struct XConnection { pub xinput2: ffi::XInput2, pub xlib_xcb: ffi::Xlib_xcb, pub display: *mut ffi::Display, + pub x11_fd: c_int, pub latest_error: Mutex>, } @@ -48,6 +50,11 @@ impl XConnection { display }; + // Get X11 socket file descriptor + let fd = unsafe { + (xlib.XConnectionNumber)(display) + }; + Ok(XConnection { xlib, xrandr, @@ -56,6 +63,7 @@ impl XConnection { xinput2, xlib_xcb, display, + x11_fd: fd, latest_error: Mutex::new(None), }) } From b682c3dfb5135afa5e5e053f89e031749a4e1441 Mon Sep 17 00:00:00 2001 From: Osspial Date: Tue, 5 Mar 2019 17:55:01 -0500 Subject: [PATCH 06/13] Ignore the AltGr key when populating ModifersState (#763) * When building ModifiersState, ignore AltGr on Windows * Add CHANGELOG entry * Also filter out Control when pressing AltGr --- CHANGELOG.md | 1 + src/platform_impl/windows/event.rs | 80 +++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c64e3e6bd..1ab516efae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on. - On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area. - On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled. +- On Windows, ignore the AltGr key when populating the `ModifersState` type. # Version 0.18.1 (2018-12-30) diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 60e1b910f8..a3d733d25e 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -1,28 +1,82 @@ -use std::char; +use std::{char, ptr}; use std::os::raw::c_int; +use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; use event::{ScanCode, ModifiersState, VirtualKeyCode}; -use winapi::shared::minwindef::{WPARAM, LPARAM, UINT}; +use winapi::shared::minwindef::{WPARAM, LPARAM, UINT, HKL, HKL__}; use winapi::um::winuser; +fn key_pressed(vkey: c_int) -> bool { + unsafe { + (winuser::GetKeyState(vkey) & (1 << 15)) == (1 << 15) + } +} + pub fn get_key_mods() -> ModifiersState { let mut mods = ModifiersState::default(); + let filter_out_altgr = layout_uses_altgr() && key_pressed(winuser::VK_RMENU); + + mods.shift = key_pressed(winuser::VK_SHIFT); + mods.ctrl = key_pressed(winuser::VK_CONTROL) && !filter_out_altgr; + mods.alt = key_pressed(winuser::VK_MENU) && !filter_out_altgr; + mods.logo = key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN); + mods +} + +unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option { + let mut unicode_bytes = [0u16; 5]; + let len = winuser::ToUnicodeEx(v_key, 0, keyboard_state.as_ptr(), unicode_bytes.as_mut_ptr(), unicode_bytes.len() as _, 0, hkl); + if len >= 1 { + char::decode_utf16(unicode_bytes.into_iter().cloned()).next().and_then(|c| c.ok()) + } else { + None + } +} + +/// Figures out if the keyboard layout has an AltGr key instead of an Alt key. +/// +/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So, +/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every +/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If +/// pressing AltGr outputs characters that are different from the standard characters, the layout +/// uses AltGr. Otherwise, it doesn't. +/// +/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416 +fn layout_uses_altgr() -> bool { unsafe { - if winuser::GetKeyState(winuser::VK_SHIFT) & (1 << 15) == (1 << 15) { - mods.shift = true; - } - if winuser::GetKeyState(winuser::VK_CONTROL) & (1 << 15) == (1 << 15) { - mods.ctrl = true; - } - if winuser::GetKeyState(winuser::VK_MENU) & (1 << 15) == (1 << 15) { - mods.alt = true; + static ACTIVE_LAYOUT: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + static USES_ALTGR: AtomicBool = AtomicBool::new(false); + + let hkl = winuser::GetKeyboardLayout(0); + let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst); + + if hkl == old_hkl { + return USES_ALTGR.load(Ordering::SeqCst); } - if (winuser::GetKeyState(winuser::VK_LWIN) | winuser::GetKeyState(winuser::VK_RWIN)) & (1 << 15) == (1 << 15) { - mods.logo = true; + + let mut keyboard_state_altgr = [0u8; 256]; + // AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses + // we have to emulate to do an AltGr test. + keyboard_state_altgr[winuser::VK_MENU as usize] = 0x80; + keyboard_state_altgr[winuser::VK_CONTROL as usize] = 0x80; + + let keyboard_state_empty = [0u8; 256]; + + for v_key in 0..=255 { + let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl); + let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl); + if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) { + if noaltgr != altgr { + USES_ALTGR.store(true, Ordering::SeqCst); + return true; + } + } } + + USES_ALTGR.store(false, Ordering::SeqCst); + false } - mods } pub fn vkey_to_winit_vkey(vkey: c_int) -> Option { From 09182dc093b4e8469fe5833b6643006e70b767c9 Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Tue, 5 Mar 2019 18:58:14 -0700 Subject: [PATCH 07/13] Use `XRRGetScreenResourcesCurrent` when avail. (#801) * Use `XRRGetScreenResourcesCurrent` when avail. Signed-off-by: Hal Gentz * Changelog Signed-off-by: Hal Gentz --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/monitor.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ab516efae..2267774dfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit. - Rename `MonitorId` to `MonitorHandle`. - Removed `serde` implementations from `ControlFlow`. +- On X11, we will use the faster `XRRGetScreenResourcesCurrent` function instead of `XRRGetScreenResources` when available. - On macOS, fix keycodes being incorrect when using a non-US keyboard layout. - On Wayland, fix `with_title()` not setting the windows title - On Wayland, add `set_wayland_theme()` to control client decoration color theme diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index a97809f225..4954980530 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -131,9 +131,14 @@ impl XConnection { fn query_monitor_list(&self) -> Vec { unsafe { let root = (self.xlib.XDefaultRootWindow)(self.display); - // WARNING: this function is supposedly very slow, on the order of hundreds of ms. - // Upon failure, `resources` will be null. - let resources = (self.xrandr.XRRGetScreenResources)(self.display, root); + let resources = if version_is_at_least(1, 3) { + (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) + } else { + // WARNING: this function is supposedly very slow, on the order of hundreds of ms. + // Upon failure, `resources` will be null. + (self.xrandr.XRRGetScreenResources)(self.display, root) + }; + if resources.is_null() { panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist."); } From fc481b6d6da00563e862e9f6d117d684f27901ea Mon Sep 17 00:00:00 2001 From: Osspial Date: Wed, 6 Mar 2019 21:50:13 -0500 Subject: [PATCH 08/13] Update winit to 0.19.0 (#798) * Update winit to 0.19.0 * Update date for 0.19 --- CHANGELOG.md | 3 +++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2267774dfc..8036e74e0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,9 @@ - `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit. - Rename `MonitorId` to `MonitorHandle`. - Removed `serde` implementations from `ControlFlow`. + +# Version 0.19.0 (2019-03-06) + - On X11, we will use the faster `XRRGetScreenResourcesCurrent` function instead of `XRRGetScreenResources` when available. - On macOS, fix keycodes being incorrect when using a non-US keyboard layout. - On Wayland, fix `with_title()` not setting the windows title diff --git a/Cargo.toml b/Cargo.toml index 83b367a5bd..35343de22e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.18.1" +version = "0.19.0" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." keywords = ["windowing"] diff --git a/README.md b/README.md index 1966101944..217d5f20d9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ```toml [dependencies] -winit = "0.18.1" +winit = "0.19.0" ``` ## [Documentation](https://docs.rs/winit) From 17a240cd43638b43c03eba0044ecc69ef219061b Mon Sep 17 00:00:00 2001 From: Osspial Date: Tue, 19 Mar 2019 22:19:41 -0400 Subject: [PATCH 09/13] On Windows, fix CursorMoved(0, 0) getting sent on focus (#819) * On Windows, fix CursorMoved(0, 0) getting sent on focus * Add changelog entry --- CHANGELOG.md | 2 ++ src/platform_impl/windows/event_loop.rs | 12 +----------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8036e74e0b..725253d694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ - Rename `MonitorId` to `MonitorHandle`. - Removed `serde` implementations from `ControlFlow`. +- On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus. + # Version 0.19.0 (2019-03-06) - On X11, we will use the faster `XRRGetScreenResourcesCurrent` function instead of `XRRGetScreenResources` when available. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index ad4f39976d..a820ba5d21 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1335,22 +1335,12 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_SETFOCUS => { - use event::WindowEvent::{Focused, CursorMoved}; + use event::WindowEvent::Focused; subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Focused(true), }); - let x = windowsx::GET_X_LPARAM(lparam) as f64; - let y = windowsx::GET_Y_LPARAM(lparam) as f64; - let dpi_factor = get_hwnd_scale_factor(window); - let position = LogicalPosition::from_physical((x, y), dpi_factor); - - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: CursorMoved { device_id: DEVICE_ID, position, modifiers: event::get_key_mods() }, - }); - 0 }, From 6b7bd32c8e4f0b10b96be91d55b27d15dfc22856 Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Tue, 19 Mar 2019 20:20:03 -0600 Subject: [PATCH 10/13] Add contact info. (#818) Signed-off-by: Hal Gentz --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 217d5f20d9..428669ecb5 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ winit = "0.19.0" ## [Documentation](https://docs.rs/winit) +## Contact Us + +Join us in any of these: + +[![Freenode](https://img.shields.io/badge/freenode.net-%23glutin-red.svg)](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a) +[![Matrix](https://img.shields.io/badge/Matrix-%23Glutin%3Amatrix.org-blueviolet.svg)](https://matrix.to/#/#Glutin:matrix.org) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + ## Usage Winit is a window creation and management library. It can create windows and lets you handle From cb93554938f80b327bc73700c2fc33fd01649690 Mon Sep 17 00:00:00 2001 From: Tobias Kortkamp Date: Fri, 22 Mar 2019 15:44:00 +0100 Subject: [PATCH 11/13] Fix build on FreeBSD (#815) * Fix build on FreeBSD error[E0432]: unresolved import `libc::__errno_location` --> src/platform/linux/x11/mod.rs:22:85 | 22 | use libc::{select, fd_set, FD_SET, FD_ZERO, FD_ISSET, EINTR, EINVAL, ENOMEM, EBADF, __errno_location}; | ^^^^^^^^^^^^^^^^ no `__errno_location` in the root __errno_location is called __error on FreeBSD and __errno on Open- and NetBSD. Signed-off-by: Tobias Kortkamp * Import __error / __errno on *BSD as __errno_location Signed-off-by: Tobias Kortkamp * Add changelog entry Signed-off-by: Tobias Kortkamp --- CHANGELOG.md | 1 + src/platform_impl/linux/x11/mod.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 725253d694..d62c34eab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ - Removed `serde` implementations from `ControlFlow`. - On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus. +- On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend. # Version 0.19.0 (2019-03-06) diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index a98556c107..1cd1bd561b 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -19,7 +19,13 @@ use std::collections::HashMap; use std::ffi::CStr; use std::ops::Deref; use std::os::raw::*; -use libc::{select, fd_set, FD_SET, FD_ZERO, FD_ISSET, EINTR, EINVAL, ENOMEM, EBADF, __errno_location}; +use libc::{select, fd_set, FD_SET, FD_ZERO, FD_ISSET, EINTR, EINVAL, ENOMEM, EBADF}; +#[cfg(target_os = "linux")] +use libc::__errno_location; +#[cfg(target_os = "freebsd")] +use libc::__error as __errno_location; +#[cfg(any(target_os = "netbsd", target_os = "openbsd"))] +use libc::__errno as __errno_location; use std::sync::{Arc, mpsc, Weak}; use std::sync::atomic::{self, AtomicBool}; From 9874181ccdb7e93e4a5114c2ed12a2c752ccdae4 Mon Sep 17 00:00:00 2001 From: TakWolf Date: Tue, 26 Mar 2019 02:05:07 +0800 Subject: [PATCH 12/13] fix command key event left and right reverse on macOS (#810) * fix command key event left and right reverse on macOS https://github.com/tomaka/winit/issues/808 * update changelog --- CHANGELOG.md | 1 + src/platform_impl/macos/event_loop.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d62c34eab7..3ea4f9d82a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ - Removed `serde` implementations from `ControlFlow`. - On Windows, fix `CursorMoved(0, 0)` getting dispatched on window focus. +- On macOS, fix command key event left and right reverse. - On FreeBSD, NetBSD, and OpenBSD, fix build of X11 backend. # Version 0.19.0 (2019-03-06) diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index f7a1642e8d..8442da6db9 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -660,8 +660,8 @@ pub fn scancode_to_keycode(code: c_ushort) -> Option { 0x33 => events::VirtualKeyCode::Back, //0x34 => unkown, 0x35 => events::VirtualKeyCode::Escape, - 0x36 => events::VirtualKeyCode::LWin, - 0x37 => events::VirtualKeyCode::RWin, + 0x36 => events::VirtualKeyCode::RWin, + 0x37 => events::VirtualKeyCode::LWin, 0x38 => events::VirtualKeyCode::LShift, //0x39 => Caps lock, 0x3a => events::VirtualKeyCode::LAlt, From 20b09c451496dc89f56213b1aa88c47451fe8840 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 7 Apr 2019 07:25:37 +0200 Subject: [PATCH 13/13] Add additional numpad key mappings (#805) * Add additional numpad key mappings Since some platforms have already used the existing `Add`, `Subtract` and `Divide` codes to map numpad keys, the X11 and Wayland platform has been updated to achieve parity between platforms. On macOS only the `Subtract` numpad key had to be added. Since the numpad key is different from the normal keys, an alternative option would be to add new `NumpadAdd`, `NumpadSubtract` and `NumpadDivide` actions, however I think in this case it should be fine to map them to the same virtual key code. * Add Numpad PageUp/Down, Home and End on Wayland --- CHANGELOG.md | 3 +++ src/platform_impl/linux/wayland/keyboard.rs | 7 +++++++ src/platform_impl/linux/x11/events.rs | 6 +++--- src/platform_impl/macos/event_loop.rs | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ea4f9d82a..fc7c2c994c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,9 @@ - On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area. - On Windows, only vertical mouse wheel events were handled. Now, horizontal mouse wheel events are also handled. - On Windows, ignore the AltGr key when populating the `ModifersState` type. +- On Linux, the numpad's add, subtract and divide keys are now mapped to the `Add`, `Subtract` and `Divide` virtual key codes +- On macOS, the numpad's subtract key has been added to the `Subtract` mapping +- On Wayland, the numpad's home, end, page up and page down keys are now mapped to the `Home`, `End`, `PageUp` and `PageDown` virtual key codes # Version 0.18.1 (2018-12-30) diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index 420d0ab660..d093094931 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -295,6 +295,13 @@ fn keysym_to_vkey(keysym: u32) -> Option { keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), + keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::Add), + keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract), + keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide), + keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), + keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), + keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), + keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End), // => Some(VirtualKeyCode::OEM102), // => Some(VirtualKeyCode::Period), // => Some(VirtualKeyCode::Playpause), diff --git a/src/platform_impl/linux/x11/events.rs b/src/platform_impl/linux/x11/events.rs index 037ee4c482..853064134a 100644 --- a/src/platform_impl/linux/x11/events.rs +++ b/src/platform_impl/linux/x11/events.rs @@ -82,11 +82,11 @@ pub fn keysym_to_element(keysym: libc::c_uint) -> Option { ffi::XK_KP_Delete => events::VirtualKeyCode::Delete, ffi::XK_KP_Equal => events::VirtualKeyCode::NumpadEquals, //ffi::XK_KP_Multiply => events::VirtualKeyCode::NumpadMultiply, - //ffi::XK_KP_Add => events::VirtualKeyCode::NumpadAdd, + ffi::XK_KP_Add => events::VirtualKeyCode::Add, //ffi::XK_KP_Separator => events::VirtualKeyCode::Kp_separator, - //ffi::XK_KP_Subtract => events::VirtualKeyCode::NumpadSubtract, + ffi::XK_KP_Subtract => events::VirtualKeyCode::Subtract, //ffi::XK_KP_Decimal => events::VirtualKeyCode::Kp_decimal, - //ffi::XK_KP_Divide => events::VirtualKeyCode::NumpadDivide, + ffi::XK_KP_Divide => events::VirtualKeyCode::Divide, ffi::XK_KP_0 => events::VirtualKeyCode::Numpad0, ffi::XK_KP_1 => events::VirtualKeyCode::Numpad1, ffi::XK_KP_2 => events::VirtualKeyCode::Numpad2, diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 8442da6db9..42ad8cb433 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -683,6 +683,7 @@ pub fn scancode_to_keycode(code: c_ushort) -> Option { 0x4a => events::VirtualKeyCode::VolumeDown, 0x4b => events::VirtualKeyCode::Divide, 0x4c => events::VirtualKeyCode::NumpadEnter, + 0x4e => events::VirtualKeyCode::Subtract, //0x4d => unkown, 0x4e => events::VirtualKeyCode::Subtract, 0x4f => events::VirtualKeyCode::F18,