diff options
Diffstat (limited to 'src/api')
| -rw-r--r-- | src/api/x11/ffi.rs | 2 | ||||
| -rw-r--r-- | src/api/x11/input.rs | 328 | ||||
| -rw-r--r-- | src/api/x11/mod.rs | 1 | ||||
| -rw-r--r-- | src/api/x11/window.rs | 163 | ||||
| -rw-r--r-- | src/api/x11/xdisplay.rs | 3 | 
5 files changed, 406 insertions, 91 deletions
| 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 15e27ce..b13a94a 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, | 
