diff --git a/examples/cursor.rs b/examples/cursor.rs index 0f65e10ab0..dcb379714c 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 308d9dcf3e..1c1d8bdbcc 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 d156da43d3..2ba327f431 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 464a7f0579..56a259344a 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 851ef0c6ff..ad27616bf9 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 00dba8bd56..913569d754 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 476eb1a3df..187154c9e9 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 6e12ffe116..18924b87bc 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 efc9411d49..60163e28c1 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 95e70b032b..4a8ed8759e 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 f2ae3477d8..9567d57e69 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 4754b1dc87..6bae009ab9 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 6f05704cb5..0000000000 --- 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 43721b7b21..d1ed69f67c 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,440 @@ 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 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 mut keysym = 0; + let count = (self.display.xlib.Xutf8LookupString)(window_data.ic, xkev, + mem::transmute(buffer.as_mut_ptr()), + buffer.len() as libc::c_int, &mut 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) + }; + + 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 +550,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 +613,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 deca3d2c92..1cf261a4a4 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 e7e4a12b03..ea24948fdf 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 086bcf0f48..5b207bb2de 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 e833010794..864cf9c966 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 699f05d137..671e634747 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