use std::sync::Arc; use libc; use std::{mem, ptr}; use std::ffi::CString; use std::slice::from_raw_parts; use WindowAttributes; 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, scroll_increment: f64, } #[derive(Debug)] struct AxisValue { device_id: i32, axis_number: i32, value: f64 } struct InputState { /// Last-seen cursor position within a window in (x, y) /// coordinates cursor_pos: (f64, f64), /// Last-seen positions of axes, used to report delta /// movements when a new absolute axis value is received axis_values: Vec } pub struct XInputEventHandler { display: Arc, window: ffi::Window, ic: ffi::XIC, axis_list: Vec, current_state: InputState, multitouch: bool, } impl XInputEventHandler { pub fn new(display: &Arc, window: ffi::Window, ic: ffi::XIC, window_attrs: &WindowAttributes) -> XInputEventHandler { // query XInput support let mut opcode: libc::c_int = 0; let mut event: libc::c_int = 0; let mut error: libc::c_int = 0; let xinput_str = CString::new("XInputExtension").unwrap(); unsafe { if (display.xlib.XQueryExtension)(display.display, xinput_str.as_ptr(), &mut opcode, &mut event, &mut error) == ffi::False { panic!("XInput not available") } } let mut xinput_major_ver = ffi::XI_2_Major; let mut xinput_minor_ver = ffi::XI_2_Minor; unsafe { if (display.xinput2.XIQueryVersion)(display.display, &mut xinput_major_ver, &mut xinput_minor_ver) != ffi::Success as libc::c_int { panic!("Unable to determine XInput version"); } } // specify the XInput events we want to receive. // Button clicks and mouse events are handled via XInput // events. Key presses are still handled via plain core // X11 events. let mut mask: [libc::c_uchar; 3] = [0; 3]; let mut input_event_mask = ffi::XIEventMask { deviceid: ffi::XIAllMasterDevices, mask_len: mask.len() as i32, mask: mask.as_mut_ptr() }; let events = &[ ffi::XI_ButtonPress, ffi::XI_ButtonRelease, ffi::XI_Motion, ffi::XI_Enter, ffi::XI_Leave, ffi::XI_FocusIn, ffi::XI_FocusOut, ffi::XI_TouchBegin, ffi::XI_TouchUpdate, ffi::XI_TouchEnd, ]; for event in events { ffi::XISetMask(&mut mask, *event); } unsafe { match (display.xinput2.XISelectEvents)(display.display, window, &mut input_event_mask, 1) { status if status as u8 == ffi::Success => (), err => panic!("Failed to select events {:?}", err) } } XInputEventHandler { display: display.clone(), window: window, ic: ic, axis_list: read_input_axis_info(display), current_state: InputState { cursor_pos: (0.0, 0.0), axis_values: Vec::new() }, multitouch: window_attrs.multitouch, } } pub fn translate_key_event(&self, event: &mut ffi::XKeyEvent) -> Vec { use events::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 { use events::Event::{Focused, MouseInput, 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))) } 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))) } 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)), ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { if !self.multitouch { return None } let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)}; let phase = match cookie.evtype { ffi::XI_TouchBegin => TouchPhase::Started, ffi::XI_TouchUpdate => TouchPhase::Moved, ffi::XI_TouchEnd => TouchPhase::Ended, _ => unreachable!() }; Some(Event::Touch(Touch { phase: phase, location: (event_data.event_x, event_data.event_y), id: event_data.detail as u64, })) } _ => None } } } fn read_input_axis_info(display: &Arc) -> Vec { let mut axis_list = Vec::new(); let mut device_count = 0; // Check all input devices for scroll axes. let devices = unsafe{ (display.xinput2.XIQueryDevice)(display.display, ffi::XIAllDevices, &mut device_count) }; for i in 0..device_count { let device = unsafe { *(devices.offset(i as isize)) }; for k in 0..device.num_classes { let class = unsafe { *(device.classes.offset(k as isize)) }; match unsafe { (*class)._type } { // Note that scroll axis // are reported both as 'XIScrollClass' and 'XIValuatorClass' // axes. For the moment we only care about scrolling axes. ffi::XIScrollClass => { let scroll_class: &ffi::XIScrollClassInfo = unsafe{mem::transmute(class)}; axis_list.push(Axis{ id: scroll_class.sourceid, device_id: device.deviceid, axis_number: scroll_class.number, axis_type: match scroll_class.scroll_type { ffi::XIScrollTypeHorizontal => AxisType::HorizontalScroll, ffi::XIScrollTypeVertical => AxisType::VerticalScroll, _ => { unreachable!() } }, scroll_increment: scroll_class.increment, }) }, _ => {} } } } unsafe { (display.xinput2.XIFreeDeviceInfo)(devices); } axis_list } /// Given an input motion event for an axis and the previous /// state of the axes, return the horizontal/vertical /// scroll deltas fn calc_scroll_deltas(event: &ffi::XIDeviceEvent, axis_id: i32, axis_value: f64, axis_list: &[Axis], prev_axis_values: &mut Vec) -> (f64, f64) { let prev_value_pos = prev_axis_values.iter().position(|prev_axis| { prev_axis.device_id == event.sourceid && prev_axis.axis_number == axis_id }); let delta = match prev_value_pos { Some(idx) => prev_axis_values[idx].value - axis_value, None => 0.0 }; let new_axis_value = AxisValue{ device_id: event.sourceid, axis_number: axis_id, value: axis_value }; match prev_value_pos { Some(idx) => prev_axis_values[idx] = new_axis_value, None => prev_axis_values.push(new_axis_value) } let mut scroll_delta = (0.0, 0.0); for axis in axis_list.iter() { if axis.id == event.sourceid && axis.axis_number == axis_id { match axis.axis_type { AxisType::HorizontalScroll => scroll_delta.0 = delta / axis.scroll_increment, AxisType::VerticalScroll => scroll_delta.1 = delta / axis.scroll_increment } } } scroll_delta }