Skip to content

Commit

Permalink
Workaround for bug eruption-project#226
Browse files Browse the repository at this point in the history
- `udev_inhibited_workaround()` functions were added to eruption and eruption-debug-tool.
- In order to use the workaround, a new rule is added to 99-eruption.rules to make the `inhibited` file of the Vulcan 1xx's LED interface device world-writeable.
- An interactive mode was added to eruption-hwutil and eruption-debug-tool to help me debug this problem.  I kept it in and polished it up in case it would be helpful in other contexts.
- All the hwdevices files shared between eruption-hwutil and eruption-debug-tool were updated to respect the interactive mode before device initialization and between run-tests color changes.
- eruption-debug-tool has a new subcommand to send the inhibited workaround independently of the run-tests subcommand.
- gen-completions.sh was updated and run to add the new flag and subcommand.
- Additionally, wherever the vendor ID and product ID from the device is output, use "04x" formatting instead of "x", since leading zeros can be present in IDs unrelated to Roccat.
  • Loading branch information
Phen-Ro committed Aug 6, 2023
1 parent f911f66 commit 276d371
Show file tree
Hide file tree
Showing 55 changed files with 564 additions and 105 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions eruption-debug-tool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ flume = "0.10.14"
clap = { version = "4.2.3", features = ["derive"] }
clap_complete = "4.2.0"
colored = "2.0.0"
console = "0.15.5"
ctrlc = { version = "3.2.5", features = ["termination"] }
libc = "0.2.141"
nix = "0.26.2"
Expand All @@ -68,6 +69,7 @@ eyre = "0.6.8"
color-eyre = "0.6.2"
hexdump = "0.1.1"
hidapi = { git = "https://github.com/X3n0m0rph59/hidapi-rs.git", rev = "a842fd6", default-features = false, features = ["linux-native"] }
udev = "0.7"
bitvec = "1.0.1"
byteorder = "1.4.3"
serialport = "4.2.0"
Expand Down
1 change: 1 addition & 0 deletions eruption-debug-tool/src/interact.rs
92 changes: 81 additions & 11 deletions eruption-debug-tool/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use tracing::*;

mod constants;
mod hwdevices;
mod interact;
mod util;

use util::{DeviceState, HexSlice};
Expand Down Expand Up @@ -78,6 +79,12 @@ macro_rules! println_v {
println!()
};

($verbosity : expr) => {
if $crate::OPTIONS.lock().as_ref().unwrap().verbose >= $verbosity as u8 {
println!()
}
};

($verbosity : expr, $l : literal $(,$params : tt) *) => {
if $crate::OPTIONS.lock().as_ref().unwrap().verbose >= $verbosity as u8 {
println!($l, $($params),*)
Expand All @@ -86,7 +93,7 @@ macro_rules! println_v {

($verbosity : expr, $($params : tt) *) => {
if $crate::OPTIONS.lock().as_ref().unwrap().verbose >= $verbosity as u8 {
println!($($params),*)
println!($($params)*)
}
};
}
Expand All @@ -97,6 +104,12 @@ macro_rules! eprintln_v {
eprintln!()
};

($verbosity : expr) => {
if $crate::OPTIONS.lock().as_ref().unwrap().verbose >= $verbosity as u8 {
eprintln!()
}
};

($verbosity : expr, $l : literal $(,$params : tt) *) => {
if $crate::OPTIONS.lock().as_ref().unwrap().verbose >= $verbosity as u8 {
eprintln!($l, $($params),*)
Expand All @@ -105,7 +118,7 @@ macro_rules! eprintln_v {

($verbosity : expr, $($params : tt) *) => {
if $crate::OPTIONS.lock().as_ref().unwrap().verbose >= $verbosity as u8 {
eprintln!($($params),*)
eprintln!($($params)*)
}
};
}
Expand Down Expand Up @@ -136,6 +149,10 @@ pub struct Options {
#[clap(short, long, action = clap::ArgAction::Count)]
verbose: u8,

/// Send the hardware commands only after a keypress, where applicable
#[arg(short, long)]
interactive: bool,

#[clap(subcommand)]
command: Subcommands,
}
Expand Down Expand Up @@ -214,6 +231,12 @@ pub enum Subcommands {
device: usize,
},

/// Toggle the "inhibited" attribute of the udev device if it's sending bad inputs events.
ToggleUdevInhibited {
/// The index of the device, can be found with the list sub-command
device: usize,
},

/// Special utility functions, like searching for CRC polynoms and parameters
Utils {
#[clap(subcommand)]
Expand Down Expand Up @@ -311,6 +334,8 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
let opts = Options::parse();
*OPTIONS.lock() = Some(opts.clone());

interact::INTERACTIVE.store(opts.interactive, Ordering::SeqCst);

match opts.command {
Subcommands::List => {
println!();
Expand All @@ -322,7 +347,7 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
Ok(hidapi) => {
for (index, device) in hidapi.device_list().enumerate() {
println!(
"Index: {}: ID: {:x}:{:x} {}/{} Subdev: {}",
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
Expand Down Expand Up @@ -362,7 +387,7 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
Ok(hidapi) => {
if let Some((index, device)) = hidapi.device_list().enumerate().nth(device) {
println!(
"Index: {}: ID: {:x}:{:x} {}/{} Subdev: {}",
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
Expand Down Expand Up @@ -405,7 +430,7 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
Ok(hidapi) => {
if let Some((index, device)) = hidapi.device_list().enumerate().nth(device) {
println!(
"Index: {}: ID: {:x}:{:x} {}/{} Subdev: {}",
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
Expand Down Expand Up @@ -465,7 +490,7 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
hidapi.device_list().enumerate().nth(device_index)
{
println!(
"Index: {}: ID: {:x}:{:x} {}/{} Subdev: {}",
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
Expand Down Expand Up @@ -551,7 +576,7 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
hidapi.device_list().enumerate().nth(device_index)
{
println!(
"Index: {}: ID: {:x}:{:x} {}/{} Subdev: {}",
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
Expand Down Expand Up @@ -594,7 +619,7 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
hidapi.device_list().enumerate().nth(device_index)
{
println!(
"Index: {}: ID: {:x}:{:x} {}/{} Subdev: {}",
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
Expand Down Expand Up @@ -635,7 +660,7 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
hidapi.device_list().enumerate().nth(device_index)
{
println!(
"Index: {}: ID: {:x}:{:x} {}/{} Subdev: {}",
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
Expand Down Expand Up @@ -678,7 +703,7 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
hidapi.device_list().enumerate().nth(device_index)
{
println!(
"Index: {}: ID: {:x}:{:x} {}/{} Subdev: {}",
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
Expand Down Expand Up @@ -718,7 +743,7 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
hidapi.device_list().enumerate().nth(device_index)
{
println!(
"Index: {}: ID: {:x}:{:x} {}/{} Subdev: {}",
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
Expand All @@ -736,6 +761,8 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
)?;

hwdev.send_init_sequence()?;

interact::prompt("Press any key to send the test pattern.");
hwdev.send_test_pattern()?;
} else {
error!("Could not open the device, is the device in use?");
Expand Down Expand Up @@ -785,6 +812,8 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
}
}

interact::prompt("Press any key to send the test pattern.");

device.send_led_off_sequence()?;
}

Expand All @@ -795,6 +824,47 @@ pub async fn async_main() -> std::result::Result<(), eyre::Error> {
}
}

Subcommands::ToggleUdevInhibited {
device: device_index
} => {
if device_index < 255 - 4 {
// create the one and only hidapi instance
match hidapi::HidApi::new() {
Ok(hidapi) => {
if let Some((index, device)) =
hidapi.device_list().enumerate().nth(device_index)
{
println!(
"Index: {}: ID: {:04x}:{:04x} {}/{} Subdev: {}",
format!("{index:02}").bold(),
device.vendor_id(),
device.product_id(),
device.manufacturer_string().unwrap_or("<unknown>").bold(),
device.product_string().unwrap_or("<unknown>").bold(),
device.interface_number()
);

interact::prompt("Press any key to toggle the \"inhibited\" attribute.");
let workaround_attempt = util::udev_inhibited_workaround(
device.vendor_id(),
device.product_id(),
device.interface_number());
if let Err(err) = workaround_attempt {
error!("Udev \"inhibited\" workaround failed: {:?}", err)
}

}
}

Err(_) => {
error!("Could not open HIDAPI");
}
}
} else {
println!("Udev \"inhibited\" workaround is inapplicable.");
}
}

Subcommands::Utils { command } => match command {
UtilsSubcommands::ReverseCrc8 { data } => {
let mut result = Vec::new();
Expand Down
38 changes: 38 additions & 0 deletions eruption-debug-tool/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
*/

use colored::*;
use eyre::eyre;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::sync::Arc;
use std::{collections::HashMap, fmt, fs};
use std::{num::ParseIntError, path::Path};
Expand Down Expand Up @@ -215,3 +217,39 @@ pub fn find_crc8_from_params(sum: u8, buf: &[u8], p: &[(u8, u8)]) -> Vec<(u8, u8

result
}

/// For some devices, such as the Vulcan 1xx, after sending the report to update the LEDs, the device's evdev LED interface
/// goes crazy and starts spewing out KEY_UNKNOWN events. This is ignored by X and Wayland, but is interpreted as real key
/// stroke inputs on virtual consoles. As best as I can tell, this behavior is a bug somewhere in udev/evdev/hidraw. As a
/// workaround, toggling the "inhibited" attribute back and forth as a privileged user silences these events for as long as
/// the device is plugged in. Not all Roccat devices require this workaround, headphones don't, but I don't know which all
/// do and which don't. Note that this workaround can also be applied manually by writing to the "inhibited" file found at
/// path "/sys/class/input/eventX/inhibited", where the X in "eventX" is the udev number associated with the LED interface.
pub fn udev_inhibited_workaround(vendor_id: u16, product_id: u16, interface_num: i32) -> Result<()> {
let interface_num_str = format!("{interface_num:02}");
let interface_num_osstr = OsStr::new(&interface_num_str);

let mut enumerator = udev::Enumerator::new()?;
enumerator.match_subsystem("input")?;
enumerator.match_property("ID_VENDOR_ID", format!("{vendor_id:04x}"))?;
enumerator.match_property("ID_MODEL_ID", format!("{product_id:04x}"))?;
enumerator.match_property("ID_USB_INTERFACE_NUM", &interface_num_str)?;
enumerator.match_attribute("inhibited", "0")?;

enumerator
.scan_devices()?
.find(|dev| {
// For some reason, the above match_property() still brings back devices with different interface_nums, so filter again.
dev.property_value("ID_USB_INTERFACE_NUM")
.map_or(false, |value| value == interface_num_osstr)
})
.map_or_else(
|| Err(eyre!("Udev device not found.")),
|mut dev|
{
// Toggling the value on and off is enough to quiet spurious events.
dev.set_attribute_value("inhibited", "1")?;
dev.set_attribute_value("inhibited", "0")?;
Ok(())
})
}
1 change: 1 addition & 0 deletions eruption-hwutil/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ color-eyre = "0.6.2"
bitvec = "1.0.1"
byteorder = "1.4.3"
hidapi = { git = "https://github.com/X3n0m0rph59/hidapi-rs.git", rev = "a842fd6", default-features = false, features = ["linux-native"] }
udev = "0.7"
hexdump = "0.1.1"
serialport = "4.2.0"
crc8 = "0.1.1"
Expand Down
5 changes: 4 additions & 1 deletion eruption-hwutil/src/hwdevices/corsair_strafe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use std::{sync::Arc, thread};
use tracing::*;

#[allow(unused)]
use crate::{constants, eprintln_v, println_v};
use crate::{constants, interact, eprintln_v, println_v};

use super::{DeviceTrait, HwDeviceError, RGBA};

Expand Down Expand Up @@ -263,6 +263,7 @@ impl CorsairStrafe {

impl DeviceTrait for CorsairStrafe {
fn send_init_sequence(&self) -> Result<()> {
interact::prompt("Press any key to send initialization sequence.");
println_v!(1, "Sending device init sequence...");

if !self.is_bound {
Expand Down Expand Up @@ -493,6 +494,8 @@ impl DeviceTrait for CorsairStrafe {
}; NUM_KEYS],
)?;

interact::prompt_or_wait("Press any key to change colors.", Duration::from_millis(100));

// test each LED
for i in 0..NUM_KEYS {
let mut led_map = [RGBA {
Expand Down
3 changes: 2 additions & 1 deletion eruption-hwutil/src/hwdevices/custom_serial_leds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use std::{sync::Arc, time::Duration};
use super::{HwDeviceError, RGBA};

#[allow(unused)]
use crate::{constants, eprintln_v, println_v};
use crate::{constants, interact, eprintln_v, println_v};

const BAUD_RATE: u32 = 460800;
const NUM_LEDS: usize = 80;
Expand Down Expand Up @@ -59,6 +59,7 @@ impl CustomSerialLeds {
}

pub fn send_init_sequence(&mut self) -> Result<()> {
interact::prompt("Press any key to send initialization sequence.");
// some devices need many iterations to sync, so we need to try multiple times
for _ in 0..8 {
let led_map = [RGBA {
Expand Down
Loading

0 comments on commit 276d371

Please sign in to comment.