aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml6
-rw-r--r--src/api/x11/ffi.rs2
-rw-r--r--src/api/x11/input.rs328
-rw-r--r--src/api/x11/mod.rs1
-rw-r--r--src/api/x11/window.rs163
-rw-r--r--src/api/x11/xdisplay.rs3
6 files changed, 409 insertions, 94 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 709fe61..7642e48 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -77,16 +77,16 @@ dwmapi-sys = "0.1"
osmesa-sys = "0.0.5"
wayland-client = { version = "0.2.0", features = ["egl", "dlopen"] }
wayland-kbd = "0.2.0"
-x11-dl = "=1.0.1"
+x11-dl = "~2.0"
[target.x86_64-unknown-linux-gnu.dependencies]
osmesa-sys = "0.0.5"
wayland-client = { version = "0.2.0", features = ["egl", "dlopen"] }
wayland-kbd = "0.2.0"
-x11-dl = "=1.0.1"
+x11-dl = "~2.0"
[target.arm-unknown-linux-gnueabihf.dependencies]
osmesa-sys = "0.0.5"
wayland-client = { version = "0.2.0", features = ["egl", "dlopen"] }
wayland-kbd = "0.2.0"
-x11-dl = "=1.0.1"
+x11-dl = "~2.0"
diff --git a/src/api/x11/ffi.rs b/src/api/x11/ffi.rs
index 8c9a35d..3d4f0ed 100644
--- a/src/api/x11/ffi.rs
+++ b/src/api/x11/ffi.rs
@@ -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;
diff --git a/src/api/x11/input.rs b/src/api/x11/input.rs
new file mode 100644
index 0000000..25add95
--- /dev/null
+++ b/src/api/x11/input.rs
@@ -0,0 +1,328 @@
+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
+}
+
+#[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 {
+ /// 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
+}
+
+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.
+ // 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; 2] = [0, 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,
+ ffi::XI_Enter,
+ ffi::XI_Leave,
+ ffi::XI_FocusIn,
+ ffi::XI_FocusOut
+ ];
+ 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::{Focused, MouseInput, MouseMoved, MouseWheel};
+ use events::ElementState::{Pressed, Released};
+ use events::MouseButton::{Left, Right, Middle};
+ use events::MouseScrollDelta::{PixelDelta, LineDelta};
+
+ match cookie.evtype {
+ 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 | 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)))
+ } 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)};
+ 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
+ }
+ }
+ },
+ 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();
+ None
+ },
+ ffi::XI_Leave => None,
+ ffi::XI_FocusIn => Some(Focused(true)),
+ ffi::XI_FocusOut => Some(Focused(false)),
+ _ => 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 } {
+ // 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!() }
+ }
+ })
+ },
+ _ => {}
+ }
+ }
+ }
+
+ 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) => 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
+}
+
diff --git a/src/api/x11/mod.rs b/src/api/x11/mod.rs
index e900583..1ba6bc7 100644
--- a/src/api/x11/mod.rs
+++ b/src/api/x11/mod.rs
@@ -7,6 +7,7 @@ pub use self::xdisplay::XConnection;
pub mod ffi;
mod events;
+mod input;
mod monitor;
mod window;
mod xdisplay;
diff --git a/src/api/x11/window.rs b/src/api/x11/window.rs
index 04ad2ab..79118ff 100644
--- a/src/api/x11/window.rs
+++ b/src/api/x11/window.rs
@@ -2,6 +2,7 @@ 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;
@@ -20,7 +21,8 @@ use api::egl::Context as EglContext;
use platform::MonitorID as PlatformMonitorID;
-use super::{events, ffi};
+use super::input::XInputEventHandler;
+use super::{ffi};
use super::{MonitorID, XConnection};
// XOpenIM doesn't seem to be thread-safe
@@ -106,33 +108,69 @@ 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> Iterator for PollEventsIterator<'a> {
type Item = Event;
fn next(&mut self) -> Option<Event> {
- if let Some(ev) = self.window.pending_events.lock().unwrap().pop_front() {
- return Some(ev);
- }
+ 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() };
- let res = unsafe { (self.window.x.display.xlib.XCheckMaskEvent)(self.window.x.display.display, -1, &mut xev) };
+ let res = unsafe { (xlib.XCheckMaskEvent)(self.window.x.display.display, -1, &mut xev) };
if res == 0 {
- let res = unsafe { (self.window.x.display.xlib.XCheckTypedEvent)(self.window.x.display.display, ffi::ClientMessage, &mut xev) };
+ let res = unsafe { (xlib.XCheckTypedEvent)(self.window.x.display.display, ffi::ClientMessage, &mut xev) };
if res == 0 {
- return None;
+ let res = unsafe { (xlib.XCheckTypedEvent)(self.window.x.display.display, ffi::GenericEvent, &mut xev) };
+ if res == 0 {
+ return None;
+ }
}
}
match xev.get_type() {
ffi::KeymapNotify => {
- unsafe { (self.window.x.display.xlib.XRefreshKeyboardMapping)(mem::transmute(&xev)); }
+ unsafe { (xlib.XRefreshKeyboardMapping)(mem::transmute(&xev)); }
},
ffi::ClientMessage => {
@@ -164,93 +202,34 @@ 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) };
- }
-
- 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));
- }
+ 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);
}
-
- 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::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
+ 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(_) => {}
+ }
+ },
+ _ => {}
}
- _ => None
- };
-
- match button {
- Some(button) =>
- return Some(MouseInput(state, button)),
- None => ()
- };
- },
+ }
+ }
- _ => ()
+ _ => {}
};
}
}
@@ -296,6 +275,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 +588,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
diff --git a/src/api/x11/xdisplay.rs b/src/api/x11/xdisplay.rs
index 9037155..2576b74 100644
--- a/src/api/x11/xdisplay.rs
+++ b/src/api/x11/xdisplay.rs
@@ -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,