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.
This commit is contained in:
Benjamin Saunders 2017-04-22 13:52:35 -07:00
parent 544ee13bf3
commit 22bc119cd7
19 changed files with 994 additions and 786 deletions

View file

@ -1,6 +1,6 @@
extern crate winit; extern crate winit;
use winit::{Event, ElementState, MouseCursor, WindowEvent}; use winit::{Event, ElementState, MouseCursor, WindowEvent, KeyboardInput};
fn main() { fn main() {
let events_loop = winit::EventsLoop::new(); let events_loop = winit::EventsLoop::new();
@ -13,7 +13,7 @@ fn main() {
events_loop.run_forever(|event| { events_loop.run_forever(|event| {
match 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]); println!("Setting cursor to \"{:?}\"", cursors[cursor_idx]);
window.set_cursor(cursors[cursor_idx]); window.set_cursor(cursors[cursor_idx]);
if cursor_idx < cursors.len() - 1 { if cursor_idx < cursors.len() - 1 {

View file

@ -37,10 +37,13 @@ fn main() {
winit::Event::WindowEvent { event, .. } => { winit::Event::WindowEvent { event, .. } => {
match event { match event {
winit::WindowEvent::Closed => events_loop.interrupt(), 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(),
_ => () _ => ()
} }
}, },
_ => {}
} }
}); });
} }

View file

@ -1,6 +1,6 @@
extern crate winit; extern crate winit;
use winit::{WindowEvent, ElementState}; use winit::{WindowEvent, ElementState, KeyboardInput};
fn main() { fn main() {
let events_loop = winit::EventsLoop::new(); let events_loop = winit::EventsLoop::new();
@ -16,7 +16,7 @@ fn main() {
match event { match event {
winit::Event::WindowEvent { event, .. } => { winit::Event::WindowEvent { event, .. } => {
match event { match event {
WindowEvent::KeyboardInput(ElementState::Pressed, _, _, _) => { WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, .. }, .. } => {
if grabbed { if grabbed {
grabbed = false; grabbed = false;
window.set_cursor_state(winit::CursorState::Normal) window.set_cursor_state(winit::CursorState::Normal)
@ -30,13 +30,14 @@ fn main() {
WindowEvent::Closed => events_loop.interrupt(), WindowEvent::Closed => events_loop.interrupt(),
a @ WindowEvent::MouseMoved(_, _) => { a @ WindowEvent::MouseMoved { .. } => {
println!("{:?}", a); println!("{:?}", a);
}, },
_ => (), _ => (),
} }
}, }
_ => {}
} }
}); });
} }

View file

@ -57,6 +57,9 @@ macro_rules! gen_api_transition {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(usize); pub struct WindowId(usize);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
pub struct Window2 { pub struct Window2 {
pub window: ::std::sync::Arc<Window>, pub window: ::std::sync::Arc<Window>,
events_loop: ::std::sync::Weak<EventsLoop>, events_loop: ::std::sync::Weak<EventsLoop>,

View file

@ -1,12 +1,16 @@
use std::path::PathBuf; use std::path::PathBuf;
use WindowId; use {WindowId, DeviceId, AxisId, ButtonId};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Event { pub enum Event {
WindowEvent { WindowEvent {
window_id: WindowId, window_id: WindowId,
event: WindowEvent, event: WindowEvent,
} },
DeviceEvent {
device_id: DeviceId,
event: DeviceEvent,
},
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -35,31 +39,36 @@ pub enum WindowEvent {
Focused(bool), Focused(bool),
/// An event from the keyboard has been received. /// An event from the keyboard has been received.
KeyboardInput(ElementState, ScanCode, Option<VirtualKeyCode>, ModifiersState), KeyboardInput { device_id: DeviceId, input: KeyboardInput },
/// The cursor has moved on the window. /// 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. /// `position` is (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this
MouseMoved(i32, i32), /// 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. /// The cursor has entered the window.
MouseEntered, MouseEntered { device_id: DeviceId },
/// The cursor has left the window. /// The cursor has left the window.
MouseLeft, MouseLeft { device_id: DeviceId },
/// A mouse wheel movement or touchpad scroll occurred. /// 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. /// An mouse button press has been received.
MouseInput(ElementState, MouseButton), MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton },
/// Touchpad pressure event. /// Touchpad pressure event.
/// ///
/// At the moment, only supported on Apple forcetouch-capable macbooks. /// 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 /// 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). /// 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. /// The window needs to be redrawn.
Refresh, Refresh,
@ -73,6 +82,48 @@ pub enum WindowEvent {
Touch(Touch) 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<VirtualKeyCode>,
/// 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)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum TouchPhase { pub enum TouchPhase {
Started, Started,
@ -98,13 +149,14 @@ pub enum TouchPhase {
/// ///
/// Touch may be cancelled if for example window lost focus. /// Touch may be cancelled if for example window lost focus.
pub struct Touch { pub struct Touch {
pub device_id: DeviceId,
pub phase: TouchPhase, pub phase: TouchPhase,
pub location: (f64,f64), pub location: (f64,f64),
/// unique identifier of a finger. /// unique identifier of a finger.
pub id: u64 pub id: u64
} }
pub type ScanCode = u8; pub type ScanCode = u32;
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum ElementState { pub enum ElementState {

View file

@ -169,6 +169,22 @@ pub struct Window {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(platform::WindowId); 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. /// Provides a way to retreive events from the windows that were registered to it.
// TODO: document usage in multiple threads // TODO: document usage in multiple threads
pub struct EventsLoop { pub struct EventsLoop {

View file

@ -287,6 +287,7 @@ impl Window {
let phase: i32 = msg_send![touch, phase]; let phase: i32 = msg_send![touch, phase];
state.events_queue.push_back(Event::Touch(Touch { state.events_queue.push_back(Event::Touch(Touch {
device_id: DEVICE_ID,
id: touch_id, id: touch_id,
location: (location.x as f64, location.y as f64), location: (location.x as f64, location.y as f64),
phase: match phase { 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);

View file

@ -58,6 +58,14 @@ pub enum WindowId {
Wayland(wayland::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)] #[derive(Clone)]
pub enum MonitorId { pub enum MonitorId {
#[doc(hidden)] #[doc(hidden)]
@ -137,8 +145,8 @@ impl Window2 {
} }
}, },
UnixBackend::X(ref connec) => { UnixBackend::X(_) => {
x11::Window2::new(events_loop, connec, window, pl_attribs).map(Window2::X) x11::Window2::new(events_loop, window, pl_attribs).map(Window2::X)
}, },
UnixBackend::Error(_) => { UnixBackend::Error(_) => {
// If the Backend is Error(), it is not possible to instanciate an EventsLoop at all, // 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())) EventsLoop::Wayland(wayland::EventsLoop::new(ctxt.clone()))
}, },
UnixBackend::X(_) => { UnixBackend::X(ref ctxt) => {
EventsLoop::X(x11::EventsLoop::new()) EventsLoop::X(x11::EventsLoop::new(ctxt.clone()))
}, },
UnixBackend::Error(_) => { UnixBackend::Error(_) => {

View file

@ -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::{Arc, Mutex};
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use super::{DecoratedHandler, WindowId, WaylandContext}; use super::{DecoratedHandler, WindowId, DeviceId, WaylandContext};
use wayland_client::{EventQueue, EventQueueHandle, Init, Proxy}; use wayland_client::{EventQueue, EventQueueHandle, Init, Proxy};
@ -221,7 +221,7 @@ struct InputHandler {
seat: Option<wl_seat::WlSeat>, seat: Option<wl_seat::WlSeat>,
mouse: Option<wl_pointer::WlPointer>, mouse: Option<wl_pointer::WlPointer>,
mouse_focus: Option<Arc<wl_surface::WlSurface>>, mouse_focus: Option<Arc<wl_surface::WlSurface>>,
mouse_location: (i32, i32), mouse_location: (f64, f64),
axis_buffer: Option<(f32, f32)>, axis_buffer: Option<(f32, f32)>,
axis_discrete_buffer: Option<(i32, i32)>, axis_discrete_buffer: Option<(i32, i32)>,
axis_state: TouchPhase, axis_state: TouchPhase,
@ -242,7 +242,7 @@ impl InputHandler {
seat: ctxt.get_seat(), seat: ctxt.get_seat(),
mouse: None, mouse: None,
mouse_focus: None, mouse_focus: None,
mouse_location: (0,0), mouse_location: (0.0,0.0),
axis_buffer: None, axis_buffer: None,
axis_discrete_buffer: None, axis_discrete_buffer: None,
axis_state: TouchPhase::Started, axis_state: TouchPhase::Started,
@ -310,14 +310,17 @@ impl wl_pointer::Handler for InputHandler {
surface_x: f64, surface_x: f64,
surface_y: 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 { for window in &self.windows {
if window.equals(surface) { if window.equals(surface) {
self.mouse_focus = Some(window.clone()); self.mouse_focus = Some(window.clone());
let (w, h) = self.mouse_location; let (w, h) = self.mouse_location;
let mut guard = self.callback.lock().unwrap(); let mut guard = self.callback.lock().unwrap();
guard.send_event(Event::MouseEntered, make_wid(window)); guard.send_event(Event::MouseEntered { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)) },
guard.send_event(Event::MouseMoved(w, h), make_wid(window)); make_wid(window));
guard.send_event(Event::MouseMoved { device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
position: (w, h) },
make_wid(window));
break; break;
} }
} }
@ -332,7 +335,8 @@ impl wl_pointer::Handler for InputHandler {
self.mouse_focus = None; self.mouse_focus = None;
for window in &self.windows { for window in &self.windows {
if window.equals(surface) { 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_x: f64,
surface_y: 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 { if let Some(ref window) = self.mouse_focus {
let (w,h) = self.mouse_location; 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 ? // TODO figure out the translation ?
_ => return _ => 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(ref window) = self.mouse_focus {
if let Some((x, y)) = axis_discrete_buffer { if let Some((x, y)) = axis_discrete_buffer {
self.callback.lock().unwrap().send_event( self.callback.lock().unwrap().send_event(
Event::MouseWheel( Event::MouseWheel {
MouseScrollDelta::LineDelta(x as f32, y as f32), device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
self.axis_state delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
), phase: self.axis_state,
},
make_wid(window) make_wid(window)
); );
} else if let Some((x, y)) = axis_buffer { } else if let Some((x, y)) = axis_buffer {
self.callback.lock().unwrap().send_event( self.callback.lock().unwrap().send_event(
Event::MouseWheel( Event::MouseWheel {
MouseScrollDelta::PixelDelta(x as f32, y as f32), device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
self.axis_state delta: MouseScrollDelta::PixelDelta(x as f32, y as f32),
), phase: self.axis_state,
},
make_wid(window) make_wid(window)
); );
} }
@ -547,12 +561,15 @@ impl wl_keyboard::Handler for InputHandler {
// anyway, as we need libxkbcommon to interpret it (it is // anyway, as we need libxkbcommon to interpret it (it is
// supposed to be serialized by the compositor using libxkbcommon) // supposed to be serialized by the compositor using libxkbcommon)
self.callback.lock().unwrap().send_event( self.callback.lock().unwrap().send_event(
Event::KeyboardInput( Event::KeyboardInput {
state, device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
key as u8, input: KeyboardInput {
None, state: state,
ModifiersState::default() scancode: key,
), virtual_keycode: None,
modifiers: ModifiersState::default(),
},
},
wid wid
); );
}, },

View file

@ -1,10 +1,10 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use {VirtualKeyCode, ElementState, WindowEvent as Event}; use {VirtualKeyCode, ElementState, WindowEvent as Event, KeyboardInput};
use events::ModifiersState; use events::ModifiersState;
use super::{wayland_kbd, EventsLoopSink, WindowId}; use super::{wayland_kbd, EventsLoopSink, WindowId, DeviceId};
use wayland_client::EventQueueHandle; use wayland_client::EventQueueHandle;
use wayland_client::protocol::wl_keyboard; use wayland_client::protocol::wl_keyboard;
@ -39,17 +39,20 @@ impl wayland_kbd::Handler for KbdHandler {
let vkcode = key_to_vkey(rawkey, keysym); let vkcode = key_to_vkey(rawkey, keysym);
let mut guard = self.sink.lock().unwrap(); let mut guard = self.sink.lock().unwrap();
guard.send_event( guard.send_event(
Event::KeyboardInput( Event::KeyboardInput {
state, device_id: ::DeviceId(::platform::DeviceId::Wayland(DeviceId)),
rawkey as u8, input: KeyboardInput {
vkcode, state: state,
ModifiersState { scancode: rawkey,
shift: mods.shift, virtual_keycode: vkcode,
ctrl: mods.ctrl, modifiers: ModifiersState {
alt: mods.alt, shift: mods.shift,
logo: mods.logo ctrl: mods.ctrl,
} alt: mods.alt,
), logo: mods.logo
},
},
},
wid wid
); );
// send char event only on key press, not release // send char event only on key press, not release

View file

@ -15,3 +15,6 @@ mod context;
mod event_loop; mod event_loop;
mod keyboard; mod keyboard;
mod window; mod window;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;

View file

@ -2,8 +2,8 @@ use {events, libc};
use super::ffi; use super::ffi;
use VirtualKeyCode; use VirtualKeyCode;
pub fn keycode_to_element(scancode: libc::c_uint) -> Option<VirtualKeyCode> { pub fn keysym_to_element(keysym: libc::c_uint) -> Option<VirtualKeyCode> {
Some(match scancode { Some(match keysym {
ffi::XK_BackSpace => events::VirtualKeyCode::Back, ffi::XK_BackSpace => events::VirtualKeyCode::Back,
ffi::XK_Tab => events::VirtualKeyCode::Tab, ffi::XK_Tab => events::VirtualKeyCode::Tab,
//ffi::XK_Linefeed => events::VirtualKeyCode::Linefeed, //ffi::XK_Linefeed => events::VirtualKeyCode::Linefeed,

View file

@ -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<AxisValue>
}
pub struct XInputEventHandler {
display: Arc<XConnection>,
window: ffi::Window,
ic: ffi::XIC,
axis_list: Vec<Axis>,
current_state: InputState,
multitouch: bool,
}
impl XInputEventHandler {
pub fn new(display: &Arc<XConnection>, 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<Event> {
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<Event> {
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<XConnection>) -> Vec<Axis> {
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<AxisValue>) -> (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
}

View file

@ -1,18 +1,22 @@
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))] #![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::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 use self::xdisplay::{XConnection, XNotSupported, XError};
pub mod ffi; pub mod ffi;
use platform::PlatformSpecificWindowBuilderAttributes; 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 events;
mod input;
mod monitor; mod monitor;
mod window; mod window;
mod xdisplay; mod xdisplay;
@ -25,16 +29,76 @@ mod xdisplay;
// the one generated by the macro. // the one generated by the macro.
pub struct EventsLoop { pub struct EventsLoop {
windows: ::std::sync::Mutex<Vec<::std::sync::Arc<Window>>>,
interrupted: ::std::sync::atomic::AtomicBool, interrupted: ::std::sync::atomic::AtomicBool,
display: Arc<XConnection>,
wm_delete_window: ffi::Atom,
windows: Mutex<HashMap<WindowId, WindowData>>,
devices: Mutex<HashMap<DeviceId, Device>>,
xi2ext: XExtension,
root: ffi::Window,
} }
impl EventsLoop { impl EventsLoop {
pub fn new() -> EventsLoop { pub fn new(display: Arc<XConnection>) -> EventsLoop {
EventsLoop { let wm_delete_window = unsafe { (display.xlib.XInternAtom)(display.display, b"WM_DELETE_WINDOW\0".as_ptr() as *const c_char, 0) };
windows: ::std::sync::Mutex::new(vec![]), display.check_errors().expect("Failed to call XInternAtom");
interrupted: ::std::sync::atomic::AtomicBool::new(false),
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) { pub fn interrupt(&self) {
@ -42,41 +106,440 @@ impl EventsLoop {
} }
pub fn poll_events<F>(&self, mut callback: F) pub fn poll_events<F>(&self, mut callback: F)
where F: FnMut(::Event) where F: FnMut(Event)
{ {
let windows = self.windows.lock().unwrap(); let xlib = &self.display.xlib;
for window in windows.iter() {
for event in window.poll_events() { let mut xev = unsafe { mem::uninitialized() };
callback(::Event::WindowEvent { unsafe {
window_id: ::WindowId(::platform::WindowId::X(WindowId(&**window as *const Window as usize))), // Ensure XNextEvent won't block
event: event, 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<F>(&self, mut callback: F) pub fn run_forever<F>(&self, mut callback: F)
where F: FnMut(::Event) where F: FnMut(Event)
{ {
self.interrupted.store(false, ::std::sync::atomic::Ordering::Relaxed); 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 { loop {
self.poll_events(|e| callback(e)); unsafe { (xlib.XNextEvent)(self.display.display, &mut xev) }; // Blocks as necessary
::std::thread::sleep(::std::time::Duration::from_millis(5)); self.process_event(&mut xev, &mut callback);
if self.interrupted.load(::std::sync::atomic::Ordering::Relaxed) { if self.interrupted.load(::std::sync::atomic::Ordering::Relaxed) {
break; 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<F>(&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)] #[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 struct Window2 {
pub window: ::std::sync::Arc<Window>, pub window: Arc<Window>,
events_loop: ::std::sync::Weak<::platform::EventsLoop>, events_loop: Weak<::platform::EventsLoop>,
} }
impl ::std::ops::Deref for Window2 { 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 { impl Window2 {
pub fn new(events_loop: ::std::sync::Arc<::platform::EventsLoop>, display: &Arc<XConnection>, pub fn new(events_loop: Arc<::platform::EventsLoop>,
window: &::WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes) window: &::WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes)
-> Result<Window2, CreationError> -> Result<Window2, CreationError>
{ {
let win = ::std::sync::Arc::new(try!(Window::new(display, window, pl_attribs))); let x_events_loop = if let ::platform::EventsLoop::X(ref e) = *events_loop { e } else { unreachable!() };
if let ::platform::EventsLoop::X(ref ev) = *events_loop { let win = ::std::sync::Arc::new(try!(Window::new(&x_events_loop, window, pl_attribs)));
ev.windows.lock().unwrap().push(win.clone());
} else { // creating IM
// It should not be possible to create an eventloop not matching the backend let im = unsafe {
// in use let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
unreachable!()
} 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 { Ok(Window2 {
window: win, window: win,
events_loop: ::std::sync::Arc::downgrade(&events_loop), events_loop: Arc::downgrade(&events_loop),
}) })
} }
#[inline] #[inline]
pub fn id(&self) -> WindowId { 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 Some(ev) = self.events_loop.upgrade() {
if let ::platform::EventsLoop::X(ref ev) = *ev { if let ::platform::EventsLoop::X(ref ev) = *ev {
let mut windows = ev.windows.lock().unwrap(); 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<WindowConfig>,
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<GenericEventCookie<'b>> {
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,
}
}
}

View file

@ -1,14 +1,11 @@
use {WindowEvent as Event, MouseCursor}; use MouseCursor;
use CreationError; use CreationError;
use CreationError::OsError; use CreationError::OsError;
use libc; use libc;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::{mem, ptr, cmp}; 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::sync::{Arc, Mutex};
use std::os::raw::c_long; use std::os::raw::{c_int, c_long, c_uchar};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
@ -18,14 +15,8 @@ use platform::PlatformSpecificWindowBuilderAttributes;
use platform::MonitorId as PlatformMonitorId; use platform::MonitorId as PlatformMonitorId;
use super::input::XInputEventHandler;
use super::{ffi}; use super::{ffi};
use super::{MonitorId, XConnection}; use super::{MonitorId, XConnection, WindowId, EventsLoop};
// 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(());
}
// TODO: remove me // TODO: remove me
fn with_c_str<F, T>(s: &str, f: F) -> T where F: FnOnce(*const libc::c_char) -> T { fn with_c_str<F, T>(s: &str, f: F) -> T where F: FnOnce(*const libc::c_char) -> T {
@ -47,8 +38,6 @@ pub struct XWindow {
is_fullscreen: bool, is_fullscreen: bool,
screen_id: libc::c_int, screen_id: libc::c_int,
xf86_desk_mode: Option<ffi::XF86VidModeModeInfo>, xf86_desk_mode: Option<ffi::XF86VidModeModeInfo>,
ic: ffi::XIC,
im: ffi::XIM,
window_proxy_data: Arc<Mutex<Option<WindowProxyData>>>, window_proxy_data: Arc<Mutex<Option<WindowProxyData>>>,
} }
@ -65,8 +54,6 @@ impl Drop for XWindow {
// are no longer able to send messages to this window. // are no longer able to send messages to this window.
*self.window_proxy_data.lock().unwrap() = None; *self.window_proxy_data.lock().unwrap() = None;
let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
if self.is_fullscreen { if self.is_fullscreen {
if let Some(mut xf86_desk_mode) = self.xf86_desk_mode { 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); (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.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); (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<GenericEventCookie<'b>> {
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<Event> {
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<Event> {
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 struct Window {
pub x: Arc<XWindow>, pub x: Arc<XWindow>,
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<VecDeque<Event>>,
cursor_state: Mutex<CursorState>, cursor_state: Mutex<CursorState>,
input_handler: Mutex<XInputEventHandler>
} }
impl Window { impl Window {
pub fn new(display: &Arc<XConnection>, window_attrs: &WindowAttributes, pub fn new(ctx: &EventsLoop, window_attrs: &WindowAttributes,
pl_attribs: &PlatformSpecificWindowBuilderAttributes) pl_attribs: &PlatformSpecificWindowBuilderAttributes)
-> Result<Window, CreationError> -> Result<Window, CreationError>
{ {
let display = &ctx.display;
let dimensions = { let dimensions = {
// x11 only applies constraints when the window is actively resized // 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"); display.check_errors().expect("Failed to set window visibility");
} }
// creating window, step 2 // Opt into handling window close
let wm_delete_window = unsafe { unsafe {
let mut wm_delete_window = with_c_str("WM_DELETE_WINDOW", |delete_window| (display.xlib.XSetWMProtocols)(display.display, window, &ctx.wm_delete_window as *const _ as *mut _, 1);
(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);
display.check_errors().expect("Failed to call XSetWMProtocols"); display.check_errors().expect("Failed to call XSetWMProtocols");
(display.xlib.XFlush)(display.display); (display.xlib.XFlush)(display.display);
display.check_errors().expect("Failed to call XFlush"); 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 // Attempt to make keyboard input repeat detectable
unsafe { 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 // creating the window object
let window_proxy_data = WindowProxyData { let window_proxy_data = WindowProxyData {
display: display.clone(), display: display.clone(),
@ -580,19 +375,12 @@ impl Window {
x: Arc::new(XWindow { x: Arc::new(XWindow {
display: display.clone(), display: display.clone(),
window: window, window: window,
im: im,
ic: ic,
screen_id: screen_id, screen_id: screen_id,
is_fullscreen: is_fullscreen, is_fullscreen: is_fullscreen,
xf86_desk_mode: xf86_desk_mode, xf86_desk_mode: xf86_desk_mode,
window_proxy_data: window_proxy_data, 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), cursor_state: Mutex::new(CursorState::Normal),
input_handler: Mutex::new(XInputEventHandler::new(display, window, ic, window_attrs))
}; };
window.set_title(&window_attrs.title); 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] #[inline]
pub fn get_xlib_display(&self) -> *mut libc::c_void { pub fn get_xlib_display(&self) -> *mut libc::c_void {
self.x.display.display as *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(|_| ()) self.x.display.check_errors().map_err(|_| ())
} }
} }
#[inline]
pub fn id(&self) -> WindowId { WindowId(self.x.window) }
} }

View file

@ -1,8 +1,9 @@
use cocoa::{self, appkit, foundation}; use cocoa::{self, appkit, foundation};
use cocoa::appkit::{NSApplication, NSEvent, NSView, NSWindow}; 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 super::window::Window;
use std; use std;
use super::DeviceId;
pub struct EventsLoop { pub struct EventsLoop {
@ -315,8 +316,16 @@ impl EventsLoop {
let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event)); let vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
let state = ElementState::Pressed; let state = ElementState::Pressed;
let code = NSEvent::keyCode(ns_event) as u8; let code = NSEvent::keyCode(ns_event) as u32;
let window_event = WindowEvent::KeyboardInput(state, code, vkey, event_mods(ns_event)); 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)); events.push_back(into_event(window_event));
let event = events.pop_front(); let event = events.pop_front();
self.pending_events.lock().unwrap().extend(events.into_iter()); 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 vkey = to_virtual_key_code(NSEvent::keyCode(ns_event));
let state = ElementState::Released; let state = ElementState::Released;
let code = NSEvent::keyCode(ns_event) as u8; let code = NSEvent::keyCode(ns_event) as u32;
let window_event = WindowEvent::KeyboardInput(state, code, vkey, event_mods(ns_event)); 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)) Some(into_event(window_event))
}, },
@ -342,14 +359,30 @@ impl EventsLoop {
{ {
if !key_pressed && NSEvent::modifierFlags(event).contains(keymask) { if !key_pressed && NSEvent::modifierFlags(event).contains(keymask) {
let state = ElementState::Pressed; let state = ElementState::Pressed;
let code = NSEvent::keyCode(event) as u8; let code = NSEvent::keyCode(event) as u32;
let window_event = WindowEvent::KeyboardInput(state, code, Some(key), event_mods(event)); 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) Some(window_event)
} else if key_pressed && !NSEvent::modifierFlags(event).contains(keymask) { } else if key_pressed && !NSEvent::modifierFlags(event).contains(keymask) {
let state = ElementState::Released; let state = ElementState::Released;
let code = NSEvent::keyCode(event) as u8; let code = NSEvent::keyCode(event) as u32;
let window_event = WindowEvent::KeyboardInput(state, code, Some(key), event_mods(event)); 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) Some(window_event)
} else { } else {
@ -399,15 +432,15 @@ impl EventsLoop {
event event
}, },
appkit::NSLeftMouseDown => { Some(into_event(WindowEvent::MouseInput(ElementState::Pressed, MouseButton::Left))) }, appkit::NSLeftMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Left })) },
appkit::NSLeftMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, 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(ElementState::Pressed, MouseButton::Right))) }, appkit::NSRightMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Right })) },
appkit::NSRightMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, 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(ElementState::Pressed, MouseButton::Middle))) }, appkit::NSOtherMouseDown => { Some(into_event(WindowEvent::MouseInput { device_id: DEVICE_ID, state: ElementState::Pressed, button: MouseButton::Middle })) },
appkit::NSOtherMouseUp => { Some(into_event(WindowEvent::MouseInput(ElementState::Released, 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::NSMouseEntered => { Some(into_event(WindowEvent::MouseEntered { device_id: DEVICE_ID })) },
appkit::NSMouseExited => { Some(into_event(WindowEvent::MouseLeft)) }, appkit::NSMouseExited => { Some(into_event(WindowEvent::MouseLeft { device_id: DEVICE_ID })) },
appkit::NSMouseMoved | appkit::NSMouseMoved |
appkit::NSLeftMouseDragged | appkit::NSLeftMouseDragged |
@ -433,9 +466,9 @@ impl EventsLoop {
let view_rect = NSView::frame(*window.view); let view_rect = NSView::frame(*window.view);
let scale_factor = window.hidpi_factor(); let scale_factor = window.hidpi_factor();
let x = (scale_factor * view_point.x as f32) as i32; 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 i32; let y = (scale_factor * (view_rect.size.height - view_point.y) as f32) as f64;
let window_event = WindowEvent::MouseMoved(x, y); let window_event = WindowEvent::MouseMoved { device_id: DEVICE_ID, position: (x, y) };
let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event }; let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event };
Some(event) Some(event)
}, },
@ -461,14 +494,14 @@ impl EventsLoop {
appkit::NSEventPhaseEnded => TouchPhase::Ended, appkit::NSEventPhaseEnded => TouchPhase::Ended,
_ => TouchPhase::Moved, _ => 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)) Some(into_event(window_event))
}, },
appkit::NSEventTypePressure => { appkit::NSEventTypePressure => {
let pressure = ns_event.pressure(); let pressure = ns_event.pressure();
let stage = ns_event.stage(); 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)) Some(into_event(window_event))
}, },
@ -632,3 +665,6 @@ fn event_mods(event: cocoa::base::id) -> ModifiersState {
logo: flags.contains(appkit::NSCommandKeyMask), 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);

View file

@ -4,6 +4,9 @@ pub use self::events_loop::EventsLoop;
pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor}; pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor};
pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window}; pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId;
use {CreationError}; use {CreationError};
pub struct Window2 { pub struct Window2 {

View file

@ -8,6 +8,7 @@ use std::os::windows::ffi::OsStringExt;
use CursorState; use CursorState;
use WindowEvent as Event; use WindowEvent as Event;
use KeyboardInput;
use events::ModifiersState; use events::ModifiersState;
use super::event; use super::event;
use super::WindowState; use super::WindowState;
@ -140,7 +141,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
}); });
if mouse_outside_window { 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. // Calling TrackMouseEvent in order to receive mouse leave events.
user32::TrackMouseEvent(&mut winapi::TRACKMOUSEEVENT { 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 x = winapi::GET_X_LPARAM(lparam) as f64;
let y = winapi::GET_Y_LPARAM(lparam) as i32; 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 0
}, },
@ -174,7 +175,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
}); });
if mouse_in_window { if mouse_in_window {
send_event(window, MouseLeft); send_event(window, MouseLeft { device_id: DEVICE_ID });
} }
0 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 i32;
let value = value as f32 / winapi::WHEEL_DELTA as f32; 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 0
}, },
winapi::WM_KEYDOWN | winapi::WM_SYSKEYDOWN => { winapi::WM_KEYDOWN | winapi::WM_SYSKEYDOWN => {
use events::WindowEvent::KeyboardInput;
use events::ElementState::Pressed; use events::ElementState::Pressed;
if msg == winapi::WM_SYSKEYDOWN && wparam as i32 == winapi::VK_F4 { if msg == winapi::WM_SYSKEYDOWN && wparam as i32 == winapi::VK_F4 {
user32::DefWindowProcW(window, msg, wparam, lparam) user32::DefWindowProcW(window, msg, wparam, lparam)
} else { } else {
let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam); 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 0
} }
}, },
winapi::WM_KEYUP | winapi::WM_SYSKEYUP => { winapi::WM_KEYUP | winapi::WM_SYSKEYUP => {
use events::WindowEvent::KeyboardInput;
use events::ElementState::Released; use events::ElementState::Released;
let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam); 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 0
}, },
@ -218,7 +233,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::WindowEvent::MouseInput; use events::WindowEvent::MouseInput;
use events::MouseButton::Left; use events::MouseButton::Left;
use events::ElementState::Pressed; use events::ElementState::Pressed;
send_event(window, MouseInput(Pressed, Left)); send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left });
0 0
}, },
@ -226,7 +241,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::WindowEvent::MouseInput; use events::WindowEvent::MouseInput;
use events::MouseButton::Left; use events::MouseButton::Left;
use events::ElementState::Released; use events::ElementState::Released;
send_event(window, MouseInput(Released, Left)); send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Left });
0 0
}, },
@ -234,7 +249,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::WindowEvent::MouseInput; use events::WindowEvent::MouseInput;
use events::MouseButton::Right; use events::MouseButton::Right;
use events::ElementState::Pressed; use events::ElementState::Pressed;
send_event(window, MouseInput(Pressed, Right)); send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right });
0 0
}, },
@ -242,7 +257,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::WindowEvent::MouseInput; use events::WindowEvent::MouseInput;
use events::MouseButton::Right; use events::MouseButton::Right;
use events::ElementState::Released; use events::ElementState::Released;
send_event(window, MouseInput(Released, Right)); send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Right });
0 0
}, },
@ -250,7 +265,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::WindowEvent::MouseInput; use events::WindowEvent::MouseInput;
use events::MouseButton::Middle; use events::MouseButton::Middle;
use events::ElementState::Pressed; use events::ElementState::Pressed;
send_event(window, MouseInput(Pressed, Middle)); send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle });
0 0
}, },
@ -258,7 +273,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::WindowEvent::MouseInput; use events::WindowEvent::MouseInput;
use events::MouseButton::Middle; use events::MouseButton::Middle;
use events::ElementState::Released; use events::ElementState::Released;
send_event(window, MouseInput(Released, Middle)); send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Middle });
0 0
}, },
@ -267,7 +282,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::MouseButton::Other; use events::MouseButton::Other;
use events::ElementState::Pressed; 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 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 0
}, },
@ -276,7 +291,7 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
use events::MouseButton::Other; use events::MouseButton::Other;
use events::ElementState::Released; use events::ElementState::Released;
let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int; 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 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);

View file

@ -26,10 +26,10 @@ pub fn get_key_mods() -> ModifiersState {
} }
pub fn vkeycode_to_element(wparam: winapi::WPARAM, lparam: winapi::LPARAM) -> (ScanCode, Option<VirtualKeyCode>) { pub fn vkeycode_to_element(wparam: winapi::WPARAM, lparam: winapi::LPARAM) -> (ScanCode, Option<VirtualKeyCode>) {
let scancode = ((lparam >> 16) & 0xff) as u8; let scancode = ((lparam >> 16) & 0xff) as u32;
let extended = (lparam & 0x01000000) != 0; let extended = (lparam & 0x01000000) != 0;
let vk = match wparam as i32 { 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_CONTROL => if extended { winapi::VK_RCONTROL } else { winapi::VK_LCONTROL },
winapi::VK_MENU => if extended { winapi::VK_RMENU } else { winapi::VK_LMENU }, winapi::VK_MENU => if extended { winapi::VK_RMENU } else { winapi::VK_LMENU },
other => other other => other