Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uhid service and alternative cli #35

Merged
merged 13 commits into from
May 1, 2023
Merged
518 changes: 496 additions & 22 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@ version = "0.2.4"
edition = "2018"
publish = false

[features]
uhid = ["cherryrgb/uhid"]

[workspace]
members = [
"service",
"ncli",
]

[workspace.package]
version = "0.2.4"
edition = "2018"
publish = false

[dependencies]
cherryrgb = { path = "cherryrgb" }
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ Get usage help
./cherryrgb_cli animation --help
./cherryrgb_cli custom-colors --help
```
### Alternative CLI and service for Linux

See [this](docs/UHID-driver.md) doc.

### Set LED animation

Expand Down Expand Up @@ -135,8 +138,8 @@ This is a known issue in the keyboard firmware.
It is mentioned here: <https://bbs.archlinux.org/viewtopic.php?id=267365>

- **Proper** way to fix it: **Contact Cherry Support**

- **Workaround**: Comment out the respective line in [`99-cherryrgb.rules`](https://github.com/skraus-dev/cherryrgb-rs/blob/master/udev/99-cherryrgb.rules) and reload/trigger the udev rule.
- See [this](docs/UHID-driver.md) doc for an alternative solution on Linux.

## Disclaimer

Expand Down
9 changes: 8 additions & 1 deletion cherryrgb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ repository = "https://github.com/skraus-dev/cherryrgb-rs"
license = "MIT"
homepage = "https://github.com/skraus-dev/cherryrgb-rs"

[features]
uhid = ["dep:uhid-virt"]

[dependencies]
thiserror = "1"
binrw = "0.8"
hex = "0.4"
log = "0.4"
rgb = "0.8"
rgb = { version = "0.8", features = ["serde"] }
rusb = "0.9"
serde = { version = "1.0.160", features = ["derive"] }
strum = "0.24.1"
strum_macros = "0.24.3"
serde_json = "1.0"

[target.'cfg(all(target_os = "linux"))'.dependencies]
uhid-virt = { version = "0.0.6", optional = true }
3 changes: 2 additions & 1 deletion cherryrgb/src/extensions.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use binrw::{BinRead, BinReaderExt, BinResult, BinWrite, BinWriterExt, ReadOptions, WriteOptions};
use rgb::RGB8;
use serde::{Deserialize, Serialize};
use std::{
io::{Cursor, Read, Seek},
str::FromStr,
Expand All @@ -24,7 +25,7 @@ where
}

/// Wrap around RGB8 type, to implement traits on it
#[derive(Clone, Default, Debug, PartialEq)]
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct OwnRGB8(RGB8);

impl OwnRGB8 {
Expand Down
55 changes: 55 additions & 0 deletions cherryrgb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@

mod extensions;
mod models;
#[cfg(all(target_os = "linux", feature = "uhid"))]
mod vkbd;

use binrw::BinReaderExt;
use models::{Keymap, ProfileKey};
Expand All @@ -66,10 +68,14 @@ use thiserror::Error;
// Re-exports
pub use extensions::{OwnRGB8, ToVec};
pub use hex;
#[cfg(all(target_os = "linux", feature = "uhid"))]
pub use models::RpcAnimation;
pub use models::{Brightness, CustomKeyLeds, LightingMode, Packet, Payload, Speed};
pub use rgb;
pub use rusb;
pub use strum;
#[cfg(all(target_os = "linux", feature = "uhid"))]
pub use vkbd::VirtKbd;

// Constants
/// USB Vendor ID - Cherry GmbH
Expand All @@ -78,6 +84,8 @@ pub const CHERRY_USB_VID: u16 = 0x046a;
const INTERFACE_NUM: u8 = 1;
const INTERRUPT_EP: u8 = 0x82;
static TIMEOUT: Duration = Duration::from_millis(1000);
#[cfg(all(target_os = "linux", feature = "uhid"))]
static SHORT_TIMEOUT: Duration = Duration::from_millis(100);

/// (64 byte packet - 4 byte packet header - 4 byte payload header)
const CHUNK_SIZE: usize = 56;
Expand Down Expand Up @@ -220,6 +228,25 @@ impl CherryKeyboard {
assert_eq!(device_desc.num_configurations(), 1);
assert_eq!(config_desc.num_interfaces(), 2);

// This should find 2 endpoints with Interrupt inputs
for interface in config_desc.interfaces() {
for interface_desc in interface.descriptors() {
for endpoint_desc in interface_desc.endpoint_descriptors() {
if endpoint_desc.direction() == rusb::Direction::In
&& endpoint_desc.transfer_type() == rusb::TransferType::Interrupt
{
log::debug!(
"Found Interrupt input: ci={} if={} se={} addr=0x{:02x}",
config_desc.number(),
interface_desc.interface_number(),
interface_desc.setting_number(),
endpoint_desc.address()
);
}
}
}
}

// Skip kernel driver detachment for non-unix platforms
if cfg!(unix) {
device_handle
Expand Down Expand Up @@ -407,6 +434,34 @@ impl CherryKeyboard {
Ok(all_keys)
}

/// forward a key event from our usb device to the virtual UHID keyboard,
/// filter out any bogus events while doing so.
#[cfg(all(target_os = "linux", feature = "uhid"))]
pub fn forward_filtered_keys(&self, vdevice: &mut VirtKbd) -> Result<(), CherryRgbError> {
let mut buf = [0; 64];
match self
.device_handle
.read_interrupt(INTERRUPT_EP, &mut buf, SHORT_TIMEOUT)
{
Ok(len) => {
// Bogus event data has bit 3 set in the 3rd byte
if len >= 3 && buf[2] >= 8 {
log::debug!(" - BOGUS read {} bytes: {:?} filtered", len, &buf[..len]);
return Ok(());
}
log::debug!(" - read {} bytes: {:?}", len, &buf[..len]);
vdevice.forward(&buf[..len]);
}
Err(err) => {
if err == rusb::Error::Timeout {
return Ok(());
}
return Err(CherryRgbError::GeneralUsbError(err));
}
}
Ok(())
}

/// Just taken 1:1 from usb capture
pub fn fetch_device_state(&self) -> Result<(), CherryRgbError> {
log::trace!("Fetching device state - START");
Expand Down
77 changes: 55 additions & 22 deletions cherryrgb/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,62 @@ use crate::{
};

use binrw::{binrw, until_eof, BinRead, BinWrite, BinWriterExt};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use strum_macros::{EnumString, EnumVariantNames};
use strum_macros::{EnumProperty, EnumString, EnumVariantNames};

/// Modes support:
/// -> C: Color
/// -> S: Speed
/// Mode attributes:
/// -> C: Supports color option
/// -> S: Supports speed option
/// -> U: Unofficial
#[binrw]
#[brw(repr = u8)]
#[derive(Clone, Eq, PartialEq, Debug, EnumString, EnumVariantNames)]
#[derive(
Clone, Eq, PartialEq, Debug, EnumString, EnumProperty, EnumVariantNames, Serialize, Deserialize,
)]
#[strum(serialize_all = "snake_case")]
pub enum LightingMode {
Wave = 0x00, // CS
Spectrum = 0x01, // S
#[strum(props(attr = "CS"))]
Wave = 0x00, // CS
#[strum(props(attr = "S"))]
Spectrum = 0x01, // S
#[strum(props(attr = "CS"))]
Breathing = 0x02, // CS
Static = 0x03, // n/A
Radar = 0x04, // Unofficial
Vortex = 0x05, // Unofficial
Fire = 0x06, // Unofficial
Stars = 0x07, // Unofficial
Rain = 0x0B, // Unofficial (looks like Matrix :D)
#[strum(props(attr = "C"))]
Static = 0x03, // C
#[strum(props(attr = "U"))]
Radar = 0x04, // Unofficial
#[strum(props(attr = "U"))]
Vortex = 0x05, // Unofficial
#[strum(props(attr = "U"))]
Fire = 0x06, // Unofficial
#[strum(props(attr = "U"))]
Stars = 0x07, // Unofficial
#[strum(props(attr = "U"))]
Rain = 0x0B, // Unofficial (looks like Matrix :D)
#[strum(props(attr = ""))]
Custom = 0x08,
Rolling = 0x0A, // S
Curve = 0x0C, // CS
WaveMid = 0x0E, // Unoffical
Scan = 0x0F, // C
#[strum(props(attr = "S"))]
Rolling = 0x0A, // S
#[strum(props(attr = "CS"))]
Curve = 0x0C, // CS
#[strum(props(attr = "yes"))]
WaveMid = 0x0E, // Unofficial
#[strum(props(attr = "C"))]
Scan = 0x0F, // C
#[strum(props(attr = "CS"))]
Radiation = 0x12, // CS
Ripples = 0x13, // CS
#[strum(props(attr = "CS"))]
Ripples = 0x13, // CS
#[strum(props(attr = "CS"))]
SingleKey = 0x15, // CS
}

/// Probably controlled at OS / driver level
/// Just defined here for completeness' sake
#[binrw]
#[brw(repr = u8)]
#[derive(Eq, PartialEq, Debug)]
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum UsbPollingRate {
Low, // 125Hz
Medium, // 250 Hz
Expand All @@ -50,7 +71,7 @@ pub enum UsbPollingRate {
/// LED animation speed
#[binrw]
#[brw(repr = u8)]
#[derive(Clone, Eq, PartialEq, Debug, EnumString, EnumVariantNames)]
#[derive(Clone, Eq, PartialEq, Debug, EnumString, EnumVariantNames, Serialize, Deserialize)]
#[strum(serialize_all = "snake_case")]
pub enum Speed {
VeryFast = 0,
Expand All @@ -63,7 +84,7 @@ pub enum Speed {
/// LED brightness
#[binrw]
#[brw(repr = u8)]
#[derive(Clone, Eq, PartialEq, Debug, EnumString, EnumVariantNames)]
#[derive(Clone, Eq, PartialEq, Debug, EnumString, EnumVariantNames, Serialize, Deserialize)]
#[strum(serialize_all = "snake_case")]
pub enum Brightness {
Off = 0,
Expand Down Expand Up @@ -209,7 +230,7 @@ where
}

/// Wrapper around custom LED color for all keys
#[derive(Default, Debug)]
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct CustomKeyLeds {
key_leds: Vec<OwnRGB8>,
}
Expand Down Expand Up @@ -320,3 +341,15 @@ impl CustomKeyLeds {
Ok(result)
}
}

/// Parameters for set_led_animation (sent serialized from
/// cherryrgb_ncli to cherryrgb_service).
#[cfg(all(target_os = "linux", feature = "uhid"))]
#[derive(Debug, Serialize, Deserialize)]
pub struct RpcAnimation {
pub mode: LightingMode,
pub brightness: Brightness,
pub speed: Speed,
pub color: Option<OwnRGB8>,
pub rainbow: bool,
}
Loading