Skip to content

Commit

Permalink
Better error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
iamkroot committed Dec 6, 2021
1 parent 4e1612a commit 9f6f32d
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 50 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ libc = "0.2.107"
log = { version = "0.4.14", features = ["release_max_level_warn"] }
env_logger = "0.9.0"
clap = "2.34.0"
anyhow = "1.0"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ The following features are planned and implemented for the app:
* [x] Systemd service to enable autostart
* [x] Run without `sudo`
* [x] Start Calc only on drag instead of tap
* [x] Don't panic on errors - exit gracefully
* [ ] Integration with system's NumLock state (toggle with external keyboards)
* [ ] Don't panic on errors - exit gracefully
* [ ] `strip` release binaries (goes from ~5MB to ~1.5MB)
* [ ] Autodetect laptop model
* [ ] Disable numpad if idle for more than a minute
Expand Down
36 changes: 20 additions & 16 deletions src/devices.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use anyhow::{anyhow, Context, Result};
use evdev_rs::{
enums::{EventCode, EV_ABS},
Device, DeviceWrapper,
Expand All @@ -6,23 +7,25 @@ use std::{fs::OpenOptions, os::unix::prelude::OpenOptionsExt};

use crate::numpad_layout::BBox;

fn parse_id(line: &str, search_str: &str) -> Result<u32, String> {
let pos = line.find(search_str).ok_or("Can't find token")?;
fn parse_id(line: &str, search_str: &str) -> Result<u32> {
let pos = line
.find(search_str)
.ok_or_else(|| anyhow!("Can't find token {} in {}", search_str, line))?;
let start_idx = pos + search_str.len();
let mut chars = line.chars();
// we know chars is at least as long as start_idx
unsafe { chars.advance_by(start_idx).unwrap_unchecked() };
let end_idx = start_idx
+ chars
.position(|c| !c.is_numeric())
.expect("Reached end of line");
.ok_or(anyhow!("Reached end of line"))?;
let digits = line[start_idx..end_idx].parse();
digits.map_err(|_| "No ID".to_string())
digits.context("Could not parse u32 ID")
}

/// Parse `/proc/bus/input/devices` to find the keyboard and touchpad devices.
/// Returns the evdev handles for keybard and touchpad, along with I2C ID of touchpad.
pub(crate) fn read_proc_input() -> Result<(u32, u32, u32), String> {
pub(crate) fn read_proc_input() -> Result<(u32, u32, u32)> {
#[derive(Debug, PartialEq, Eq)]
enum Detection {
NotDetected,
Expand All @@ -36,7 +39,8 @@ pub(crate) fn read_proc_input() -> Result<(u32, u32, u32), String> {
let mut touchpad_ev_id: Option<u32> = None;
let mut keyboard_ev_id: Option<u32> = None;

let data = std::fs::read_to_string("/proc/bus/input/devices").map_err(|e| e.to_string())?;
let data = std::fs::read_to_string("/proc/bus/input/devices")
.context("Could not read devices file")?;

for line in data.lines() {
match touchpad_detection {
Expand Down Expand Up @@ -92,33 +96,33 @@ pub(crate) fn read_proc_input() -> Result<(u32, u32, u32), String> {
}
}
Ok((
keyboard_ev_id.ok_or("Can't find keyboard")?,
touchpad_ev_id.ok_or("Can't find touchpad")?,
touchpad_i2c_id.ok_or("Can't find touchpad I2C ID")?,
keyboard_ev_id.ok_or(anyhow!("Can't find keyboard evdev"))?,
touchpad_ev_id.ok_or(anyhow!("Can't find touchpad evdev"))?,
touchpad_i2c_id.ok_or(anyhow!("Can't find touchpad I2C ID"))?,
))
}

pub(crate) fn open_input_evdev(evdev_id: u32) -> Device {
pub(crate) fn open_input_evdev(evdev_id: u32) -> Result<Device> {
let file = OpenOptions::new()
.read(true)
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open(format!("/dev/input/event{}", evdev_id))
.expect("Couldn't open device event handle");
Device::new_from_file(file).expect("Unable to open evdev device")
Device::new_from_file(file).context("Unable to open evdev device")
}

pub(crate) fn get_touchpad_bbox(touchpad_evdev: &Device) -> BBox {
pub(crate) fn get_touchpad_bbox(touchpad_evdev: &Device) -> Result<BBox> {
let absx = touchpad_evdev
.abs_info(&EventCode::EV_ABS(EV_ABS::ABS_X))
.expect("MAX");
.ok_or(anyhow!("Could not get touchpad max x"))?;
let absy = touchpad_evdev
.abs_info(&EventCode::EV_ABS(EV_ABS::ABS_Y))
.expect("MAX");
BBox::new(
.ok_or(anyhow!("Could not get touchpad max y"))?;
Ok(BBox::new(
absx.minimum as f32,
absx.maximum as f32,
absy.minimum as f32,
absy.maximum as f32,
)
))
}
29 changes: 22 additions & 7 deletions src/dummy_keyboard.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::io::ErrorKind::{NotFound, PermissionDenied};

use anyhow::{Context, Error, Result};
use evdev_rs::{
enums::{EventCode, EV_KEY, EV_SYN},
DeviceWrapper, InputEvent, TimeVal, UInputDevice, UninitDevice,
Expand All @@ -18,23 +21,35 @@ impl std::fmt::Debug for DummyKeyboard {
}

impl DummyKeyboard {
pub(crate) fn new(layout: &NumpadLayout) -> Self {
let dev = UninitDevice::new().expect("No libevdev");
pub(crate) fn new(layout: &NumpadLayout) -> Result<Self> {
let dev = UninitDevice::new().context("Unable to create uninit evdev device.")?;
dev.set_name("asus_touchpad");
let default_keys = [EV_KEY::KEY_LEFTSHIFT, EV_KEY::KEY_NUMLOCK, EV_KEY::KEY_CALC];
for key in default_keys {
dev.enable(&EventCode::EV_KEY(key))
.expect("Unable to enable key");
.with_context(|| format!("Unable to enable key {:?}", key))?;
}
for row in layout.keys().iter() {
for key in row {
dev.enable(&EventCode::EV_KEY(*key))
.expect("Unable to enable key");
.with_context(|| format!("Unable to enable key {:?}", key))?;
}
}
Self {
udev: UInputDevice::create_from_device(&dev).expect("Unable to create UInput"),
}
let udev = UInputDevice::create_from_device(&dev).map_err(|err| {
let mut context = "Unable to create dummy UInput device".to_string();
let extra_context = match err.kind() {
NotFound => ("Is uinput kernel module loaded?"),
PermissionDenied => ("Do you have the permission to read /dev/uinput?"),
_ => "",
};
if !extra_context.is_empty() {
context.push_str(". ");
context.push_str(extra_context);
}
Error::new(err).context(context)
})?;

Ok(Self { udev })
}
}

Expand Down
38 changes: 21 additions & 17 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::dummy_keyboard::{DummyKeyboard, KeyEvents};
use crate::numpad_layout::{NumpadLayout, LAYOUT_NAMES};
use crate::touchpad_i2c::{Brightness, TouchpadI2C};
use crate::util::ElapsedSince;
use anyhow::{Context, Result};
use clap::{App, Arg};
use evdev_rs::{
enums::{EventCode, EV_ABS, EV_KEY, EV_MSC},
Expand Down Expand Up @@ -183,15 +184,16 @@ impl Numpad {
}
}

fn toggle_numlock(&mut self) {
fn toggle_numlock(&mut self) -> Result<()> {
if self.state.toggle_numlock() {
self.touchpad_i2c.set_brightness(self.state.brightness);
self.touchpad_i2c.set_brightness(self.state.brightness)?;
// don't grab touchpad - allow moving pointer even if active
} else {
self.touchpad_i2c.set_brightness(Brightness::Zero);
self.touchpad_i2c.set_brightness(Brightness::Zero)?;
// we might still be grabbing the touchpad. release it.
self.ungrab();
}
Ok(())
}

fn grab(&mut self) {
Expand Down Expand Up @@ -236,7 +238,7 @@ impl Numpad {
self.state.finger_state = FingerState::Lifted;
}

fn handle_touchpad_event(&mut self, ev: InputEvent) {
fn handle_touchpad_event(&mut self, ev: InputEvent) -> Result<()> {
// TODO: Double-taps when numpad is active should not be propagated.
// Need to grab/ungrab the device intelligently.
// TODO: Dragging after a hold does not cause pointer to move, even
Expand Down Expand Up @@ -305,7 +307,7 @@ impl Numpad {
if self.layout.in_numlock_bbox(self.state.pos) {
if ev.time.elapsed_since(self.state.tap_started_at) >= Self::HOLD_DURATION {
debug!("Hold finish - toggle numlock");
self.toggle_numlock();
self.toggle_numlock()?;
// If user doesn't lift the finger quickly, we don't want to keep
// toggling, so assume finger was moved.
// Can't do finger_state = Lifted, since that would start another tap
Expand All @@ -321,7 +323,7 @@ impl Numpad {
{
debug!("Hold finish - cycle brightness");
self.touchpad_i2c
.set_brightness(self.state.brightness.cycle());
.set_brightness(self.state.brightness.cycle())?;
self.state.cur_key.reset();
}
}
Expand All @@ -337,7 +339,7 @@ impl Numpad {
self.state.finger_dragged_too_much = true;
self.ungrab();
self.on_lift();
return;
return Ok(());
}

if self.state.numlock && self.state.finger_state == FingerState::TouchStart {
Expand All @@ -351,9 +353,10 @@ impl Numpad {
}
}
}
Ok(())
}

fn process(&mut self) {
fn process(&mut self) -> Result<()> {
let tp_fd = libc::pollfd {
fd: self.evdev.file().as_raw_fd(),
events: libc::POLLIN,
Expand All @@ -374,7 +377,7 @@ impl Numpad {
if fds[0].revents & libc::POLLIN != 0 {
// read until no more events
while let Ok((_, ev)) = self.evdev.next_event(ReadFlag::NORMAL) {
self.handle_touchpad_event(ev);
self.handle_touchpad_event(ev)?;
}
}
if fds[1].revents & libc::POLLIN != 0 {
Expand All @@ -391,7 +394,7 @@ impl Numpad {
}
}

fn main() {
fn main() -> Result<()> {
env_logger::init();
let matches = App::new("asus-numpad")
.arg(
Expand All @@ -406,19 +409,20 @@ fn main() {
let layout_name = matches.value_of("layout").expect("Expected layout");

let (keyboard_ev_id, touchpad_ev_id, i2c_id) =
read_proc_input().expect("Couldn't get proc input devices");
let touchpad_dev = open_input_evdev(touchpad_ev_id);
let keyboard_dev = open_input_evdev(keyboard_ev_id);
let bbox = get_touchpad_bbox(&touchpad_dev);
read_proc_input().context("Couldn't get proc input devices")?;
let touchpad_dev = open_input_evdev(touchpad_ev_id)?;
let keyboard_dev = open_input_evdev(keyboard_ev_id)?;
let bbox = get_touchpad_bbox(&touchpad_dev)?;
let layout = match layout_name {
"ux433fa" => NumpadLayout::ux433fa(bbox),
"m433ia" => NumpadLayout::m433ia(bbox),
"ux581" => NumpadLayout::ux581(bbox),
"gx701" => NumpadLayout::gx701(bbox),
_ => unreachable!(),
};
let kb = DummyKeyboard::new(&layout);
let touchpad_i2c = TouchpadI2C::new(i2c_id);
let kb = DummyKeyboard::new(&layout)?;
let touchpad_i2c = TouchpadI2C::new(i2c_id)?;
let mut numpad = Numpad::new(touchpad_dev, keyboard_dev, touchpad_i2c, kb, layout);
numpad.process();
numpad.process()?;
Ok(())
}
37 changes: 29 additions & 8 deletions src/touchpad_i2c.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::fmt::Debug;
use std::io::ErrorKind::{NotFound, PermissionDenied};

use anyhow::{Context, Error, Result};
use i2cdev::core::I2CDevice;
use i2cdev::linux::LinuxI2CDevice;
use i2cdev::linux::{LinuxI2CDevice, LinuxI2CError};

#[derive(Debug, Clone, Copy)]
pub enum Brightness {
Expand Down Expand Up @@ -53,15 +55,32 @@ pub struct TouchpadI2C {
}

impl TouchpadI2C {
pub fn new(i2c_id: u32) -> Self {
pub fn new(i2c_id: u32) -> Result<Self> {
const TOUCHPAD_ADDR: u16 = 0x15;
let dev =
unsafe { LinuxI2CDevice::force_new(format!("/dev/i2c-{}", i2c_id), TOUCHPAD_ADDR) };
let dev = dev.expect("Failed to open Touchpad I2C. Is i2c-dev kernel module loaded?");
Self { dev, i2c_id }
let dev = unsafe {
LinuxI2CDevice::force_new(format!("/dev/i2c-{}", i2c_id), TOUCHPAD_ADDR).map_err(
|err| {
let mut context = format!("Unable to open Touchpad I2C at /dev/i2c-{}", i2c_id);
let extra_context = match &err {
LinuxI2CError::Io(e) => match e.kind() {
NotFound => "Is i2c-dev kernel module loaded?",
PermissionDenied => "Do you have the permission to read /dev/i2c-*?",
_ => "",
},
LinuxI2CError::Nix(_) => "",
};
if !extra_context.is_empty() {
context.push_str(". ");
context.push_str(extra_context);
};
Error::new(err).context(context)
},
)?
};
Ok(Self { dev, i2c_id })
}

pub fn set_brightness(&mut self, brightness: Brightness) {
pub fn set_brightness(&mut self, brightness: Brightness) -> Result<()> {
let msg = [
0x05,
0x00,
Expand All @@ -77,7 +96,9 @@ impl TouchpadI2C {
brightness as u8,
0xad,
];
self.dev.write(&msg).expect("could not set brightness");
self.dev
.write(&msg)
.with_context(|| format!("Could not set touchpad brightness to {}", brightness))
}
}

Expand Down
1 change: 0 additions & 1 deletion src/util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use evdev_rs::TimeVal;


pub trait ElapsedSince {
/// Calculate time elapsed since `other`.
///
Expand Down

0 comments on commit 9f6f32d

Please sign in to comment.