Use XInput2 for event handling

This provides smooth scrolling for touchpad devices and will
enable support for touch events etc. in future.
This commit is contained in:
Robert Knight 2015-06-22 22:49:48 +01:00
parent 164d47b93c
commit 94c31e42a4
5 changed files with 416 additions and 86 deletions

View file

@ -2,6 +2,8 @@ pub use x11_dl::keysym::*;
pub use x11_dl::xcursor::*;
pub use x11_dl::xf86vmode::*;
pub use x11_dl::xlib::*;
pub use x11_dl::xinput::*;
pub use x11_dl::xinput2::*;
pub use self::glx::types::GLXContext;

337
src/api/x11/input.rs Normal file
View file

@ -0,0 +1,337 @@
use std::sync::Arc;
use libc;
use std::{mem, ptr};
use std::ffi::CString;
use std::slice::from_raw_parts;
use events::Event;
use super::{events, ffi};
use super::XConnection;
#[derive(Debug)]
enum AxisType {
HorizontalScroll,
VerticalScroll,
Other
}
#[derive(Debug)]
struct Axis {
id: i32,
device_id: i32,
axis_number: i32,
axis_type: AxisType
}
#[derive(Debug)]
struct AxisValue {
device_id: i32,
axis_number: i32,
value: f64
}
struct InputState {
cursor_pos: (f64, f64),
axis_values: Vec<AxisValue>
}
pub struct XInputEventHandler {
display: Arc<XConnection>,
window: ffi::Window,
ic: ffi::XIC,
axis_list: Vec<Axis>,
current_state: InputState
}
impl XInputEventHandler {
pub fn new(display: &Arc<XConnection>, window: ffi::Window, ic: ffi::XIC) -> 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
let mut mask: [libc::c_uchar; 1] = [0];
let mut input_event_mask = ffi::XIEventMask {
deviceid: ffi::XIAllDevices,
mask_len: mask.len() as i32,
mask: mask.as_mut_ptr()
};
let events = &[
ffi::XI_ButtonPress,
ffi::XI_ButtonRelease,
ffi::XI_Motion
];
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()
}
}
}
pub fn translate_key_event(&self, event: &mut ffi::XKeyEvent) -> Vec<Event> {
use events::Event::{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 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());
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));
translated_events
}
pub fn translate_event(&mut self, cookie: &ffi::XGenericEventCookie) -> Option<Event> {
use events::Event::{MouseInput, MouseMoved, MouseWheel};
use events::ElementState::{Pressed, Released};
use events::MouseButton::{Left, Right, Middle};
use events::MouseScrollDelta::{PixelDelta, LineDelta};
match cookie.evtype {
ffi::XI_KeyPress | ffi::XI_KeyRelease => {
let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)};
if cookie.evtype == ffi::XI_KeyPress {
if event_data.flags & ffi::XIKeyRepeat == 0 {
println!("XInput Key {} pressed", event_data.detail);
None
} else {
None
}
} else {
None
}
},
ffi::XI_ButtonPress | ffi::XI_ButtonRelease => {
let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)};
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 => {
if event_data.flags & ffi::XIPointerEmulated == 0 {
Some(MouseWheel(LineDelta(0.0, 1.0)))
} else {
// emulated button event from a touch/smooth-scroll
// event. Scrolling is instead reported via the
// XI_Motion event handler
None
}
},
ffi::Button5 => {
if event_data.flags & ffi::XIPointerEmulated == 0 {
Some(MouseWheel(LineDelta(0.0, -1.0)))
} else {
None
}
},
_ => None
}
},
ffi::XI_Motion => {
let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)};
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(PixelDelta(scroll_delta.0 as f32, scroll_delta.1 as f32)))
} 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
}
}
},
_ => None
}
}
}
fn read_input_axis_info(display: &Arc<XConnection>) -> Vec<Axis> {
let mut axis_list = Vec::new();
let mut device_count = 0;
// only get events from the master devices which are 'attached'
// to the keyboard or cursor
let devices = unsafe{
(display.xinput2.XIQueryDevice)(display.display, ffi::XIAllMasterDevices, &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 } {
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!() }
}
})
},
ffi::XIValuatorClass => {
let valuator_class: &ffi::XIValuatorClassInfo = unsafe{mem::transmute(class)};
axis_list.push(Axis{
id: valuator_class.sourceid,
device_id: device.deviceid,
axis_number: valuator_class.number,
axis_type: AxisType::Other
})
},
_ => {}
}
}
}
axis_list.sort_by(|a, b| {
if a.device_id != b.device_id {
a.device_id.cmp(&b.device_id)
} else if a.id != b.id {
a.id.cmp(&b.id)
} else {
a.axis_number.cmp(&b.axis_number)
}
});
axis_list
}
/// Given an input motion event for an axis and the previous
/// state of the axises, 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) => axis_value - prev_axis_values[idx].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,
AxisType::VerticalScroll => scroll_delta.1 = delta,
_ => {}
}
}
}
scroll_delta
}

View file

@ -7,6 +7,7 @@ pub use self::xdisplay::XConnection;
pub mod ffi;
mod events;
mod input;
mod monitor;
mod window;
mod xdisplay;

View file

@ -2,10 +2,12 @@ use {Event, BuilderAttribs, MouseCursor};
use CreationError;
use CreationError::OsError;
use libc;
use std::borrow::Borrow;
use std::{mem, ptr};
use std::cell::Cell;
use std::sync::atomic::AtomicBool;
use std::collections::VecDeque;
use std::slice::from_raw_parts;
use std::sync::{Arc, Mutex};
use Api;
@ -20,6 +22,7 @@ use api::egl::Context as EglContext;
use platform::MonitorID as PlatformMonitorID;
use super::input::XInputEventHandler;
use super::{events, ffi};
use super::{MonitorID, XConnection};
@ -106,8 +109,64 @@ 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,
window: &'a Window
}
impl<'a> PollEventsIterator<'a> {
fn queue_event(&mut self, event: Event) {
self.window.pending_events.lock().unwrap().push_back(event);
}
fn process_generic_event(&mut self, event: &ffi::XEvent) {
if let Some(cookie) = GenericEventCookie::from_event(self.window.x.display.borrow(), *event) {
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.queue_event(event),
None => {}
}
},
Err(_) => {}
}
},
_ => {}
}
}
}
}
impl<'a> Iterator for PollEventsIterator<'a> {
@ -126,7 +185,10 @@ impl<'a> Iterator for PollEventsIterator<'a> {
let res = unsafe { (self.window.x.display.xlib.XCheckTypedEvent)(self.window.x.display.display, ffi::ClientMessage, &mut xev) };
if res == 0 {
return None;
let res = unsafe { (self.window.x.display.xlib.XCheckTypedEvent)(self.window.x.display.display, ffi::GenericEvent, &mut xev) };
if res == 0 {
return None;
}
}
}
@ -164,93 +226,16 @@ impl<'a> Iterator for PollEventsIterator<'a> {
return Some(Refresh);
},
ffi::MotionNotify => {
use events::Event::MouseMoved;
let event: &ffi::XMotionEvent = unsafe { mem::transmute(&xev) };
return Some(MouseMoved((event.x as i32, event.y as i32)));
},
ffi::KeyPress | ffi::KeyRelease => {
use events::Event::{KeyboardInput, ReceivedCharacter};
use events::ElementState::{Pressed, Released};
let event: &mut ffi::XKeyEvent = unsafe { mem::transmute(&mut xev) };
if event.type_ == ffi::KeyPress {
let raw_ev: *mut ffi::XKeyEvent = event;
unsafe { (self.window.x.display.xlib.XFilterEvent)(mem::transmute(raw_ev), self.window.x.window) };
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.queue_event(event);
}
let state = if xev.get_type() == ffi::KeyPress { Pressed } else { Released };
let mut kp_keysym = 0;
let written = unsafe {
use std::str;
let mut buffer: [u8; 16] = [mem::uninitialized(); 16];
let raw_ev: *mut ffi::XKeyEvent = event;
let count = (self.window.x.display.xlib.Xutf8LookupString)(self.window.x.ic, mem::transmute(raw_ev),
mem::transmute(buffer.as_mut_ptr()),
buffer.len() as libc::c_int, &mut kp_keysym, ptr::null_mut());
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
};
{
let mut pending = self.window.pending_events.lock().unwrap();
for chr in written.chars() {
pending.push_back(ReceivedCharacter(chr));
}
}
let mut keysym = unsafe {
(self.window.x.display.xlib.XKeycodeToKeysym)(self.window.x.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);
return Some(KeyboardInput(state, event.keycode as u8, vkey));
},
ffi::GenericEvent => { self.process_generic_event(&mut xev); }
ffi::ButtonPress | ffi::ButtonRelease => {
use events::Event::{MouseInput, MouseWheel};
use events::ElementState::{Pressed, Released};
use events::MouseButton::{Left, Right, Middle};
use events::MouseScrollDelta::{LineDelta};
let event: &ffi::XButtonEvent = unsafe { mem::transmute(&xev) };
let state = if xev.get_type() == ffi::ButtonPress { Pressed } else { Released };
let button = match event.button {
ffi::Button1 => Some(Left),
ffi::Button2 => Some(Middle),
ffi::Button3 => Some(Right),
ffi::Button4 => {
let delta = LineDelta(0.0, 1.0);
self.window.pending_events.lock().unwrap().push_back(MouseWheel(delta));
None
}
ffi::Button5 => {
let delta = LineDelta(0.0, -1.0);
self.window.pending_events.lock().unwrap().push_back(MouseWheel(delta));
None
}
_ => None
};
match button {
Some(button) =>
return Some(MouseInput(state, button)),
None => ()
};
},
_ => ()
_ => {}
};
}
}
@ -296,6 +281,7 @@ pub struct Window {
/// Events that have been retreived with XLib but not dispatched with iterators yet
pending_events: Mutex<VecDeque<Event>>,
cursor_state: Mutex<CursorState>,
input_handler: Mutex<XInputEventHandler>
}
impl Window {
@ -608,6 +594,7 @@ impl Window {
pixel_format: pixel_format,
pending_events: Mutex::new(VecDeque::new()),
cursor_state: Mutex::new(CursorState::Normal),
input_handler: Mutex::new(XInputEventHandler::new(display, window, ic))
};
// returning

View file

@ -12,6 +12,7 @@ pub struct XConnection {
pub xlib: ffi::Xlib,
pub xf86vmode: ffi::Xf86vmode,
pub xcursor: ffi::Xcursor,
pub xinput2: ffi::XInput2,
pub glx: Option<ffi::glx::Glx>,
pub egl: Option<Egl>,
pub display: *mut ffi::Display,
@ -30,6 +31,7 @@ impl XConnection {
let xlib = try!(ffi::Xlib::open().map_err(|_| XNotSupported));
let xcursor = try!(ffi::Xcursor::open().map_err(|_| XNotSupported));
let xf86vmode = try!(ffi::Xf86vmode::open().map_err(|_| XNotSupported));
let xinput2 = try!(ffi::XInput2::open().map_err(|_| XNotSupported));
unsafe extern "C" fn x_error_callback(_: *mut ffi::Display, event: *mut ffi::XErrorEvent)
-> libc::c_int
@ -86,6 +88,7 @@ impl XConnection {
xlib: xlib,
xf86vmode: xf86vmode,
xcursor: xcursor,
xinput2: xinput2,
glx: glx,
egl: egl,
display: display,