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