steeloxide 0.1.0

A rust library for interacting with SteelSeries devices.
Documentation
use hidapi::{HidApi, HidDevice};

use crate::{
    colour::Rgb, consts::STEELSERIES_APS, error::{DeviceError, MouseError, Result}, keyboard::{packet::{ColourPacket, KeyboardPacket}}
};

pub mod device_type;
pub(crate) mod consts;
pub(crate) mod packet;

pub use device_type::KeyboardDevice;

pub struct Keyboard {
    device: HidDevice,
    api: HidApi,
    device_type: KeyboardDevice,

    colours: ColourPacket,
}

impl std::fmt::Debug for Keyboard {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Keyboard")
            .field("device_type", &self.device_type)
            .field("colours", &self.colours)
            .finish_non_exhaustive() 
    }
}

impl Keyboard {
    // TODO: more types
    pub fn new(device_type: KeyboardDevice) -> Result<Self> {
        let api = HidApi::new()?;

        let info = api
            .device_list()
            .find(|d| {
                d.vendor_id() == STEELSERIES_APS
                    && d.product_id() == device_type.product_id()
                    && d.interface_number() == device_type.interface()
            })
            .ok_or(DeviceError::NotFound)?;

        #[cfg(feature = "default")]
        tracing::info!(
            "found {} (VID=0x{:04X}, PID=0x{:04X}, interface={})",
            info.product_string().unwrap_or("Unknown"),
            info.vendor_id(),
            info.product_id(),
            info.interface_number(),
        );

        let device = info.open_device(&api)?;
        Ok(Self {
            api,
            device_type,
            device,
            colours: ColourPacket::new(device_type)
        })
    }

    pub fn get_type(&self) -> KeyboardDevice {
        self.device_type
    }

    pub fn set_zone(&mut self, zone: usize, colour: Rgb) -> Result<usize> {
        self.colours.change_zone(zone, colour)?;
        self.send_packet(&self.colours)
    }

    pub fn set_colours(&mut self, colours: &[Rgb]) -> Result<usize> {
        if colours.len() > self.device_type.get_zones() {
            return Err(MouseError::ZoneOutOfRange.into());
        }

        for (i, zone) in colours.iter().enumerate() {
            self.colours.change_zone(i, *zone)?;
        }

        self.send_packet(&self.colours)
    }

    pub fn get_colours(&self) -> Vec<Rgb> {
        self.colours.get_colours()
    }

    pub fn get_zone(&self, zone: usize) -> Result<Rgb> {
        self.colours.get_zone(zone)
    }

    fn send_packet<T: KeyboardPacket>(&self, data: &T) -> Result<usize> {
        Ok(self.device.write(&data.serialize()?)?)
    }
}