Detecting two keys pressed in quick succession My computer has been having this issue where when I press a key, two key presses get registered. The goal of this program is to detect the time delta between the two keys so that I can set an effective debounce threshold. I also thought that it would be a good idea to try out Rust, so here it is:

use std::{mem, ptr, env, process, io};
use std::io::Read;
use std::io::Write;
use std::fs::File;
use std::collections::HashMap;

const EV_SYN: u16 = 0x00;
const EV_KEY: u16 = 0x01;

#[repr(C)]
struct LinuxTimeval {
    tv_sec: isize,
    tv_usec: isize,
}

impl LinuxTimeval {
    fn to_microseconds(&amp;self) -&gt; u64 {
        self.tv_sec as u64 * 1000000u64 + self.tv_usec as u64
    }
}

#[repr(C)]
struct LinuxInputEvent {
    time: LinuxTimeval,
    type_: u16,
    code: u16,
    value: i32,
}

fn read_struct_from_file&lt;T: Sized&gt;(file: &amp;mut File) -&gt; T {
    let mut buf = vec![0u8; mem::size_of::&lt;T&gt;()];
    match file.read(&amp;mut buf) {
        Ok(sz) =&gt; assert!(sz == buf.len()),
        Err(err) =&gt; panic!("Unable to read from device: {}", err),
    }
    let ptr = &amp;buf as &amp;[u8] as *const [u8] as *const T;
    unsafe { ptr::read(ptr) }
}

fn read_events_from_file(file: &amp;mut File) -&gt; Vec&lt;LinuxInputEvent&gt; {
    let mut vec = Vec::new();
    loop {
        let event: LinuxInputEvent = read_struct_from_file(file);
        if event.type_ == EV_SYN {
            if vec.len() &gt; 0 {
                break;
            } else {
                continue;
            }
        } else {
            vec.push(event);
        }
    }
    vec
}

fn process_key_event(event: &amp;LinuxInputEvent,
                     last_occurence_per_key: &amp;mut HashMap&lt;u16, u64&gt;,
                     threshold: u64) {
    let key = event.code;
    let micros = event.time.to_microseconds();
    match last_occurence_per_key.get(&amp;key) {
        None =&gt; (),
        Some(last_micros) =&gt; {
            let delta = micros - last_micros;
            if delta &lt; threshold {
                println!("Key code {:3}: event delta: {} us",
                         key, delta);
            }
        }
    }
    last_occurence_per_key.insert(key, micros);
}

fn print_usage_and_exit(program_name: &amp;str) {
    writeln!(&amp;mut io::stderr(), "Usage: {} &lt;microseconds-delta&gt;",
             program_name).unwrap();
    process::exit(1);
}

fn main() {
    let args: Vec&lt;_&gt; = env::args().collect();
    if args.len() != 2 { print_usage_and_exit(&amp;args[0]); }
    let threshold: u64 = args[1].parse().unwrap_or(0);
    if threshold == 0 { print_usage_and_exit(&amp;args[0]); }
    let mut file;
    match File::open("/dev/input/by-path/platform-i8042-serio-0-event-kbd") {
        Ok(f) =&gt; { file = f },
        Err(err) =&gt; panic!("Unable to open file: {}", err),
    }
    let mut last_occurence_per_key = HashMap::new();
    loop {
        match read_events_from_file(&amp;mut file)
                .iter().find(|&amp;event| event.type_ == EV_KEY &amp;&amp; event.value == 1) {
            None =&gt; continue,
            Some(event) =&gt; process_key_event(event,
                                             &amp;mut last_occurence_per_key,
                                             threshold),
        }
    }
}


Please let me know if any improvements can be made.
Highlights


Using the libc crate for C data types
No heap allocation of read struct
No (new) panics — better error handling
Event iterator instead of vectors
New types and traits to collect related functionality


Beware - I still haven't gotten this to actually read any events on my machine, but it does compile. That should be good enough, right? ^_^

libc crate

The libc crate should always be used when interoperating with C code. This crate is guaranteed to have the correct sizes for platform-specific types.

Additionally, it already has a definition for timeval, so that was nice.

Less heap allocation

I used mem::uninitialized to create an on-stack version of the struct with no values. I then got a reference to that area as bytes and read directly to that.

Note that I marked that function as unsafe - depending on what T resolves to, it could produce very bad results.

Error handling

Everywhere that had a panic! or assert! was changed into a Result. This allows the caller to decide how to deal with the error. The most awkward place left is in the call to read_c_struct, as it just stops the iterator on failure. Instead, it could return the Result directly and allow its caller to see the failure.

Embrace the Iterator

Rust loves iterators. This seemed like a perfect case for it - you have a stream of events and you want to do something based on them. I reworked your method into an Iterator implementation. This allows the caller to decide how much memory to allocate; in the final code, I believe it's very small. It also allows using the filter method and other iterator adaptors.

New types and traits

Iterators need a type to exist, and I also felt that your hashmap and threshold needed to live together. I also moved the read-specific methods into a new trait that is then implemented for all types that implement Read.

Complete code

There's a few comments scattered about for smaller things, but here it is:

extern crate libc;

use std::{mem, env, process, io, slice};
use std::io::{Read, Write};
use std::fs::File;
use std::collections::HashMap;

// Some helper methods for reading things
trait ReadExtensions : Read {
    /// Completely fill the supplied buffer
    fn read_complete(&amp;mut self, buf: &amp;mut [u8]) -&gt; io::Result&lt;()&gt; {
        let mut total_read = 0;
        while total_read &lt; buf.len() {
            match self.read(&amp;mut buf[total_read..]) {
                // Unable to read anything
                Ok(0) =&gt; return Err(io::Error::new(io::ErrorKind::Other, "Unable to read")),
                // Read some amount
                Ok(n) =&gt; total_read += n,
                // Might have been interrupted by something
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted =&gt; {},
                Err(e) =&gt; return Err(From::from(e))
            }
        }
        Ok(())
    }

    /// Directly read a C struct.
    ///
    /// Unsafe because only the concrete type of `T` can determine if
    /// the result is safe. Should only be used to read structs
    /// composed of primitive types without any pointers.
    unsafe fn read_c_struct&lt;T&gt;(&amp;mut self) -&gt; io::Result&lt;T&gt; {
        // avoid allocating on the heap
        let mut val = mem::uninitialized();
        let ptr = &amp;mut val as *mut T as *mut u8;
        let mut buf = slice::from_raw_parts_mut(ptr, mem::size_of::&lt;T&gt;());

        try!(self.read_complete(buf));
        Ok(val)
    }
}

impl&lt;R&gt; ReadExtensions for R where R: io::Read {}



const EV_KEY: u16 = 0x01;

fn to_microseconds(tv: libc::timeval) -&gt; u64 {
    // u64 suffix unneeded, adding _ for readability
    tv.tv_sec as u64 * 1_000_000 + tv.tv_usec as u64
}

// Using libc
#[derive(Copy,Clone)]
#[repr(C)]
struct LinuxInputEvent {
    time: libc::timeval,
    type_: libc::uint16_t,
    code: libc::uint16_t,
    value: libc::int32_t,
}

/// An iterator of Linux events
struct InputSource {
    file: File,
}

impl InputSource {
    fn new(path: &amp;str) -&gt; io::Result&lt;InputSource&gt; {
        let f = try!(File::open(path));
        Ok(InputSource { file: f })
    }
}

impl Iterator for InputSource {
    type Item = LinuxInputEvent;

    fn next(&amp;mut self) -&gt; Option&lt;LinuxInputEvent&gt; {
        unsafe { self.file.read_c_struct() }.ok()
    }
}

/// Tracks when multiple presses of the same key occurs and calls the
/// callback when duplicate events are triggered within a threshold.
struct KeyEventSink {
    last_occurences: HashMap&lt;u16, u64&gt;,
    threshold: u64,
}

impl KeyEventSink {
    fn new(threshold: u64) -&gt; KeyEventSink {
        KeyEventSink {
            last_occurences: HashMap::new(),
            threshold: threshold,
        }
    }

    // Take a closure so we don't have to worry about user interface here
    fn process_event&lt;F&gt;(&amp;mut self, event: LinuxInputEvent, f: F)
        where F: FnOnce(u16, u64)
    {
        let key = event.code;
        let micros = to_microseconds(event.time);

        // if-let for one useful match arm
        if let Some(last_micros) = self.last_occurences.get(&amp;key) {
            let delta = micros - last_micros;
            if delta &lt; self.threshold {
                f(key, delta)
            }
        }
        self.last_occurences.insert(key, micros);
    }
}

fn print_usage_and_exit(program_name: &amp;str) {
    writeln!(&amp;mut io::stderr(), "Usage: {} &lt;microseconds-delta&gt;", program_name).unwrap();
    process::exit(1);
}

fn main() {
    let args: Vec&lt;_&gt; = env::args().collect();
    if args.len() != 2 { print_usage_and_exit(&amp;args[0]); }
    let threshold: u64 = args[1].parse().unwrap_or(0);
    if threshold == 0 { print_usage_and_exit(&amp;args[0]); }

    // just use value of match
    let input = match InputSource::new("/dev/input/event2") {
        Ok(f) =&gt; f,
        Err(err) =&gt; panic!("Unable to open file: {}", err),
    };

    let mut sink = KeyEventSink::new(threshold);

    let key_events = input.filter(|&amp;event| event.type_ == EV_KEY &amp;&amp; event.value == 1);
    for key_event in key_events {
        sink.process_event(key_event, |key, delta| {
            println!("Key code {:3}: event delta: {} μs", key, delta);
        })
    }
}


Followups


  is there any particular reason you added the Copy and Clone traits to LinuxInputEvent? 


Basically, LinuxInputEvent is a simple type comprised of Copyable values, and it's also fairly small in the grand scheme of things. Making it Copy simply improves the ergonomics of using it. If you implement Copy, you need to implement Clone.


  Seeing as the function is used at most once, FnOnce would also be better than Fn


Absolutely correct - I updated to use this.

I also updated the read_complete method a bit based on feedback from this Stack Overflow question.