1 // XInput2 example for x11-rs
2 //
3 // This is a basic example showing how to use XInput2 to read
4 // keyboard, mouse and other input events in X11.
5 //
6 // See Pete Hutterer's "XI2 Recipes" blog series,
7 // starting at http://who-t.blogspot.co.uk/2009/05/xi2-recipes-part-1.html
8 // for a guide.
9 
10 #![cfg_attr(not(feature = "xlib"), allow(dead_code))]
11 #![cfg_attr(not(feature = "xlib"), allow(unused_imports))]
12 
13 extern crate x11;
14 extern crate libc;
15 
16 use std::ffi::CString;
17 use std::ptr::{
18   null,
19   null_mut,
20 };
21 use std::mem::{transmute, zeroed};
22 use std::os::raw::*;
23 use std::slice::{from_raw_parts};
24 use x11::{xlib, xinput2};
25 
26 /// Provides a basic framework for connecting to an X Display,
27 /// creating a window, displaying it and running the event loop
28 pub struct DemoWindow {
29     pub display: *mut xlib::Display,
30     pub window: xlib::Window,
31 
32     wm_protocols: xlib::Atom,
33     wm_delete_window: xlib::Atom
34 }
35 
36 impl DemoWindow {
37     /// Create a new window with a given title and size
new(title: &str, width: u32, height: u32) -> DemoWindow38     pub fn new(title: &str, width: u32, height: u32) -> DemoWindow {
39         unsafe {
40             // Open display
41             let display = xlib::XOpenDisplay(null());
42             if display == null_mut() {
43                 panic!("can't open display");
44             }
45 
46             // Load atoms
47             let wm_delete_window_str = CString::new("WM_DELETE_WINDOW").unwrap();
48             let wm_protocols_str = CString::new("WM_PROTOCOLS").unwrap();
49 
50             let wm_delete_window = xlib::XInternAtom(display, wm_delete_window_str.as_ptr(), xlib::False);
51             let wm_protocols = xlib::XInternAtom(display, wm_protocols_str.as_ptr(), xlib::False);
52 
53             if wm_delete_window == 0 || wm_protocols == 0 {
54                 panic!("can't load atoms");
55             }
56 
57             // Create window
58             let screen_num = xlib::XDefaultScreen(display);
59             let root = xlib::XRootWindow(display, screen_num);
60             let white_pixel = xlib::XWhitePixel(display, screen_num);
61 
62             let mut attributes: xlib::XSetWindowAttributes = zeroed();
63             attributes.background_pixel = white_pixel;
64 
65             let window = xlib::XCreateWindow(display, root, 0, 0, width as c_uint, height as c_uint, 0, 0,
66                                              xlib::InputOutput as c_uint, null_mut(),
67                                              xlib::CWBackPixel, &mut attributes);
68             // Set window title
69             let title_str = CString::new(title).unwrap();
70             xlib::XStoreName(display, window, title_str.as_ptr() as *mut _);
71 
72             // Subscribe to delete (close) events
73             let mut protocols = [wm_delete_window];
74 
75             if xlib::XSetWMProtocols(display, window, &mut protocols[0] as *mut xlib::Atom, 1) == xlib::False {
76                 panic!("can't set WM protocols");
77             }
78 
79             DemoWindow{
80                 display: display,
81                 window: window,
82                 wm_protocols: wm_protocols,
83                 wm_delete_window: wm_delete_window
84             }
85         }
86     }
87 
88     /// Display the window
show(&mut self)89     pub fn show(&mut self) {
90         unsafe {
91             xlib::XMapWindow(self.display, self.window);
92         }
93     }
94 
95     /// Process events for the window. Window close events are handled automatically,
96     /// other events are passed on to |event_handler|
run_event_loop<EventHandler>(&mut self, mut event_handler: EventHandler) where EventHandler: FnMut(&xlib::XEvent)97     pub fn run_event_loop<EventHandler>(&mut self, mut event_handler: EventHandler)
98         where EventHandler: FnMut(&xlib::XEvent) {
99             let mut event: xlib::XEvent = unsafe{zeroed()};
100             loop {
101                 unsafe{xlib::XNextEvent(self.display, &mut event)};
102                 match event.get_type() {
103                     xlib::ClientMessage => {
104                         let xclient: xlib::XClientMessageEvent = From::from(event);
105 
106                         // WM_PROTOCOLS client message
107                         if xclient.message_type == self.wm_protocols && xclient.format == 32 {
108                             let protocol = xclient.data.get_long(0) as xlib::Atom;
109 
110                             // WM_DELETE_WINDOW (close event)
111                             if protocol == self.wm_delete_window {
112                                 break;
113                             }
114                         }
115                     },
116                     _ => event_handler(&event)
117                 }
118             }
119         }
120 }
121 
122 impl Drop for DemoWindow {
123     /// Destroys the window and disconnects from the display
drop(&mut self)124     fn drop(&mut self) {
125         unsafe {
126             xlib::XDestroyWindow(self.display, self.window);
127             xlib::XCloseDisplay(self.display);
128         }
129     }
130 }
131 
132 const TITLE: &'static str = "XInput Demo";
133 const DEFAULT_WIDTH: c_uint = 640;
134 const DEFAULT_HEIGHT: c_uint = 480;
135 
136 #[derive(Debug)]
137 enum AxisType {
138     HorizontalScroll,
139     VerticalScroll,
140     Other
141 }
142 
143 #[derive(Debug)]
144 struct Axis {
145     id: i32,
146     device_id: i32,
147     axis_number: i32,
148     axis_type: AxisType
149 }
150 
151 #[derive(Debug)]
152 struct AxisValue {
153     device_id: i32,
154     axis_number: i32,
155     value: f64
156 }
157 
158 struct InputState {
159     cursor_pos: (f64, f64),
160     axis_values: Vec<AxisValue>
161 }
162 
read_input_axis_info(display: *mut xlib::Display) -> Vec<Axis>163 fn read_input_axis_info(display: *mut xlib::Display) -> Vec<Axis> {
164     let mut axis_list = Vec::new();
165     let mut device_count = 0;
166 
167     // only get events from the master devices which are 'attached'
168     // to the keyboard or cursor
169     let devices = unsafe{xinput2::XIQueryDevice(display, xinput2::XIAllMasterDevices, &mut device_count)};
170     for i in 0..device_count {
171         let device = unsafe { *(devices.offset(i as isize)) };
172         for k in 0..device.num_classes {
173             let class = unsafe { *(device.classes.offset(k as isize)) };
174             match unsafe { (*class)._type } {
175                 xinput2::XIScrollClass => {
176                     let scroll_class: &xinput2::XIScrollClassInfo = unsafe{transmute(class)};
177                     axis_list.push(Axis{
178                         id: scroll_class.sourceid,
179                         device_id: device.deviceid,
180                         axis_number: scroll_class.number,
181                         axis_type: match scroll_class.scroll_type {
182                             xinput2::XIScrollTypeHorizontal => AxisType::HorizontalScroll,
183                             xinput2::XIScrollTypeVertical => AxisType::VerticalScroll,
184                             _ => { unreachable!() }
185                         }
186                     })
187                 },
188                 xinput2::XIValuatorClass => {
189                     let valuator_class: &xinput2::XIValuatorClassInfo = unsafe{transmute(class)};
190                     axis_list.push(Axis{
191                         id: valuator_class.sourceid,
192                         device_id: device.deviceid,
193                         axis_number: valuator_class.number,
194                         axis_type: AxisType::Other
195                     })
196                 },
197                 _ => { /* TODO */ }
198             }
199         }
200     }
201 
202     axis_list.sort_by(|a, b| {
203         if a.device_id != b.device_id {
204             a.device_id.cmp(&b.device_id)
205         } else if a.id != b.id {
206             a.id.cmp(&b.id)
207         } else {
208             a.axis_number.cmp(&b.axis_number)
209         }
210     });
211     axis_list
212 }
213 
214 /// Given an input motion event for an axis and the previous
215 /// state of the axises, return the horizontal/vertical
216 /// scroll deltas
calc_scroll_deltas(event: &xinput2::XIDeviceEvent, axis_id: i32, axis_value: f64, axis_list: &[Axis], prev_axis_values: &mut Vec<AxisValue>) -> (f64, f64)217 fn calc_scroll_deltas(event: &xinput2::XIDeviceEvent,
218                      axis_id: i32,
219                      axis_value: f64,
220                      axis_list: &[Axis],
221                      prev_axis_values: &mut Vec<AxisValue>) -> (f64, f64) {
222     let prev_value_pos = prev_axis_values.iter().position(|prev_axis| {
223         prev_axis.device_id == event.sourceid &&
224             prev_axis.axis_number == axis_id
225     });
226     let delta = match prev_value_pos {
227         Some(idx) => axis_value - prev_axis_values[idx].value,
228         None => 0.0
229     };
230 
231     let new_axis_value = AxisValue{
232         device_id: event.sourceid,
233         axis_number: axis_id,
234         value: axis_value
235     };
236 
237     match prev_value_pos {
238         Some(idx) => prev_axis_values[idx] = new_axis_value,
239         None => prev_axis_values.push(new_axis_value)
240     }
241 
242     let mut scroll_delta = (0.0, 0.0);
243 
244     for axis in axis_list.iter() {
245         if axis.id == event.sourceid &&
246             axis.axis_number == axis_id {
247                 match axis.axis_type {
248                     AxisType::HorizontalScroll => scroll_delta.0 = delta,
249                     AxisType::VerticalScroll => scroll_delta.1 = delta,
250                     _ => {}
251                 }
252             }
253     }
254 
255     scroll_delta
256 }
257 
258 #[cfg(not(all(feature = "xlib", feature = "xinput")))]
main()259 fn main () {
260     panic!("this example requires `--features 'xlib xinput'`");
261 }
262 
263 #[cfg(all(feature = "xlib", feature = "xinput"))]
main()264 fn main () {
265     let mut demo_window = DemoWindow::new(TITLE, DEFAULT_WIDTH, DEFAULT_HEIGHT);
266 
267     // query XInput support
268     let mut opcode: c_int = 0;
269     let mut event: c_int = 0;
270     let mut error: c_int = 0;
271     let xinput_str = CString::new("XInputExtension").unwrap();
272     let xinput_available = unsafe {
273         xlib::XQueryExtension(demo_window.display, xinput_str.as_ptr(), &mut opcode, &mut event, &mut error)
274     };
275     if xinput_available == xlib::False {
276         panic!("XInput not available")
277     }
278 
279     let mut xinput_major_ver = xinput2::XI_2_Major;
280     let mut xinput_minor_ver = xinput2::XI_2_Minor;
281     if unsafe{xinput2::XIQueryVersion(demo_window.display,
282       &mut xinput_major_ver, &mut xinput_minor_ver)} != xlib::Success as c_int {
283         panic!("XInput2 not available");
284     }
285     println!("XI version available {}.{}", xinput_major_ver, xinput_minor_ver);
286 
287     // init XInput events
288     let mut mask: [c_uchar; 1] = [0];
289     let mut input_event_mask = xinput2::XIEventMask {
290         deviceid: xinput2::XIAllMasterDevices,
291         mask_len: mask.len() as i32,
292         mask: mask.as_mut_ptr()
293     };
294     let events = &[
295         xinput2::XI_ButtonPress,
296         xinput2::XI_ButtonRelease,
297         xinput2::XI_KeyPress,
298         xinput2::XI_KeyRelease,
299         xinput2::XI_Motion
300     ];
301     for &event in events {
302         xinput2::XISetMask(&mut mask, event);
303     }
304 
305     match unsafe{xinput2::XISelectEvents(demo_window.display,
306       demo_window.window, &mut input_event_mask, 1)} {
307         status if status as u8 == xlib::Success => (),
308         err => panic!("Failed to select events {:?}", err)
309     }
310 
311     // Show window
312     demo_window.show();
313 
314     // Main loop
315     let display = demo_window.display;
316     let axis_list = read_input_axis_info(display);
317 
318     let mut prev_state = InputState{
319         cursor_pos: (0.0, 0.0),
320         axis_values: Vec::new()
321     };
322 
323     demo_window.run_event_loop(|event| {
324         match event.get_type() {
325             xlib::GenericEvent => {
326                 let mut cookie: xlib::XGenericEventCookie = From::from(*event);
327                 if unsafe{xlib::XGetEventData(display, &mut cookie)} != xlib::True {
328                     println!("Failed to retrieve event data");
329                     return;
330                 }
331                 match cookie.evtype {
332                     xinput2::XI_KeyPress | xinput2::XI_KeyRelease => {
333                         let event_data: &xinput2::XIDeviceEvent = unsafe{transmute(cookie.data)};
334                         if cookie.evtype == xinput2::XI_KeyPress {
335                             if event_data.flags & xinput2::XIKeyRepeat == 0 {
336                                 println!("Key {} pressed", event_data.detail);
337                             }
338                         } else {
339                             println!("Key {} released", event_data.detail);
340                         }
341                     },
342                     xinput2::XI_ButtonPress | xinput2::XI_ButtonRelease => {
343                         let event_data: &xinput2::XIDeviceEvent = unsafe{transmute(cookie.data)};
344                         if cookie.evtype == xinput2::XI_ButtonPress {
345                             println!("Button {} pressed", event_data.detail);
346                         } else {
347                             println!("Button {} released", event_data.detail);
348                         }
349                     },
350                     xinput2::XI_Motion => {
351                         let event_data: &xinput2::XIDeviceEvent = unsafe{transmute(cookie.data)};
352                         let axis_state = event_data.valuators;
353                         let mask = unsafe{ from_raw_parts(axis_state.mask, axis_state.mask_len as usize) };
354                         let mut axis_count = 0;
355 
356                         let mut scroll_delta = (0.0, 0.0);
357                         for axis_id in 0..axis_state.mask_len {
358                             if xinput2::XIMaskIsSet(&mask, axis_id) {
359                                 let axis_value = unsafe{*axis_state.values.offset(axis_count)};
360                                 let delta = calc_scroll_deltas(event_data, axis_id, axis_value, &axis_list, &mut prev_state.axis_values);
361                                 scroll_delta.0 += delta.0;
362                                 scroll_delta.1 += delta.1;
363                                 axis_count += 1;
364                             }
365                         }
366 
367                         if scroll_delta.0.abs() > 0.0 || scroll_delta.1.abs() > 0.0 {
368                             println!("Mouse wheel/trackpad scrolled by ({}, {})", scroll_delta.0, scroll_delta.1);
369                         }
370 
371                         let new_cursor_pos = (event_data.event_x, event_data.event_y);
372                         if new_cursor_pos != prev_state.cursor_pos {
373                             println!("Mouse moved to ({}, {})", new_cursor_pos.0, new_cursor_pos.1);
374                             prev_state.cursor_pos = new_cursor_pos;
375                         }
376                     },
377                     _ => ()
378                 }
379                 unsafe{xlib::XFreeEventData(display, &mut cookie)};
380             },
381             _ => ()
382         }
383     });
384 }
385