input/
input.rs

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