patternrustMinor
Detecting two keys pressed in quick succession
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
```
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
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
Less heap allocation
I used
Note that I marked that function as unsafe - depending on what
Error handling
Everywhere that had a
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
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
Complete code
There's a few comments scattered about for smaller things, but here it is:
``
/// 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
- 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.