HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustMinor

Detecting two keys pressed in quick succession

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
pressedkeystwoquicksuccessiondetecting

Problem

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(&self) -> 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(file: &mut File) -> T {
let mut buf = vec![0u8; mem::size_of::()];
match file.read(&mut buf) {
Ok(sz) => assert!(sz == buf.len()),
Err(err) => panic!("Unable to read from device: {}", err),
}
let ptr = &buf as &[u8] as const [u8] as const T;
unsafe { ptr::read(ptr) }
}

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

fn process_key_event(event: &LinuxInputEvent,
last_occurence_per_key: &mut HashMap,
threshold: u64) {
let key = event.code;
let micros = event.time.to_microseconds();
match last_occurence_per_key.get(&key) {
None => (),
Some(last_micros) => {
let delta = micros - last_micros;
if delta ",
program_name).unwrap();
process::exit(1);
}

fn main() {
let args: Vec = env::arg

Solution

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(&mut self, buf: &mut [u8]) -> io::Result {
let mut total_read = 0;
while total_read return Err(io::Error::new(io::ErrorKind::Other, "Unable to read")),
// Read some amount
Ok(n) => total_read += n,
// Might have been interrupted by something
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {},
Err(e) => 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(&mut self) -> io::Result {
// avoid allocating on the heap
let mut val = mem::uninitialized();
let ptr = &mut val as mut T as mut u8;
let mut buf = slice::from_raw_parts_mut(ptr, mem::size_of::());

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

impl ReadExtensions for R where R: io::Read {}

const EV_KEY: u16 = 0x01;

fn to_microseconds(tv: libc::timeval) -> 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: &str) -> io::Result {
let f = try!(File::open(path));
Ok(InputSource { file: f })
}
}

impl Iterator for InputSource {
type Item = LinuxInputEvent;

fn next(&mut self) -> Option {
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,
threshold: u64,
}

impl KeyEventSink {
fn new(threshold: u64) -> 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(&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(&key) {
let delta = micros - last_micros;
if delta ", program_name).unwrap();
process::exit(1);
}

fn main() {
let args: Ve

Code Snippets

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(&mut self, buf: &mut [u8]) -> io::Result<()> {
        let mut total_read = 0;
        while total_read < buf.len() {
            match self.read(&mut buf[total_read..]) {
                // Unable to read anything
                Ok(0) => return Err(io::Error::new(io::ErrorKind::Other, "Unable to read")),
                // Read some amount
                Ok(n) => total_read += n,
                // Might have been interrupted by something
                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {},
                Err(e) => 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<T>(&mut self) -> io::Result<T> {
        // avoid allocating on the heap
        let mut val = mem::uninitialized();
        let ptr = &mut val as *mut T as *mut u8;
        let mut buf = slice::from_raw_parts_mut(ptr, mem::size_of::<T>());

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

impl<R> ReadExtensions for R where R: io::Read {}



const EV_KEY: u16 = 0x01;

fn to_microseconds(tv: libc::timeval) -> 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: &str) -> io::Result<InputSource> {
        let f = try!(File::open(path));
        Ok(InputSource { file: f })
    }
}

impl Iterator for InputSource {
    type Item = LinuxInputEvent;

    fn next(&mut self) -> Option<LinuxInputEvent> {
        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<u16, u64>,
    threshold: u64,
}

impl KeyEventSink {
    fn new(threshold: u64) -> 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<F>(&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
    

Context

StackExchange Code Review Q#96645, answer score: 6

Revisions (0)

No revisions yet.