From a55ac6eb8f3ccd15bd388c6e87b1ad270bdfb7ec Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sat, 22 Apr 2017 13:52:35 -0700 Subject: [PATCH] Richer input events This expands input events to represent sub-pixel mouse positions, devices responsible for generating events, and raw device-oriented events. The X11 back end is refactored to make full use of the new expressiveness. Other backends have had new functionality minimally stubbed out, save for the macos backend which already supports sub-pixel mouse positions. --- examples/cursor.rs | 4 +- examples/fullscreen.rs | 5 +- examples/grabbing.rs | 9 +- src/api_transition.rs | 3 + src/events.rs | 76 ++- src/lib.rs | 16 + src/platform/ios/mod.rs | 4 + src/platform/linux/mod.rs | 16 +- src/platform/linux/wayland/event_loop.rs | 67 ++- src/platform/linux/wayland/keyboard.rs | 29 +- src/platform/linux/wayland/mod.rs | 3 + src/platform/linux/x11/events.rs | 4 +- src/platform/linux/x11/input.rs | 391 ------------ src/platform/linux/x11/mod.rs | 729 +++++++++++++++++++++-- src/platform/linux/x11/window.rs | 285 +-------- src/platform/macos/events_loop.rs | 80 ++- src/platform/macos/mod.rs | 3 + src/platform/windows/callback.rs | 54 +- src/platform/windows/event.rs | 4 +- 19 files changed, 997 insertions(+), 785 deletions(-) delete mode 100644 src/platform/linux/x11/input.rs diff --git a/examples/cursor.rs b/examples/cursor.rs index 0f65e10ab06..dcb379714cd 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -1,6 +1,6 @@ extern crate winit; -use winit::{Event, ElementState, MouseCursor, WindowEvent}; +use winit::{Event, ElementState, MouseCursor, WindowEvent, KeyboardInput}; fn main() { let events_loop = winit::EventsLoop::new(); @@ -13,7 +13,7 @@ fn main() { events_loop.run_forever(|event| { match event { - Event::WindowEvent { event: WindowEvent::KeyboardInput(ElementState::Pressed, _, _, _), .. } => { + Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. }, .. } => { println!("Setting cursor to \"{:?}\"", cursors[cursor_idx]); window.set_cursor(cursors[cursor_idx]); if cursor_idx < cursors.len() - 1 { diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 308d9dcf3ec..1c1d8bdbcc1 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -37,10 +37,13 @@ fn main() { winit::Event::WindowEvent { event, .. } => { match event { winit::WindowEvent::Closed => events_loop.interrupt(), - winit::WindowEvent::KeyboardInput(_, _, Some(winit::VirtualKeyCode::Escape), _) => events_loop.interrupt(), + winit::WindowEvent::KeyboardInput { + input: winit::KeyboardInput { virtual_keycode: Some(winit::VirtualKeyCode::Escape), .. }, .. + } => events_loop.interrupt(), _ => () } }, + _ => {} } }); } diff --git a/examples/grabbing.rs b/examples/grabbing.rs index d156da43d36..2ba327f431a 100644 --- a/examples/grabbing.rs +++ b/examples/grabbing.rs @@ -1,6 +1,6 @@ extern crate winit; -use winit::{WindowEvent, ElementState}; +use winit::{WindowEvent, ElementState, KeyboardInput}; fn main() { let events_loop = winit::EventsLoop::new(); @@ -16,7 +16,7 @@ fn main() { match event { winit::Event::WindowEvent { event, .. } => { match event { - WindowEvent::KeyboardInput(ElementState::Pressed, _, _, _) => { + WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. } => { if grabbed { grabbed = false; window.set_cursor_state(winit::CursorState::Normal) @@ -30,13 +30,14 @@ fn main() { WindowEvent::Closed => events_loop.interrupt(), - a @ WindowEvent::MouseMoved(_, _) => { + a @ WindowEvent::MouseMoved { .. } => { println!("{:?}", a); }, _ => (), } - }, + } + _ => {} } }); } diff --git a/src/api_transition.rs b/src/api_transition.rs index 464a7f05792..56a259344a0 100644 --- a/src/api_transition.rs +++ b/src/api_transition.rs @@ -57,6 +57,9 @@ macro_rules! gen_api_transition { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(usize); + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct DeviceId; + pub struct Window2 { pub window: ::std::sync::Arc, events_loop: ::std::sync::Weak, diff --git a/src/events.rs b/src/events.rs index 851ef0c6ffe..ad27616bf92 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,12 +1,16 @@ use std::path::PathBuf; -use WindowId; +use {WindowId, DeviceId, AxisId, ButtonId}; #[derive(Clone, Debug)] pub enum Event { WindowEvent { window_id: WindowId, event: WindowEvent, - } + }, + DeviceEvent { + device_id: DeviceId, + event: DeviceEvent, + }, } #[derive(Clone, Debug)] @@ -35,31 +39,36 @@ pub enum WindowEvent { Focused(bool), /// An event from the keyboard has been received. - KeyboardInput(ElementState, ScanCode, Option, ModifiersState), + KeyboardInput { device_id: DeviceId, input: KeyboardInput }, /// The cursor has moved on the window. /// - /// The parameter are the (x,y) coords in pixels relative to the top-left corner of the window. - MouseMoved(i32, i32), + /// `position` is (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this + /// data is limited by the display area and it may have been transformed by the OS to implement effects such as + /// mouse acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. + MouseMoved { device_id: DeviceId, position: (f64, f64) }, /// The cursor has entered the window. - MouseEntered, + MouseEntered { device_id: DeviceId }, /// The cursor has left the window. - MouseLeft, + MouseLeft { device_id: DeviceId }, /// A mouse wheel movement or touchpad scroll occurred. - MouseWheel(MouseScrollDelta, TouchPhase), + MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase }, - /// An event from the mouse has been received. - MouseInput(ElementState, MouseButton), + /// An mouse button press has been received. + MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton }, /// Touchpad pressure event. /// /// At the moment, only supported on Apple forcetouch-capable macbooks. /// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad /// is being pressed) and stage (integer representing the click level). - TouchpadPressure(f32, i64), + TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 }, + + /// Motion on some analog axis not otherwise handled. May overlap with mouse motion. + AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 }, /// The window needs to be redrawn. Refresh, @@ -73,6 +82,48 @@ pub enum WindowEvent { Touch(Touch) } +/// Represents raw hardware events that are not associated with any particular window. +/// +/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person +/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because +/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs +/// may not match. +/// +/// Note that these events are delivered regardless of input focus. +#[derive(Clone, Debug)] +pub enum DeviceEvent { + Added, + Removed, + Motion { axis: AxisId, value: f64 }, + Button { button: ButtonId, state: ElementState }, + Key(KeyboardInput), + Text { codepoint: char }, +} + +#[derive(Debug, Clone, Copy)] +pub struct KeyboardInput { + /// Identifies the physical key pressed + /// + /// This should not change if the user adjusts the host's keyboard map. Use when the physical location of the + /// key is more important than the key's host GUI semantics, such as for movement controls in a first-person + /// game. + pub scancode: ScanCode, + + pub state: ElementState, + + /// Identifies the semantic meaning of the key + /// + /// Use when the semantics of the key are more important than the physical location of the key, such as when + /// implementing appropriate behavior for "page up." + pub virtual_keycode: Option, + + /// Modifier keys active at the time of this input. + /// + /// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from + /// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere. + pub modifiers: ModifiersState +} + #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum TouchPhase { Started, @@ -98,13 +149,14 @@ pub enum TouchPhase { /// /// Touch may be cancelled if for example window lost focus. pub struct Touch { + pub device_id: DeviceId, pub phase: TouchPhase, pub location: (f64,f64), /// unique identifier of a finger. pub id: u64 } -pub type ScanCode = u8; +pub type ScanCode = u32; #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum ElementState { diff --git a/src/lib.rs b/src/lib.rs index 00dba8bd561..913569d7547 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -169,6 +169,22 @@ pub struct Window { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(platform::WindowId); +/// Identifier of an input device. +/// +/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which +/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or +/// physical. Virtual devices typically aggregate inputs from multiple physical devices. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId(platform::DeviceId); + +/// Identifier for a specific analog axis on some device. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AxisId(u32); + +/// Identifier for a specific button on some device. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ButtonId(u32); + /// Provides a way to retreive events from the windows that were registered to it. // TODO: document usage in multiple threads pub struct EventsLoop { diff --git a/src/platform/ios/mod.rs b/src/platform/ios/mod.rs index 476eb1a3dfc..187154c9e9b 100644 --- a/src/platform/ios/mod.rs +++ b/src/platform/ios/mod.rs @@ -287,6 +287,7 @@ impl Window { let phase: i32 = msg_send![touch, phase]; state.events_queue.push_back(Event::Touch(Touch { + device_id: DEVICE_ID, id: touch_id, location: (location.x as f64, location.y as f64), phase: match phase { @@ -495,3 +496,6 @@ impl<'a> Iterator for PollEventsIterator<'a> { } } } + +// Constant device ID, to be removed when this backend is updated to report real device IDs. +const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 6e12ffe116c..18924b87bcb 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -58,6 +58,14 @@ pub enum WindowId { Wayland(wayland::WindowId) } +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum DeviceId { + #[doc(hidden)] + X(x11::DeviceId), + #[doc(hidden)] + Wayland(wayland::DeviceId) +} + #[derive(Clone)] pub enum MonitorId { #[doc(hidden)] @@ -137,8 +145,8 @@ impl Window2 { } }, - UnixBackend::X(ref connec) => { - x11::Window2::new(events_loop, connec, window, pl_attribs).map(Window2::X) + UnixBackend::X(_) => { + x11::Window2::new(events_loop, window, pl_attribs).map(Window2::X) }, UnixBackend::Error(_) => { // If the Backend is Error(), it is not possible to instanciate an EventsLoop at all, @@ -308,8 +316,8 @@ impl EventsLoop { EventsLoop::Wayland(wayland::EventsLoop::new(ctxt.clone())) }, - UnixBackend::X(_) => { - EventsLoop::X(x11::EventsLoop::new()) + UnixBackend::X(ref ctxt) => { + EventsLoop::X(x11::EventsLoop::new(ctxt.clone())) }, UnixBackend::Error(_) => { diff --git a/src/platform/linux/wayland/event_loop.rs b/src/platform/linux/wayland/event_loop.rs index efc9411d49e..60163e28c15 100644 --- a/src/platform/linux/wayland/event_loop.rs +++ b/src/platform/linux/wayland/event_loop.rs @@ -1,9 +1,9 @@ -use {WindowEvent as Event, ElementState, MouseButton, MouseScrollDelta, TouchPhase, ModifiersState}; +use {WindowEvent as Event, ElementState, MouseButton, MouseScrollDelta, TouchPhase, ModifiersState, KeyboardInput}; use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicBool; -use super::{DecoratedHandler, WindowId, WaylandContext}; +use super::{DecoratedHandler, WindowId, DeviceId, WaylandContext}; use wayland_client::{EventQueue, EventQueueHandle, Init, Proxy}; @@ -221,7 +221,7 @@ struct InputHandler { seat: Option, mouse: Option, mouse_focus: Option>, - mouse_location: (i32, i32), + mouse_location: (f64, f64), axis_buffer: Option<(f32, f32)>, axis_discrete_buffer: Option<(i32, i32)>, axis_state: TouchPhase, @@ -242,7 +242,7 @@ impl InputHandler { seat: ctxt.get_seat(), mouse: None, mouse_focus: None, - mouse_location: (0,0), + mouse_location: (0.0,0.0), axis_buffer: None, axis_discrete_buffer: None, axis_state: TouchPhase::Started, @@ -310,14 +310,17 @@ impl wl_pointer::Handler for InputHandler { surface_x: f64, surface_y: f64) { - self.mouse_location = (surface_x as i32, surface_y as i32); + self.mouse_location = (surface_x, surface_y); for window in &self.windows { if window.equals(surface) { self.mouse_focus = Some(window.clone()); let (w, h) = self.mouse_location; let mut guard = self.callback.lock().unwrap(); - guard.send_event(Event::MouseEntered, make_wid(window)); - guard.send_event(Event::MouseMoved(w, h), make_wid(window)); + guard.send_event(Event::MouseEntered { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)) }, + make_wid(window)); + guard.send_event(Event::MouseMoved { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), + position: (w, h) }, + make_wid(window)); break; } } @@ -332,7 +335,8 @@ impl wl_pointer::Handler for InputHandler { self.mouse_focus = None; for window in &self.windows { if window.equals(surface) { - self.callback.lock().unwrap().send_event(Event::MouseLeft, make_wid(window)); + self.callback.lock().unwrap().send_event(Event::MouseLeft { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)) }, + make_wid(window)); } } } @@ -344,10 +348,11 @@ impl wl_pointer::Handler for InputHandler { surface_x: f64, surface_y: f64) { - self.mouse_location = (surface_x as i32, surface_y as i32); + self.mouse_location = (surface_x, surface_y); if let Some(ref window) = self.mouse_focus { let (w,h) = self.mouse_location; - self.callback.lock().unwrap().send_event(Event::MouseMoved(w, h), make_wid(window)); + self.callback.lock().unwrap().send_event(Event::MouseMoved { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), + position: (w, h) }, make_wid(window)); } } @@ -371,7 +376,14 @@ impl wl_pointer::Handler for InputHandler { // TODO figure out the translation ? _ => return }; - self.callback.lock().unwrap().send_event(Event::MouseInput(state, button), make_wid(window)); + self.callback.lock().unwrap().send_event( + Event::MouseInput { + device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), + state: state, + button: button, + }, + make_wid(window) + ); } } @@ -403,18 +415,20 @@ impl wl_pointer::Handler for InputHandler { if let Some(ref window) = self.mouse_focus { if let Some((x, y)) = axis_discrete_buffer { self.callback.lock().unwrap().send_event( - Event::MouseWheel( - MouseScrollDelta::LineDelta(x as f32, y as f32), - self.axis_state - ), + Event::MouseWheel { + device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), + delta: MouseScrollDelta::LineDelta(x as f32, y as f32), + phase: self.axis_state, + }, make_wid(window) ); } else if let Some((x, y)) = axis_buffer { self.callback.lock().unwrap().send_event( - Event::MouseWheel( - MouseScrollDelta::PixelDelta(x as f32, y as f32), - self.axis_state - ), + Event::MouseWheel { + device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), + delta: MouseScrollDelta::PixelDelta(x as f32, y as f32), + phase: self.axis_state, + }, make_wid(window) ); } @@ -547,12 +561,15 @@ impl wl_keyboard::Handler for InputHandler { // anyway, as we need libxkbcommon to interpret it (it is // supposed to be serialized by the compositor using libxkbcommon) self.callback.lock().unwrap().send_event( - Event::KeyboardInput( - state, - key as u8, - None, - ModifiersState::default() - ), + Event::KeyboardInput { + device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), + input: KeyboardInput { + state: state, + scancode: key, + virtual_keycode: None, + modifiers: ModifiersState::default(), + }, + }, wid ); }, diff --git a/src/platform/linux/wayland/keyboard.rs b/src/platform/linux/wayland/keyboard.rs index 95e70b032b2..4a8ed8759e8 100644 --- a/src/platform/linux/wayland/keyboard.rs +++ b/src/platform/linux/wayland/keyboard.rs @@ -1,10 +1,10 @@ use std::sync::{Arc, Mutex}; -use {VirtualKeyCode, ElementState, WindowEvent as Event}; +use {VirtualKeyCode, ElementState, WindowEvent as Event, KeyboardInput}; use events::ModifiersState; -use super::{wayland_kbd, EventsLoopSink, WindowId}; +use super::{wayland_kbd, EventsLoopSink, WindowId, DeviceId}; use wayland_client::EventQueueHandle; use wayland_client::protocol::wl_keyboard; @@ -39,17 +39,20 @@ impl wayland_kbd::Handler for KbdHandler { let vkcode = key_to_vkey(rawkey, keysym); let mut guard = self.sink.lock().unwrap(); guard.send_event( - Event::KeyboardInput( - state, - rawkey as u8, - vkcode, - ModifiersState { - shift: mods.shift, - ctrl: mods.ctrl, - alt: mods.alt, - logo: mods.logo - } - ), + Event::KeyboardInput { + device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)), + input: KeyboardInput { + state: state, + scancode: rawkey, + virtual_keycode: vkcode, + modifiers: ModifiersState { + shift: mods.shift, + ctrl: mods.ctrl, + alt: mods.alt, + logo: mods.logo + }, + }, + }, wid ); // send char event only on key press, not release diff --git a/src/platform/linux/wayland/mod.rs b/src/platform/linux/wayland/mod.rs index f2ae3477d89..9567d57e698 100644 --- a/src/platform/linux/wayland/mod.rs +++ b/src/platform/linux/wayland/mod.rs @@ -15,3 +15,6 @@ mod context; mod event_loop; mod keyboard; mod window; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId; diff --git a/src/platform/linux/x11/events.rs b/src/platform/linux/x11/events.rs index 4754b1dc871..6bae009ab9e 100644 --- a/src/platform/linux/x11/events.rs +++ b/src/platform/linux/x11/events.rs @@ -2,8 +2,8 @@ use {events, libc}; use super::ffi; use VirtualKeyCode; -pub fn keycode_to_element(scancode: libc::c_uint) -> Option { - Some(match scancode { +pub fn keysym_to_element(keysym: libc::c_uint) -> Option { + Some(match keysym { ffi::XK_BackSpace => events::VirtualKeyCode::Back, ffi::XK_Tab => events::VirtualKeyCode::Tab, //ffi::XK_Linefeed => events::VirtualKeyCode::Linefeed, diff --git a/src/platform/linux/x11/input.rs b/src/platform/linux/x11/input.rs deleted file mode 100644 index 6f05704cb51..00000000000 --- a/src/platform/linux/x11/input.rs +++ /dev/null @@ -1,391 +0,0 @@ -use std::sync::Arc; - -use libc; -use std::{mem, ptr}; -use std::ffi::CString; -use std::slice::from_raw_parts; - -use WindowAttributes; - -use events::WindowEvent as Event; -use events::ModifiersState; - -use super::{events, ffi}; -use super::XConnection; - -#[derive(Debug)] -enum AxisType { - HorizontalScroll, - VerticalScroll -} - -#[derive(Debug)] -struct Axis { - id: i32, - device_id: i32, - axis_number: i32, - axis_type: AxisType, - scroll_increment: f64, -} - -#[derive(Debug)] -struct AxisValue { - device_id: i32, - axis_number: i32, - value: f64 -} - -struct InputState { - /// Last-seen cursor position within a window in (x, y) - /// coordinates - cursor_pos: (f64, f64), - /// Last-seen positions of axes, used to report delta - /// movements when a new absolute axis value is received - axis_values: Vec -} - -pub struct XInputEventHandler { - display: Arc, - window: ffi::Window, - ic: ffi::XIC, - axis_list: Vec, - current_state: InputState, - multitouch: bool, -} - -impl XInputEventHandler { - pub fn new(display: &Arc, window: ffi::Window, ic: ffi::XIC, - window_attrs: &WindowAttributes) -> XInputEventHandler { - // query XInput support - let mut opcode: libc::c_int = 0; - let mut event: libc::c_int = 0; - let mut error: libc::c_int = 0; - let xinput_str = CString::new("XInputExtension").unwrap(); - - unsafe { - if (display.xlib.XQueryExtension)(display.display, xinput_str.as_ptr(), &mut opcode, &mut event, &mut error) == ffi::False { - panic!("XInput not available") - } - } - - let mut xinput_major_ver = ffi::XI_2_Major; - let mut xinput_minor_ver = ffi::XI_2_Minor; - - unsafe { - if (display.xinput2.XIQueryVersion)(display.display, &mut xinput_major_ver, &mut xinput_minor_ver) != ffi::Success as libc::c_int { - panic!("Unable to determine XInput version"); - } - } - - // specify the XInput events we want to receive. - // Button clicks and mouse events are handled via XInput - // events. Key presses are still handled via plain core - // X11 events. - let mut mask: [libc::c_uchar; 3] = [0; 3]; - let mut input_event_mask = ffi::XIEventMask { - deviceid: ffi::XIAllMasterDevices, - mask_len: mask.len() as i32, - mask: mask.as_mut_ptr() - }; - let events = &[ - ffi::XI_ButtonPress, - ffi::XI_ButtonRelease, - ffi::XI_Motion, - ffi::XI_Enter, - ffi::XI_Leave, - ffi::XI_FocusIn, - ffi::XI_FocusOut, - ffi::XI_TouchBegin, - ffi::XI_TouchUpdate, - ffi::XI_TouchEnd, - ]; - for event in events { - ffi::XISetMask(&mut mask, *event); - } - - unsafe { - match (display.xinput2.XISelectEvents)(display.display, window, &mut input_event_mask, 1) { - status if status as u8 == ffi::Success => (), - err => panic!("Failed to select events {:?}", err) - } - } - - XInputEventHandler { - display: display.clone(), - window: window, - ic: ic, - axis_list: read_input_axis_info(display), - current_state: InputState { - cursor_pos: (0.0, 0.0), - axis_values: Vec::new() - }, - multitouch: window_attrs.multitouch, - } - } - - pub fn translate_key_event(&self, event: &mut ffi::XKeyEvent) -> Vec { - use events::WindowEvent::{KeyboardInput, ReceivedCharacter}; - use events::ElementState::{Pressed, Released}; - - let mut translated_events = Vec::new(); - - let state; - if event.type_ == ffi::KeyPress { - let raw_ev: *mut ffi::XKeyEvent = event; - unsafe { (self.display.xlib.XFilterEvent)(mem::transmute(raw_ev), self.window) }; - state = Pressed; - } else { - state = Released; - } - - let mut kp_keysym = 0; - - let mut ev_mods = ModifiersState::default(); - - let written = unsafe { - use std::str; - - let mut buffer: [u8; 16] = [mem::uninitialized(); 16]; - let raw_ev: *mut ffi::XKeyEvent = event; - let count = (self.display.xlib.Xutf8LookupString)(self.ic, mem::transmute(raw_ev), - mem::transmute(buffer.as_mut_ptr()), - buffer.len() as libc::c_int, &mut kp_keysym, ptr::null_mut()); - - { - // Translate x event state to mods - let state = event.state; - if (state & ffi::Mod1Mask) != 0 { - ev_mods.alt = true; - } - - if (state & ffi::ShiftMask) != 0 { - ev_mods.shift = true; - } - - if (state & ffi::ControlMask) != 0 { - ev_mods.ctrl = true; - } - - if (state & ffi::Mod4Mask) != 0 { - ev_mods.logo = true; - } - } - - str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string() - }; - - for chr in written.chars() { - translated_events.push(ReceivedCharacter(chr)); - } - - let mut keysym = unsafe { - (self.display.xlib.XKeycodeToKeysym)(self.display.display, event.keycode as ffi::KeyCode, 0) - }; - - if (ffi::XK_KP_Space as libc::c_ulong <= keysym) && (keysym <= ffi::XK_KP_9 as libc::c_ulong) { - keysym = kp_keysym - }; - - let vkey = events::keycode_to_element(keysym as libc::c_uint); - - translated_events.push(KeyboardInput(state, event.keycode as u8, vkey, ev_mods)); - translated_events - } - - pub fn translate_event(&mut self, cookie: &ffi::XGenericEventCookie) -> Option { - use events::WindowEvent::{Focused, MouseEntered, MouseInput, MouseLeft, MouseMoved, MouseWheel}; - use events::ElementState::{Pressed, Released}; - use events::MouseButton::{Left, Right, Middle}; - use events::MouseScrollDelta::LineDelta; - use events::{Touch, TouchPhase}; - - match cookie.evtype { - ffi::XI_ButtonPress | ffi::XI_ButtonRelease => { - let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)}; - if self.multitouch && (event_data.flags & ffi::XIPointerEmulated) != 0 { - // Deliver multi-touch events instead of emulated mouse events. - return None - } - let state = if cookie.evtype == ffi::XI_ButtonPress { - Pressed - } else { - Released - }; - match event_data.detail as u32 { - ffi::Button1 => Some(MouseInput(state, Left)), - ffi::Button2 => Some(MouseInput(state, Middle)), - ffi::Button3 => Some(MouseInput(state, Right)), - ffi::Button4 | ffi::Button5 => { - if event_data.flags & ffi::XIPointerEmulated == 0 { - // scroll event from a traditional wheel with - // distinct 'clicks' - let delta = if event_data.detail as u32 == ffi::Button4 { - 1.0 - } else { - -1.0 - }; - Some(MouseWheel(LineDelta(0.0, delta), TouchPhase::Moved)) - } else { - // emulated button event from a touch/smooth-scroll - // event. Ignore these events and handle scrolling - // via XI_Motion event handler instead - None - } - } - _ => None - } - }, - ffi::XI_Motion => { - let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)}; - if self.multitouch && (event_data.flags & ffi::XIPointerEmulated) != 0 { - // Deliver multi-touch events instead of emulated mouse events. - return None - } - let axis_state = event_data.valuators; - let mask = unsafe{ from_raw_parts(axis_state.mask, axis_state.mask_len as usize) }; - let mut axis_count = 0; - - let mut scroll_delta = (0.0, 0.0); - for axis_id in 0..axis_state.mask_len { - if ffi::XIMaskIsSet(&mask, axis_id) { - let axis_value = unsafe{*axis_state.values.offset(axis_count)}; - let delta = calc_scroll_deltas(event_data, axis_id, axis_value, &self.axis_list, - &mut self.current_state.axis_values); - scroll_delta.0 += delta.0; - scroll_delta.1 += delta.1; - axis_count += 1; - } - } - - if scroll_delta.0.abs() > 0.0 || scroll_delta.1.abs() > 0.0 { - Some(MouseWheel(LineDelta(scroll_delta.0 as f32, scroll_delta.1 as f32), - TouchPhase::Moved)) - } else { - let new_cursor_pos = (event_data.event_x, event_data.event_y); - if new_cursor_pos != self.current_state.cursor_pos { - self.current_state.cursor_pos = new_cursor_pos; - Some(MouseMoved(new_cursor_pos.0 as i32, new_cursor_pos.1 as i32)) - } else { - None - } - } - }, - ffi::XI_Enter => { - // axis movements whilst the cursor is outside the window - // will alter the absolute value of the axes. We only want to - // report changes in the axis value whilst the cursor is above - // our window however, so clear the previous axis state whenever - // the cursor re-enters the window - self.current_state.axis_values.clear(); - Some(MouseEntered) - }, - ffi::XI_Leave => Some(MouseLeft), - ffi::XI_FocusIn => Some(Focused(true)), - ffi::XI_FocusOut => Some(Focused(false)), - ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { - if !self.multitouch { - return None - } - let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)}; - let phase = match cookie.evtype { - ffi::XI_TouchBegin => TouchPhase::Started, - ffi::XI_TouchUpdate => TouchPhase::Moved, - ffi::XI_TouchEnd => TouchPhase::Ended, - _ => unreachable!() - }; - Some(Event::Touch(Touch { - phase: phase, - location: (event_data.event_x, event_data.event_y), - id: event_data.detail as u64, - })) - } - _ => None - } - } -} - -fn read_input_axis_info(display: &Arc) -> Vec { - let mut axis_list = Vec::new(); - let mut device_count = 0; - - // Check all input devices for scroll axes. - let devices = unsafe{ - (display.xinput2.XIQueryDevice)(display.display, ffi::XIAllDevices, &mut device_count) - }; - for i in 0..device_count { - let device = unsafe { *(devices.offset(i as isize)) }; - for k in 0..device.num_classes { - let class = unsafe { *(device.classes.offset(k as isize)) }; - match unsafe { (*class)._type } { - // Note that scroll axis - // are reported both as 'XIScrollClass' and 'XIValuatorClass' - // axes. For the moment we only care about scrolling axes. - ffi::XIScrollClass => { - let scroll_class: &ffi::XIScrollClassInfo = unsafe{mem::transmute(class)}; - axis_list.push(Axis{ - id: scroll_class.sourceid, - device_id: device.deviceid, - axis_number: scroll_class.number, - axis_type: match scroll_class.scroll_type { - ffi::XIScrollTypeHorizontal => AxisType::HorizontalScroll, - ffi::XIScrollTypeVertical => AxisType::VerticalScroll, - _ => { unreachable!() } - }, - scroll_increment: scroll_class.increment, - }) - }, - _ => {} - } - } - } - - unsafe { - (display.xinput2.XIFreeDeviceInfo)(devices); - } - - axis_list -} - -/// Given an input motion event for an axis and the previous -/// state of the axes, return the horizontal/vertical -/// scroll deltas -fn calc_scroll_deltas(event: &ffi::XIDeviceEvent, - axis_id: i32, - axis_value: f64, - axis_list: &[Axis], - prev_axis_values: &mut Vec) -> (f64, f64) { - let prev_value_pos = prev_axis_values.iter().position(|prev_axis| { - prev_axis.device_id == event.sourceid && - prev_axis.axis_number == axis_id - }); - let delta = match prev_value_pos { - Some(idx) => prev_axis_values[idx].value - axis_value, - None => 0.0 - }; - - let new_axis_value = AxisValue{ - device_id: event.sourceid, - axis_number: axis_id, - value: axis_value - }; - - match prev_value_pos { - Some(idx) => prev_axis_values[idx] = new_axis_value, - None => prev_axis_values.push(new_axis_value) - } - - let mut scroll_delta = (0.0, 0.0); - - for axis in axis_list.iter() { - if axis.id == event.sourceid && - axis.axis_number == axis_id { - match axis.axis_type { - AxisType::HorizontalScroll => scroll_delta.0 = delta / axis.scroll_increment, - AxisType::VerticalScroll => scroll_delta.1 = delta / axis.scroll_increment - } - } - } - - scroll_delta -} - diff --git a/src/platform/linux/x11/mod.rs b/src/platform/linux/x11/mod.rs index 43721b7b21d..f613776a4b3 100644 --- a/src/platform/linux/x11/mod.rs +++ b/src/platform/linux/x11/mod.rs @@ -1,18 +1,22 @@ #![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor}; -pub use self::window::{Window, XWindow, PollEventsIterator, WaitEventsIterator, WindowProxy}; +pub use self::window::{Window, XWindow, WindowProxy}; pub use self::xdisplay::{XConnection, XNotSupported, XError}; pub mod ffi; use platform::PlatformSpecificWindowBuilderAttributes; -use CreationError; +use {CreationError, Event, WindowEvent, DeviceEvent, AxisId, ButtonId, KeyboardInput}; -use std::sync::Arc; +use std::{mem, ptr, slice}; +use std::sync::{Arc, Mutex, Weak}; +use std::collections::HashMap; +use std::ffi::CStr; + +use libc::{self, c_uchar, c_char, c_int}; mod events; -mod input; mod monitor; mod window; mod xdisplay; @@ -25,16 +29,76 @@ mod xdisplay; // the one generated by the macro. pub struct EventsLoop { - windows: ::std::sync::Mutex>>, interrupted: ::std::sync::atomic::AtomicBool, + display: Arc, + wm_delete_window: ffi::Atom, + windows: Mutex>, + devices: Mutex>, + xi2ext: XExtension, + root: ffi::Window, } impl EventsLoop { - pub fn new() -> EventsLoop { - EventsLoop { - windows: ::std::sync::Mutex::new(vec![]), + pub fn new(display: Arc) -> EventsLoop { + let wm_delete_window = unsafe { (display.xlib.XInternAtom)(display.display, b"WM_DELETE_WINDOW\0".as_ptr() as *const c_char, 0) }; + display.check_errors().expect("Failed to call XInternAtom"); + + let xi2ext = unsafe { + let mut result = XExtension { + opcode: mem::uninitialized(), + first_event_id: mem::uninitialized(), + first_error_id: mem::uninitialized(), + }; + let res = (display.xlib.XQueryExtension)( + display.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); + if res == ffi::False { + panic!("X server missing XInput extension"); + } + result + }; + + unsafe { + let mut xinput_major_ver = ffi::XI_2_Major; + let mut xinput_minor_ver = ffi::XI_2_Minor; + + if (display.xinput2.XIQueryVersion)(display.display, &mut xinput_major_ver, &mut xinput_minor_ver) != ffi::Success as libc::c_int { + panic!("X server has XInput extension {}.{} but does not support XInput2", xinput_major_ver, xinput_minor_ver); + } + } + + let root = unsafe { (display.xlib.XDefaultRootWindow)(display.display) }; + + let result = EventsLoop { interrupted: ::std::sync::atomic::AtomicBool::new(false), + display: display, + wm_delete_window: wm_delete_window, + windows: Mutex::new(HashMap::new()), + devices: Mutex::new(HashMap::new()), + xi2ext: xi2ext, + root: root, + }; + + { + // Register for device hotplug events + let mask = ffi::XI_HierarchyChangedMask; + unsafe { + let mut event_mask = ffi::XIEventMask{ + deviceid: ffi::XIAllDevices, + mask: &mask as *const _ as *mut c_uchar, + mask_len: mem::size_of_val(&mask) as c_int, + }; + (result.display.xinput2.XISelectEvents)(result.display.display, root, + &mut event_mask as *mut ffi::XIEventMask, 1); + } + + result.init_device(ffi::XIAllDevices); } + + result } pub fn interrupt(&self) { @@ -42,41 +106,444 @@ impl EventsLoop { } pub fn poll_events(&self, mut callback: F) - where F: FnMut(::Event) + where F: FnMut(Event) { - let windows = self.windows.lock().unwrap(); - for window in windows.iter() { - for event in window.poll_events() { - callback(::Event::WindowEvent { - window_id: ::WindowId(::platform::WindowId::X(WindowId(&**window as *const Window as usize))), - event: event, - }) + let xlib = &self.display.xlib; + + let mut xev = unsafe { mem::uninitialized() }; + unsafe { + // Ensure XNextEvent won't block + let count = (xlib.XPending)(self.display.display); + if count == 0 { + return; } + + (xlib.XNextEvent)(self.display.display, &mut xev); } + self.process_event(&mut xev, &mut callback); } pub fn run_forever(&self, mut callback: F) - where F: FnMut(::Event) + where F: FnMut(Event) { self.interrupted.store(false, ::std::sync::atomic::Ordering::Relaxed); - // Yeah that's a very bad implementation. + let xlib = &self.display.xlib; + + let mut xev = unsafe { mem::uninitialized() }; + loop { - self.poll_events(|e| callback(e)); - ::std::thread::sleep(::std::time::Duration::from_millis(5)); + unsafe { (xlib.XNextEvent)(self.display.display, &mut xev) }; // Blocks as necessary + self.process_event(&mut xev, &mut callback); if self.interrupted.load(::std::sync::atomic::Ordering::Relaxed) { break; } } } + + pub fn device_name(&self, device: DeviceId) -> String { + let devices = self.devices.lock().unwrap(); + let device = devices.get(&device).unwrap(); + device.name.clone() + } + + fn process_event(&self, xev: &mut ffi::XEvent, callback: &mut F) + where F: FnMut(Event) + { + let xlib = &self.display.xlib; + + // Handle dead keys and other input method funtimes + if ffi::True == unsafe { (self.display.xlib.XFilterEvent)(xev, { let xev: &ffi::XAnyEvent = xev.as_ref(); xev.window }) } { + return; + } + + let xwindow = { let xev: &ffi::XAnyEvent = xev.as_ref(); xev.window }; + let wid = ::WindowId(::platform::WindowId::X(WindowId(xwindow))); + match xev.get_type() { + ffi::MappingNotify => { + unsafe { (xlib.XRefreshKeyboardMapping)(xev.as_mut()); } + self.display.check_errors().expect("Failed to call XRefreshKeyboardMapping"); + } + + ffi::ClientMessage => { + let client_msg: &ffi::XClientMessageEvent = xev.as_ref(); + + if client_msg.data.get_long(0) as ffi::Atom == self.wm_delete_window { + callback(Event::WindowEvent { window_id: wid, event: WindowEvent::Closed }) + } else { + // FIXME: Prone to spurious wakeups + callback(Event::WindowEvent { window_id: wid, event: WindowEvent::Awakened }) + } + } + + ffi::ConfigureNotify => { + let xev: &ffi::XConfigureEvent = xev.as_ref(); + let size = (xev.width, xev.height); + let position = (xev.x, xev.y); + // Gymnastics to ensure self.windows isn't locked when we invoke callback + let (resized, moved) = { + let mut windows = self.windows.lock().unwrap(); + let window_data = windows.get_mut(&WindowId(xwindow)).unwrap(); + if window_data.config.is_none() { + window_data.config = Some(WindowConfig::new(xev)); + (true, true) + } else { + let window = window_data.config.as_mut().unwrap(); + (if window.size != size { + window.size = size; + true + } else { false }, + if window.position != position { + window.position = position; + true + } else { false }) + } + }; + if resized { + callback(Event::WindowEvent { window_id: wid, event: WindowEvent::Resized(xev.width as u32, xev.height as u32) }); + } + if moved { + callback(Event::WindowEvent { window_id: wid, event: WindowEvent::Moved(xev.x as i32, xev.y as i32) }); + } + } + + ffi::Expose => { + callback(Event::WindowEvent { window_id: wid, event: WindowEvent::Refresh }); + } + + // FIXME: Use XInput2 + libxkbcommon for keyboard input! + ffi::KeyPress | ffi::KeyRelease => { + use events::ModifiersState; + use events::ElementState::{Pressed, Released}; + + let state; + if xev.get_type() == ffi::KeyPress { + state = Pressed; + } else { + state = Released; + } + + let xkev: &mut ffi::XKeyEvent = xev.as_mut(); + + let mut kp_keysym = 0; + + let mut ev_mods = ModifiersState::default(); + + if state == Pressed { + let written = unsafe { + use std::str; + + let mut windows = self.windows.lock().unwrap(); + let window_data = windows.get_mut(&WindowId(xwindow)).unwrap(); + let mut buffer: [u8; 16] = [mem::uninitialized(); 16]; + let count = (self.display.xlib.Xutf8LookupString)(window_data.ic, xkev, + mem::transmute(buffer.as_mut_ptr()), + buffer.len() as libc::c_int, &mut kp_keysym, ptr::null_mut()); + + { + // Translate x event state to mods + let state = xkev.state; + if (state & ffi::Mod1Mask) != 0 { + ev_mods.alt = true; + } + + if (state & ffi::ShiftMask) != 0 { + ev_mods.shift = true; + } + + if (state & ffi::ControlMask) != 0 { + ev_mods.ctrl = true; + } + + if (state & ffi::Mod4Mask) != 0 { + ev_mods.logo = true; + } + } + + str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string() + }; + + for chr in written.chars() { + callback(Event::WindowEvent { window_id: wid, event: WindowEvent::ReceivedCharacter(chr) }) + } + } + + let mut keysym = unsafe { + (self.display.xlib.XKeycodeToKeysym)(self.display.display, xkev.keycode as ffi::KeyCode, 0) + }; + + if (keysym >= ffi::XK_KP_Space as libc::c_ulong) && (keysym <= ffi::XK_KP_9 as libc::c_ulong) { + keysym = kp_keysym + }; + + let vkey = events::keysym_to_element(keysym as libc::c_uint); + + callback(Event::WindowEvent { window_id: wid, event: WindowEvent::KeyboardInput { + // Typical virtual core keyboard ID. xinput2 needs to be used to get a reliable value. + device_id: mkdid(3), + input: KeyboardInput { + state: state, + scancode: xkev.keycode, + virtual_keycode: vkey, + modifiers: ev_mods, + }, + }}); + } + + ffi::GenericEvent => { + let guard = if let Some(e) = GenericEventCookie::from_event(&self.display, *xev) { e } else { return }; + let xev = &guard.cookie; + if self.xi2ext.opcode != xev.extension { + return; + } + + use events::WindowEvent::{Focused, MouseEntered, MouseInput, MouseLeft, MouseMoved, MouseWheel, AxisMotion}; + use events::ElementState::{Pressed, Released}; + use events::MouseButton::{Left, Right, Middle, Other}; + use events::MouseScrollDelta::LineDelta; + use events::{Touch, TouchPhase}; + + match xev.evtype { + ffi::XI_ButtonPress | ffi::XI_ButtonRelease => { + let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + let wid = mkwid(xev.event); + let did = mkdid(xev.deviceid); + if (xev.flags & ffi::XIPointerEmulated) != 0 && self.windows.lock().unwrap().get(&WindowId(xev.event)).unwrap().multitouch { + // Deliver multi-touch events instead of emulated mouse events. + return; + } + let state = if xev.evtype == ffi::XI_ButtonPress { + Pressed + } else { + Released + }; + match xev.detail as u32 { + ffi::Button1 => callback(Event::WindowEvent { window_id: wid, event: + MouseInput { device_id: did, state: state, button: Left } }), + ffi::Button2 => callback(Event::WindowEvent { window_id: wid, event: + MouseInput { device_id: did, state: state, button: Middle } }), + ffi::Button3 => callback(Event::WindowEvent { window_id: wid, event: + MouseInput { device_id: did, state: state, button: Right } }), + + // Suppress emulated scroll wheel clicks, since we handle the real motion events for those. + // In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in + // turn) as axis motion, so we don't otherwise special-case these button presses. + 4 | 5 | 6 | 7 if xev.flags & ffi::XIPointerEmulated != 0 => {} + + x => callback(Event::WindowEvent { window_id: wid, event: MouseInput { device_id: did, state: state, button: Other(x as u8) } }) + } + } + ffi::XI_Motion => { + let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + let did = mkdid(xev.deviceid); + let wid = mkwid(xev.event); + let new_cursor_pos = (xev.event_x, xev.event_y); + + // Gymnastics to ensure self.windows isn't locked when we invoke callback + if { + let mut windows = self.windows.lock().unwrap(); + let window_data = windows.get_mut(&WindowId(xev.event)).unwrap(); + if Some(new_cursor_pos) != window_data.cursor_pos { + window_data.cursor_pos = Some(new_cursor_pos); + true + } else { false } + } { + callback(Event::WindowEvent { window_id: wid, event: MouseMoved { + device_id: did, + position: new_cursor_pos + }}); + } + + // More gymnastics, for self.devices + let mut events = Vec::new(); + { + let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; + let mut devices = self.devices.lock().unwrap(); + let physical_device = devices.get_mut(&DeviceId(xev.sourceid)).unwrap(); + + let mut value = xev.valuators.values; + for i in 0..xev.valuators.mask_len*8 { + if ffi::XIMaskIsSet(mask, i) { + if let Some(&mut (_, ref mut info)) = physical_device.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == i) { + let delta = (unsafe { *value } - info.position) / info.increment; + info.position = unsafe { *value }; + events.push(Event::WindowEvent { window_id: wid, event: MouseWheel { + device_id: did, + delta: match info.orientation { + ScrollOrientation::Horizontal => LineDelta(delta as f32, 0.0), + ScrollOrientation::Vertical => LineDelta(0.0, delta as f32), + }, + phase: TouchPhase::Moved, + }}); + } else { + events.push(Event::WindowEvent { window_id: wid, event: AxisMotion { + device_id: did, + axis: AxisId(i as u32), + value: unsafe { *value }, + }}); + } + value = unsafe { value.offset(1) }; + } + } + } + for event in events { + callback(event); + } + } + + ffi::XI_Enter => { + let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) }; + callback(Event::WindowEvent { window_id: mkwid(xev.event), event: MouseEntered { device_id: mkdid(xev.deviceid) } }) + } + ffi::XI_Leave => { + let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) }; + callback(Event::WindowEvent { window_id: mkwid(xev.event), event: MouseLeft { device_id: mkdid(xev.deviceid) } }) + } + ffi::XI_FocusIn => { + let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; + callback(Event::WindowEvent { window_id: mkwid(xev.event), event: Focused(true) }) + } + ffi::XI_FocusOut => { + let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; + callback(Event::WindowEvent { window_id: mkwid(xev.event), event: Focused(false) }) + } + + ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { + let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + let wid = mkwid(xev.event); + let phase = match xev.evtype { + ffi::XI_TouchBegin => TouchPhase::Started, + ffi::XI_TouchUpdate => TouchPhase::Moved, + ffi::XI_TouchEnd => TouchPhase::Ended, + _ => unreachable!() + }; + callback(Event::WindowEvent { window_id: wid, event: WindowEvent::Touch(Touch { + device_id: mkdid(xev.deviceid), + phase: phase, + location: (xev.event_x, xev.event_y), + id: xev.detail as u64, + })}) + } + + ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => { + let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; + if xev.flags & ffi::XIPointerEmulated == 0 { + callback(Event::DeviceEvent { device_id: mkdid(xev.deviceid), event: DeviceEvent::Button { + button: ButtonId(xev.detail as u32), + state: match xev.evtype { + ffi::XI_RawButtonPress => Pressed, + ffi::XI_RawButtonRelease => Released, + _ => unreachable!(), + }, + }}); + } + } + + ffi::XI_RawMotion => { + let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; + let did = mkdid(xev.deviceid); + + let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) }; + let mut value = xev.valuators.values; + for i in 0..xev.valuators.mask_len*8 { + if ffi::XIMaskIsSet(mask, i) { + callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::Motion { + axis: AxisId(i as u32), + value: unsafe { *value }, + }}); + value = unsafe { value.offset(1) }; + } + } + } + + ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { + // TODO: Use xkbcommon for keysym and text decoding + let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; + let xkeysym = unsafe { (self.display.xlib.XKeycodeToKeysym)(self.display.display, xev.detail as ffi::KeyCode, 0) }; + callback(Event::DeviceEvent { device_id: mkdid(xev.deviceid), event: DeviceEvent::Key(KeyboardInput { + scancode: xev.detail as u32, + virtual_keycode: events::keysym_to_element(xkeysym as libc::c_uint), + state: match xev.evtype { + ffi::XI_RawKeyPress => Pressed, + ffi::XI_RawKeyRelease => Released, + _ => unreachable!(), + }, + modifiers: ::events::ModifiersState::default(), + })}); + } + + ffi::XI_HierarchyChanged => { + let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) }; + for info in unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } { + if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) { + self.init_device(info.deviceid); + callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Added }); + } else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) { + callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Removed }); + let mut devices = self.devices.lock().unwrap(); + devices.remove(&DeviceId(info.deviceid)); + } + } + } + + _ => {} + } + } + + _ => {} + } + } + + fn init_device(&self, device: c_int) { + let mut devices = self.devices.lock().unwrap(); + for info in DeviceInfo::get(&self.display, device).iter() { + devices.insert(DeviceId(info.deviceid), Device::new(&self, info)); + } + } +} + +struct DeviceInfo<'a> { + display: &'a XConnection, + info: *const ffi::XIDeviceInfo, + count: usize, +} + +impl<'a> DeviceInfo<'a> { + fn get(display: &'a XConnection, device: c_int) -> Self { + unsafe { + let mut count = mem::uninitialized(); + let info = (display.xinput2.XIQueryDevice)(display.display, device, &mut count); + DeviceInfo { + display: display, + info: info, + count: count as usize, + } + } + } +} + +impl<'a> Drop for DeviceInfo<'a> { + fn drop(&mut self) { + unsafe { (self.display.xinput2.XIFreeDeviceInfo)(self.info as *mut _) }; + } +} + +impl<'a> ::std::ops::Deref for DeviceInfo<'a> { + type Target = [ffi::XIDeviceInfo]; + fn deref(&self) -> &Self::Target { + unsafe { slice::from_raw_parts(self.info, self.count) } + } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(usize); +pub struct WindowId(ffi::Window); + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId(c_int); pub struct Window2 { - pub window: ::std::sync::Arc, - events_loop: ::std::sync::Weak<::platform::EventsLoop>, + pub window: Arc, + events_loop: Weak<::platform::EventsLoop>, } impl ::std::ops::Deref for Window2 { @@ -87,28 +554,61 @@ impl ::std::ops::Deref for Window2 { } } +// XOpenIM doesn't seem to be thread-safe +lazy_static! { // TODO: use a static mutex when that's possible, and put me back in my function + static ref GLOBAL_XOPENIM_LOCK: Mutex<()> = Mutex::new(()); +} + impl Window2 { - pub fn new(events_loop: ::std::sync::Arc<::platform::EventsLoop>, display: &Arc, + pub fn new(events_loop: Arc<::platform::EventsLoop>, window: &::WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result { - let win = ::std::sync::Arc::new(try!(Window::new(display, window, pl_attribs))); - if let ::platform::EventsLoop::X(ref ev) = *events_loop { - ev.windows.lock().unwrap().push(win.clone()); - } else { - // It should not be possible to create an eventloop not matching the backend - // in use - unreachable!() - } + let x_events_loop = if let ::platform::EventsLoop::X(ref e) = *events_loop { e } else { unreachable!() }; + let win = ::std::sync::Arc::new(try!(Window::new(&x_events_loop, window, pl_attribs))); + + // creating IM + let im = unsafe { + let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap(); + + let im = (x_events_loop.display.xlib.XOpenIM)(x_events_loop.display.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()); + if im.is_null() { + panic!("XOpenIM failed"); + } + im + }; + + // creating input context + let ic = unsafe { + let ic = (x_events_loop.display.xlib.XCreateIC)(im, + b"inputStyle\0".as_ptr() as *const _, + ffi::XIMPreeditNothing | ffi::XIMStatusNothing, b"clientWindow\0".as_ptr() as *const _, + win.id().0, ptr::null::<()>()); + if ic.is_null() { + panic!("XCreateIC failed"); + } + (x_events_loop.display.xlib.XSetICFocus)(ic); + x_events_loop.display.check_errors().expect("Failed to call XSetICFocus"); + ic + }; + + x_events_loop.windows.lock().unwrap().insert(win.id(), WindowData { + im: im, + ic: ic, + config: None, + multitouch: window.multitouch, + cursor_pos: None, + }); + Ok(Window2 { window: win, - events_loop: ::std::sync::Arc::downgrade(&events_loop), + events_loop: Arc::downgrade(&events_loop), }) } #[inline] pub fn id(&self) -> WindowId { - WindowId(&*self.window as *const Window as usize) + self.window.id() } } @@ -117,8 +617,167 @@ impl Drop for Window2 { if let Some(ev) = self.events_loop.upgrade() { if let ::platform::EventsLoop::X(ref ev) = *ev { let mut windows = ev.windows.lock().unwrap(); - windows.retain(|w| &**w as *const Window != &*self.window as *const _); + + + let w = windows.remove(&self.window.id()).unwrap(); + let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap(); + unsafe { + (ev.display.xlib.XDestroyIC)(w.ic); + (ev.display.xlib.XCloseIM)(w.im); + } + } + } + } +} + +/// State maintained for translating window-related events +struct WindowData { + config: Option, + im: ffi::XIM, + ic: ffi::XIC, + multitouch: bool, + cursor_pos: Option<(f64, f64)>, +} + +// Required by ffi members +unsafe impl Send for WindowData {} + +struct WindowConfig { + size: (c_int, c_int), + position: (c_int, c_int), +} + +impl WindowConfig { + fn new(event: &ffi::XConfigureEvent) -> Self { + WindowConfig { + size: (event.width, event.height), + position: (event.x, event.y), + } + } +} + + +/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to +/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed +struct GenericEventCookie<'a> { + display: &'a XConnection, + cookie: ffi::XGenericEventCookie +} + +impl<'a> GenericEventCookie<'a> { + fn from_event<'b>(display: &'b XConnection, event: ffi::XEvent) -> Option> { + unsafe { + let mut cookie: ffi::XGenericEventCookie = From::from(event); + if (display.xlib.XGetEventData)(display.display, &mut cookie) == ffi::True { + Some(GenericEventCookie{display: display, cookie: cookie}) + } else { + None + } + } + } +} + +impl<'a> Drop for GenericEventCookie<'a> { + fn drop(&mut self) { + unsafe { + let xlib = &self.display.xlib; + (xlib.XFreeEventData)(self.display.display, &mut self.cookie); + } + } +} + +#[derive(Debug, Copy, Clone)] +struct XExtension { + opcode: c_int, + first_event_id: c_int, + first_error_id: c_int, +} + +fn mkwid(w: ffi::Window) -> ::WindowId { ::WindowId(::platform::WindowId::X(WindowId(w))) } +fn mkdid(w: c_int) -> ::DeviceId { ::DeviceId(::platform::DeviceId::X(DeviceId(w))) } + +#[derive(Debug)] +struct Device { + name: String, + scroll_axes: Vec<(i32, ScrollAxis)>, +} + +#[derive(Debug, Copy, Clone)] +struct ScrollAxis { + increment: f64, + orientation: ScrollOrientation, + position: f64, +} + +#[derive(Debug, Copy, Clone)] +enum ScrollOrientation { + Vertical, + Horizontal, +} + +impl Device { + fn new(el: &EventsLoop, info: &ffi::XIDeviceInfo) -> Self + { + let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; + + let physical_device = info._use == ffi::XISlaveKeyboard || info._use == ffi::XISlavePointer || info._use == ffi::XIFloatingSlave; + if physical_device { + // Register for global raw events + let mask = ffi::XI_RawMotionMask + | ffi::XI_RawButtonPressMask | ffi::XI_RawButtonReleaseMask + | ffi::XI_RawKeyPressMask | ffi::XI_RawKeyReleaseMask; + unsafe { + let mut event_mask = ffi::XIEventMask{ + deviceid: info.deviceid, + mask: &mask as *const _ as *mut c_uchar, + mask_len: mem::size_of_val(&mask) as c_int, + }; + (el.display.xinput2.XISelectEvents)(el.display.display, el.root, &mut event_mask as *mut ffi::XIEventMask, 1); } } + + let mut scroll_axes = Vec::new(); + + if physical_device { + let classes : &[*const ffi::XIAnyClassInfo] = + unsafe { slice::from_raw_parts(info.classes as *const *const ffi::XIAnyClassInfo, info.num_classes as usize) }; + // Identify scroll axes + for class_ptr in classes { + let class = unsafe { &**class_ptr }; + match class._type { + ffi::XIScrollClass => { + let info = unsafe { mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class) }; + scroll_axes.push((info.number, ScrollAxis { + increment: info.increment, + orientation: match info.scroll_type { + ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal, + ffi::XIScrollTypeVertical => ScrollOrientation::Vertical, + _ => { unreachable!() } + }, + position: 0.0, + })); + } + _ => {} + } + } + // Fix up initial scroll positions + for class_ptr in classes { + let class = unsafe { &**class_ptr }; + match class._type { + ffi::XIValuatorClass => { + let info = unsafe { mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class) }; + if let Some(&mut (_, ref mut axis)) = scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == info.number) { + axis.position = info.value; + } + } + _ => {} + } + } + } + + Device { + name: name.into_owned(), + scroll_axes: scroll_axes, + } } } diff --git a/src/platform/linux/x11/window.rs b/src/platform/linux/x11/window.rs index deca3d2c922..1cf261a4a4a 100644 --- a/src/platform/linux/x11/window.rs +++ b/src/platform/linux/x11/window.rs @@ -1,14 +1,11 @@ -use {WindowEvent as Event, MouseCursor}; +use MouseCursor; use CreationError; use CreationError::OsError; use libc; use std::borrow::Borrow; use std::{mem, ptr, cmp}; -use std::cell::Cell; -use std::sync::atomic::AtomicBool; -use std::collections::VecDeque; use std::sync::{Arc, Mutex}; -use std::os::raw::c_long; +use std::os::raw::{c_int, c_long, c_uchar}; use std::thread; use std::time::Duration; @@ -18,14 +15,8 @@ use platform::PlatformSpecificWindowBuilderAttributes; use platform::MonitorId as PlatformMonitorId; -use super::input::XInputEventHandler; use super::{ffi}; -use super::{MonitorId, XConnection}; - -// XOpenIM doesn't seem to be thread-safe -lazy_static! { // TODO: use a static mutex when that's possible, and put me back in my function - static ref GLOBAL_XOPENIM_LOCK: Mutex<()> = Mutex::new(()); -} +use super::{MonitorId, XConnection, WindowId, EventsLoop}; // TODO: remove me fn with_c_str(s: &str, f: F) -> T where F: FnOnce(*const libc::c_char) -> T { @@ -47,8 +38,6 @@ pub struct XWindow { is_fullscreen: bool, screen_id: libc::c_int, xf86_desk_mode: Option, - ic: ffi::XIC, - im: ffi::XIM, window_proxy_data: Arc>>, } @@ -65,8 +54,6 @@ impl Drop for XWindow { // are no longer able to send messages to this window. *self.window_proxy_data.lock().unwrap() = None; - let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap(); - if self.is_fullscreen { if let Some(mut xf86_desk_mode) = self.xf86_desk_mode { (self.display.xf86vmode.XF86VidModeSwitchToMode)(self.display.display, self.screen_id, &mut xf86_desk_mode); @@ -74,8 +61,6 @@ impl Drop for XWindow { (self.display.xf86vmode.XF86VidModeSetViewPort)(self.display.display, self.screen_id, 0, 0); } - (self.display.xlib.XDestroyIC)(self.ic); - (self.display.xlib.XCloseIM)(self.im); (self.display.xlib.XDestroyWindow)(self.display.display, self.window); } } @@ -111,190 +96,17 @@ impl WindowProxy { } } -// XEvents of type GenericEvent store their actual data -// in an XGenericEventCookie data structure. This is a wrapper -// to extract the cookie from a GenericEvent XEvent and release -// the cookie data once it has been processed -struct GenericEventCookie<'a> { - display: &'a XConnection, - cookie: ffi::XGenericEventCookie -} - -impl<'a> GenericEventCookie<'a> { - fn from_event<'b>(display: &'b XConnection, event: ffi::XEvent) -> Option> { - unsafe { - let mut cookie: ffi::XGenericEventCookie = From::from(event); - if (display.xlib.XGetEventData)(display.display, &mut cookie) == ffi::True { - Some(GenericEventCookie{display: display, cookie: cookie}) - } else { - None - } - } - } -} - -impl<'a> Drop for GenericEventCookie<'a> { - fn drop(&mut self) { - unsafe { - let xlib = &self.display.xlib; - (xlib.XFreeEventData)(self.display.display, &mut self.cookie); - } - } -} - -pub struct PollEventsIterator<'a> { - window: &'a Window -} - -impl<'a> Iterator for PollEventsIterator<'a> { - type Item = Event; - - fn next(&mut self) -> Option { - let xlib = &self.window.x.display.xlib; - - loop { - if let Some(ev) = self.window.pending_events.lock().unwrap().pop_front() { - return Some(ev); - } - - let mut xev = unsafe { mem::uninitialized() }; - - // Get the next X11 event. XNextEvent will block if there's no - // events available; checking the count first ensures an event will - // be returned without blocking. - // - // Functions like XCheckTypedEvent can prevent events from being - // popped if they are of the wrong type in which case winit would - // enter a busy loop. To avoid that, XNextEvent is used to pop - // events off the queue since it will accept any event type. - unsafe { - let count = (xlib.XPending)(self.window.x.display.display); - if count == 0 { - return None; - } - - let res = (xlib.XNextEvent)(self.window.x.display.display, &mut xev); - - // Can res ever be none zero if count is > 0? - assert!(res == 0); - }; - - match xev.get_type() { - ffi::MappingNotify => { - unsafe { (xlib.XRefreshKeyboardMapping)(mem::transmute(&xev)); } - self.window.x.display.check_errors().expect("Failed to call XRefreshKeyboardMapping"); - }, - - ffi::ClientMessage => { - use events::WindowEvent::{Closed, Awakened}; - use std::sync::atomic::Ordering::Relaxed; - - let client_msg: &ffi::XClientMessageEvent = unsafe { mem::transmute(&xev) }; - - if client_msg.data.get_long(0) == self.window.wm_delete_window as libc::c_long { - self.window.is_closed.store(true, Relaxed); - return Some(Closed); - } else { - return Some(Awakened); - } - }, - - ffi::ConfigureNotify => { - use events::WindowEvent::Resized; - let cfg_event: &ffi::XConfigureEvent = unsafe { mem::transmute(&xev) }; - let (current_width, current_height) = self.window.current_size.get(); - if current_width != cfg_event.width || current_height != cfg_event.height { - self.window.current_size.set((cfg_event.width, cfg_event.height)); - return Some(Resized(cfg_event.width as u32, cfg_event.height as u32)); - } - }, - - ffi::Expose => { - use events::WindowEvent::Refresh; - return Some(Refresh); - }, - - ffi::KeyPress | ffi::KeyRelease => { - let mut event: &mut ffi::XKeyEvent = unsafe { mem::transmute(&mut xev) }; - let events = self.window.input_handler.lock().unwrap().translate_key_event(&mut event); - for event in events { - self.window.pending_events.lock().unwrap().push_back(event); - } - }, - - ffi::GenericEvent => { - if let Some(cookie) = GenericEventCookie::from_event(self.window.x.display.borrow(), xev) { - match cookie.cookie.evtype { - ffi::XI_DeviceChanged...ffi::XI_LASTEVENT => { - match self.window.input_handler.lock() { - Ok(mut handler) => { - match handler.translate_event(&cookie.cookie) { - Some(event) => self.window.pending_events.lock().unwrap().push_back(event), - None => {} - } - }, - Err(_) => {} - } - }, - _ => {} - } - } - } - - _ => {} - }; - } - } -} - -pub struct WaitEventsIterator<'a> { - window: &'a Window, -} - -impl<'a> Iterator for WaitEventsIterator<'a> { - type Item = Event; - - fn next(&mut self) -> Option { - use std::sync::atomic::Ordering::Relaxed; - use std::mem; - - while !self.window.is_closed.load(Relaxed) { - if let Some(ev) = self.window.pending_events.lock().unwrap().pop_front() { - return Some(ev); - } - - // this will block until an event arrives, but doesn't remove - // it from the queue - let mut xev = unsafe { mem::uninitialized() }; - unsafe { (self.window.x.display.xlib.XPeekEvent)(self.window.x.display.display, &mut xev) }; - self.window.x.display.check_errors().expect("Failed to call XPeekEvent"); - - // calling poll_events() - if let Some(ev) = self.window.poll_events().next() { - return Some(ev); - } - } - - None - } -} - pub struct Window { pub x: Arc, - is_closed: AtomicBool, - wm_delete_window: ffi::Atom, - current_size: Cell<(libc::c_int, libc::c_int)>, - /// Events that have been retreived with XLib but not dispatched with iterators yet - pending_events: Mutex>, cursor_state: Mutex, - input_handler: Mutex } impl Window { - pub fn new(display: &Arc, window_attrs: &WindowAttributes, + pub fn new(ctx: &EventsLoop, window_attrs: &WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result { + let display = &ctx.display; let dimensions = { // x11 only applies constraints when the window is actively resized @@ -413,49 +225,13 @@ impl Window { display.check_errors().expect("Failed to set window visibility"); } - // creating window, step 2 - let wm_delete_window = unsafe { - let mut wm_delete_window = with_c_str("WM_DELETE_WINDOW", |delete_window| - (display.xlib.XInternAtom)(display.display, delete_window, 0) - ); - display.check_errors().expect("Failed to call XInternAtom"); - (display.xlib.XSetWMProtocols)(display.display, window, &mut wm_delete_window, 1); + // Opt into handling window close + unsafe { + (display.xlib.XSetWMProtocols)(display.display, window, &ctx.wm_delete_window as *const _ as *mut _, 1); display.check_errors().expect("Failed to call XSetWMProtocols"); (display.xlib.XFlush)(display.display); display.check_errors().expect("Failed to call XFlush"); - - wm_delete_window - }; - - // creating IM - let im = unsafe { - let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap(); - - let im = (display.xlib.XOpenIM)(display.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()); - if im.is_null() { - return Err(OsError(format!("XOpenIM failed"))); - } - im - }; - - // creating input context - let ic = unsafe { - let ic = with_c_str("inputStyle", |input_style| - with_c_str("clientWindow", |client_window| - (display.xlib.XCreateIC)( - im, input_style, - ffi::XIMPreeditNothing | ffi::XIMStatusNothing, client_window, - window, ptr::null::<()>() - ) - ) - ); - if ic.is_null() { - return Err(OsError(format!("XCreateIC failed"))); - } - (display.xlib.XSetICFocus)(ic); - display.check_errors().expect("Failed to call XSetICFocus"); - ic - }; + } // Attempt to make keyboard input repeat detectable unsafe { @@ -569,6 +345,25 @@ impl Window { } + // Select XInput2 events + { + let mask = ffi::XI_MotionMask + | ffi::XI_ButtonPressMask | ffi::XI_ButtonReleaseMask + // | ffi::XI_KeyPressMask | ffi::XI_KeyReleaseMask + | ffi::XI_EnterMask | ffi::XI_LeaveMask + | ffi::XI_FocusInMask | ffi::XI_FocusOutMask + | if window_attrs.multitouch { ffi::XI_TouchBeginMask | ffi::XI_TouchUpdateMask | ffi::XI_TouchEndMask } else { 0 }; + unsafe { + let mut event_mask = ffi::XIEventMask{ + deviceid: ffi::XIAllMasterDevices, + mask: mem::transmute::<*const i32, *mut c_uchar>(&mask as *const i32), + mask_len: mem::size_of_val(&mask) as c_int, + }; + (display.xinput2.XISelectEvents)(display.display, window, + &mut event_mask as *mut ffi::XIEventMask, 1); + }; + } + // creating the window object let window_proxy_data = WindowProxyData { display: display.clone(), @@ -580,19 +375,12 @@ impl Window { x: Arc::new(XWindow { display: display.clone(), window: window, - im: im, - ic: ic, screen_id: screen_id, is_fullscreen: is_fullscreen, xf86_desk_mode: xf86_desk_mode, window_proxy_data: window_proxy_data, }), - is_closed: AtomicBool::new(false), - wm_delete_window: wm_delete_window, - current_size: Cell::new((0, 0)), - pending_events: Mutex::new(VecDeque::new()), cursor_state: Mutex::new(CursorState::Normal), - input_handler: Mutex::new(XInputEventHandler::new(display, window, ic, window_attrs)) }; window.set_title(&window_attrs.title); @@ -768,20 +556,6 @@ impl Window { } } - #[inline] - pub fn poll_events(&self) -> PollEventsIterator { - PollEventsIterator { - window: self - } - } - - #[inline] - pub fn wait_events(&self) -> WaitEventsIterator { - WaitEventsIterator { - window: self - } - } - #[inline] pub fn get_xlib_display(&self) -> *mut libc::c_void { self.x.display.display as *mut libc::c_void @@ -1016,4 +790,7 @@ impl Window { self.x.display.check_errors().map_err(|_| ()) } } + + #[inline] + pub fn id(&self) -> WindowId { WindowId(self.x.window) } } diff --git a/src/platform/macos/events_loop.rs b/src/platform/macos/events_loop.rs index e7e4a12b03a..ea24948fdf9 100644 --- a/src/platform/macos/events_loop.rs +++ b/src/platform/macos/events_loop.rs @@ -1,8 +1,9 @@ use cocoa::{self, appkit, foundation}; use cocoa::appkit::{NSApplication, NSEvent, NSView, NSWindow}; -use events::{self, ElementState, Event, MouseButton, TouchPhase, WindowEvent, ModifiersState}; +use events::{self, ElementState, Event, MouseButton, TouchPhase, WindowEvent, ModifiersState, KeyboardInput}; use super::window::Window; use std; +use super::DeviceId; pub struct EventsLoop { @@ -315,8 +316,16 @@ impl EventsLoop { let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event)); let state = ElementState::Pressed; - let code = NSEvent::keyCode(ns_event) as u8; - let window_event = WindowEvent::KeyboardInput(state, code, vkey, event_mods(ns_event)); + let code = NSEvent::keyCode(ns_event) as u32; + let window_event = WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: state, + scancode: code, + virtual_keycode: vkey, + modifiers: event_mods(ns_event), + }, + }; events.push_back(into_event(window_event)); let event = events.pop_front(); self.pending_events.lock().unwrap().extend(events.into_iter()); @@ -327,8 +336,16 @@ impl EventsLoop { let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event)); let state = ElementState::Released; - let code = NSEvent::keyCode(ns_event) as u8; - let window_event = WindowEvent::KeyboardInput(state, code, vkey, event_mods(ns_event)); + let code = NSEvent::keyCode(ns_event) as u32; + let window_event = WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: state, + scancode: code, + virtual_keycode: vkey, + modifiers: event_mods(ns_event), + }, + }; Some(into_event(window_event)) }, @@ -342,14 +359,30 @@ impl EventsLoop { { if !key_pressed && NSEvent::modifierFlags(event).contains(keymask) { let state = ElementState::Pressed; - let code = NSEvent::keyCode(event) as u8; - let window_event = WindowEvent::KeyboardInput(state, code, Some(key), event_mods(event)); + let code = NSEvent::keyCode(event) as u32; + let window_event = WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: state, + scancode: code, + virtual_keycode: Some(key), + modifiers: event_mods(event), + }, + }; Some(window_event) } else if key_pressed && !NSEvent::modifierFlags(event).contains(keymask) { let state = ElementState::Released; - let code = NSEvent::keyCode(event) as u8; - let window_event = WindowEvent::KeyboardInput(state, code, Some(key), event_mods(event)); + let code = NSEvent::keyCode(event) as u32; + let window_event = WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: state, + scancode: code, + virtual_keycode: Some(key), + modifiers: event_mods(event), + }, + }; Some(window_event) } else { @@ -399,15 +432,15 @@ impl EventsLoop { event }, - appkit::NSLeftMouseDown => { Some(into_event(WindowEvent::MouseInput(ElementState::Pressed, MouseButton::Left))) }, - appkit::NSLeftMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, MouseButton::Left))) }, - appkit::NSRightMouseDown => { Some(into_event(WindowEvent::MouseInput(ElementState::Pressed, MouseButton::Right))) }, - appkit::NSRightMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, MouseButton::Right))) }, - appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput(ElementState::Pressed, MouseButton::Middle))) }, - appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, MouseButton::Middle))) }, + appkit::NSLeftMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Left })) }, + appkit::NSLeftMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Left })) }, + appkit::NSRightMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Right })) }, + appkit::NSRightMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Right })) }, + appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Middle })) }, + appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Released, button: MouseButton::Middle })) }, - appkit::NSMouseEntered => { Some(into_event(WindowEvent::MouseEntered)) }, - appkit::NSMouseExited => { Some(into_event(WindowEvent::MouseLeft)) }, + appkit::NSMouseEntered => { Some(into_event(WindowEvent::MouseEntered { device_id: DEVICE_ID })) }, + appkit::NSMouseExited => { Some(into_event(WindowEvent::MouseLeft { device_id: DEVICE_ID })) }, appkit::NSMouseMoved | appkit::NSLeftMouseDragged | @@ -433,9 +466,9 @@ impl EventsLoop { let view_rect = NSView::frame(*window.view); let scale_factor = window.hidpi_factor(); - let x = (scale_factor * view_point.x as f32) as i32; - let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as i32; - let window_event = WindowEvent::MouseMoved(x, y); + let x = (scale_factor * view_point.x as f32) as f64; + let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64; + let window_event = WindowEvent::MouseMoved { device_id: DEVICE_ID, position: (x, y) }; let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event }; Some(event) }, @@ -461,14 +494,14 @@ impl EventsLoop { appkit::NSEventPhaseEnded => TouchPhase::Ended, _ => TouchPhase::Moved, }; - let window_event = WindowEvent::MouseWheel(delta, phase); + let window_event = WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: delta, phase: phase }; Some(into_event(window_event)) }, appkit::NSEventTypePressure => { let pressure = ns_event.pressure(); let stage = ns_event.stage(); - let window_event = WindowEvent::TouchpadPressure(pressure, stage); + let window_event = WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure: pressure, stage: stage }; Some(into_event(window_event)) }, @@ -632,3 +665,6 @@ fn event_mods(event: cocoa::base::id) -> ModifiersState { logo: flags.contains(appkit::NSCommandKeyMask), } } + +// Constant device ID, to be removed when this backend is updated to report real device IDs. +const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index 086bcf0f487..5b207bb2ded 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -4,6 +4,9 @@ pub use self::events_loop::EventsLoop; pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor}; pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window}; +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct DeviceId; + use {CreationError}; pub struct Window2 { diff --git a/src/platform/windows/callback.rs b/src/platform/windows/callback.rs index e833010794b..864cf9c9668 100644 --- a/src/platform/windows/callback.rs +++ b/src/platform/windows/callback.rs @@ -8,6 +8,7 @@ use std::os::windows::ffi::OsStringExt; use CursorState; use WindowEvent as Event; +use KeyboardInput; use events::ModifiersState; use super::event; use super::WindowState; @@ -140,7 +141,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, }); if mouse_outside_window { - send_event(window, MouseEntered); + send_event(window, MouseEntered { device_id: DEVICE_ID }); // Calling TrackMouseEvent in order to receive mouse leave events. user32::TrackMouseEvent(&mut winapi::TRACKMOUSEEVENT { @@ -151,10 +152,10 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, }); } - let x = winapi::GET_X_LPARAM(lparam) as i32; - let y = winapi::GET_Y_LPARAM(lparam) as i32; + let x = winapi::GET_X_LPARAM(lparam) as f64; + let y = winapi::GET_Y_LPARAM(lparam) as f64; - send_event(window, MouseMoved(x, y)); + send_event(window, MouseMoved { device_id: DEVICE_ID, position: (x, y) }); 0 }, @@ -174,7 +175,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, }); if mouse_in_window { - send_event(window, MouseLeft); + send_event(window, MouseLeft { device_id: DEVICE_ID }); } 0 @@ -189,28 +190,42 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, let value = value as i32; let value = value as f32 / winapi::WHEEL_DELTA as f32; - send_event(window, MouseWheel(LineDelta(0.0, value), TouchPhase::Moved)); + send_event(window, MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved }); 0 }, winapi::WM_KEYDOWN | winapi::WM_SYSKEYDOWN => { - use events::WindowEvent::KeyboardInput; use events::ElementState::Pressed; if msg == winapi::WM_SYSKEYDOWN && wparam as i32 == winapi::VK_F4 { user32::DefWindowProcW(window, msg, wparam, lparam) } else { let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam); - send_event(window, KeyboardInput(Pressed, scancode, vkey, event::get_key_mods())); + send_event(window, Event::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: Pressed, + scancode: scancode, + virtual_keycode: vkey, + modifiers: event::get_key_mods(), + } + }); 0 } }, winapi::WM_KEYUP | winapi::WM_SYSKEYUP => { - use events::WindowEvent::KeyboardInput; use events::ElementState::Released; let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam); - send_event(window, KeyboardInput(Released, scancode, vkey, event::get_key_mods())); + send_event(window, Event::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: Released, + scancode: scancode, + virtual_keycode: vkey, + modifiers: event::get_key_mods(), + }, + }); 0 }, @@ -218,7 +233,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, use events::WindowEvent::MouseInput; use events::MouseButton::Left; use events::ElementState::Pressed; - send_event(window, MouseInput(Pressed, Left)); + send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left }); 0 }, @@ -226,7 +241,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, use events::WindowEvent::MouseInput; use events::MouseButton::Left; use events::ElementState::Released; - send_event(window, MouseInput(Released, Left)); + send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Left }); 0 }, @@ -234,7 +249,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, use events::WindowEvent::MouseInput; use events::MouseButton::Right; use events::ElementState::Pressed; - send_event(window, MouseInput(Pressed, Right)); + send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right }); 0 }, @@ -242,7 +257,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, use events::WindowEvent::MouseInput; use events::MouseButton::Right; use events::ElementState::Released; - send_event(window, MouseInput(Released, Right)); + send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Right }); 0 }, @@ -250,7 +265,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, use events::WindowEvent::MouseInput; use events::MouseButton::Middle; use events::ElementState::Pressed; - send_event(window, MouseInput(Pressed, Middle)); + send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle }); 0 }, @@ -258,7 +273,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, use events::WindowEvent::MouseInput; use events::MouseButton::Middle; use events::ElementState::Released; - send_event(window, MouseInput(Released, Middle)); + send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Middle }); 0 }, @@ -267,7 +282,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, use events::MouseButton::Other; use events::ElementState::Pressed; let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int; // waiting on PR for winapi to add GET_XBUTTON_WPARAM - send_event(window, MouseInput(Pressed, Other(xbutton as u8))); + send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Other(xbutton as u8) }); 0 }, @@ -276,7 +291,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, use events::MouseButton::Other; use events::ElementState::Released; let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int; - send_event(window, MouseInput(Released, Other(xbutton as u8))); + send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Other(xbutton as u8) }); 0 }, @@ -405,3 +420,6 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, } } } + +// Constant device ID, to be removed when this backend is updated to report real device IDs. +const DEVICE_ID: ::DeviceId = ::DeviceId(super::DeviceId); diff --git a/src/platform/windows/event.rs b/src/platform/windows/event.rs index 699f05d1378..671e6347476 100644 --- a/src/platform/windows/event.rs +++ b/src/platform/windows/event.rs @@ -26,10 +26,10 @@ pub fn get_key_mods() -> ModifiersState { } pub fn vkeycode_to_element(wparam: winapi::WPARAM, lparam: winapi::LPARAM) -> (ScanCode, Option) { - let scancode = ((lparam >> 16) & 0xff) as u8; + let scancode = ((lparam >> 16) & 0xff) as u32; let extended = (lparam & 0x01000000) != 0; let vk = match wparam as i32 { - winapi::VK_SHIFT => unsafe { user32::MapVirtualKeyA(scancode as u32, MAPVK_VSC_TO_VK_EX) as i32 }, + winapi::VK_SHIFT => unsafe { user32::MapVirtualKeyA(scancode, MAPVK_VSC_TO_VK_EX) as i32 }, winapi::VK_CONTROL => if extended { winapi::VK_RCONTROL } else { winapi::VK_LCONTROL }, winapi::VK_MENU => if extended { winapi::VK_RMENU } else { winapi::VK_LMENU }, other => other