gdevd/
lib.rs

1#[macro_use]
2extern crate log;
3#[macro_use]
4extern crate quick_error;
5
6use std::convert::TryFrom;
7use std::fmt;
8use std::fmt::Display;
9use std::hash::{Hash, Hasher};
10use std::ops::Deref;
11use std::sync::{mpsc, Arc, Mutex, MutexGuard};
12
13use hex::FromHexError;
14use quick_error::ResultExt;
15use rusb::{Context, Device, Hotplug, HotplugBuilder, Registration, UsbContext};
16
17use crate::config::Config;
18use crate::drivers::g203_lightsync::G203LightsyncDriver;
19use crate::drivers::g213::G213Driver;
20
21pub mod config;
22pub mod drivers;
23pub mod usb_ext;
24
25const LOGITECH_USB_VENDOR_ID: u16 = 0x046d;
26
27/// RGB color
28#[derive(Clone, Debug)]
29pub struct RgbColor(pub u8, pub u8, pub u8);
30
31impl RgbColor {
32    #[inline]
33    pub fn red(&self) -> u8 {
34        self.0
35    }
36
37    #[inline]
38    pub fn green(&self) -> u8 {
39        self.1
40    }
41
42    #[inline]
43    pub fn blue(&self) -> u8 {
44        self.2
45    }
46
47    pub fn from_hex(rgb_hex: &str) -> Result<Self, FromHexError> {
48        let mut bytes = [0u8; 3];
49        hex::decode_to_slice(rgb_hex, &mut bytes as &mut [u8])?;
50        Ok(RgbColor(bytes[0], bytes[1], bytes[2]))
51    }
52
53    pub fn to_hex(&self) -> String {
54        hex::encode([self.0, self.1, self.2])
55    }
56
57    #[inline]
58    pub fn to_int(&self) -> u32 {
59        ((self.0 as u32) << 16) | ((self.1 as u32) << 8) | (self.2 as u32)
60    }
61}
62
63#[derive(PartialEq, Eq, Debug, Copy, Clone)]
64pub enum Direction {
65    LeftToRight = 1,
66    RightToLeft = 6,
67    CenterToEdge = 3,
68    EdgeToCenter = 8,
69}
70
71impl TryFrom<&str> for Direction {
72    type Error = ();
73
74    fn try_from(value: &str) -> Result<Self, Self::Error> {
75        match value {
76            "left-to-right" => Ok(Direction::LeftToRight),
77            "right-to-left" => Ok(Direction::RightToLeft),
78            "center-to-edge" => Ok(Direction::CenterToEdge),
79            "edge-to-center" => Ok(Direction::EdgeToCenter),
80            _ => Err(()),
81        }
82    }
83}
84
85/// speed of effect
86#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq)]
87pub struct Speed(u16);
88
89impl From<u16> for Speed {
90    #[inline]
91    fn from(input: u16) -> Self {
92        Speed(input)
93    }
94}
95
96/// DPI
97#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq)]
98pub struct Dpi(u16);
99
100impl From<u16> for Dpi {
101    #[inline]
102    fn from(input: u16) -> Self {
103        Dpi(input)
104    }
105}
106
107/// Brightness
108#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq)]
109pub struct Brightness(u8);
110
111impl Default for Brightness {
112    #[inline]
113    fn default() -> Self {
114        Brightness(100)
115    }
116}
117
118impl TryFrom<u8> for Brightness {
119    type Error = CommandError;
120
121    fn try_from(value: u8) -> Result<Self, Self::Error> {
122        if value <= 100 {
123            Ok(Brightness(value))
124        } else {
125            Err(CommandError::InvalidArgument(
126                "brightness",
127                format!("{} < {}", value, 100),
128            ))
129        }
130    }
131}
132
133/// command to send to device to change color
134#[derive(Clone, Debug)]
135pub enum Command {
136    ColorSector(RgbColor, Option<u8>),
137    Breathe(RgbColor, Option<Speed>, Option<Brightness>),
138    Cycle(Option<Speed>, Option<Brightness>),
139    Wave(Direction, Option<Speed>, Option<Brightness>),
140    Blend(Option<Speed>, Option<Brightness>),
141    StartEffect(bool),
142    Dpi(Dpi),
143}
144
145pub type UsbDevice = Device<Context>;
146
147pub enum GDeviceManagerEvent {
148    DevicePluggedIn(UsbDevice),
149    DevicePluggedOut(UsbDevice),
150    Shutdown,
151}
152
153#[derive(Debug)]
154pub enum DeviceType {
155    Keyboard,
156    Mouse,
157}
158
159pub struct GModelId(String);
160
161/// Driver for Logitech G devices
162pub trait GDeviceDriver: Send {
163    fn get_model(&self) -> GDeviceModelRef;
164    fn open_device(&self, device: &UsbDevice) -> Option<Box<dyn GDevice>>;
165}
166
167pub type GDeviceDriverRef = Box<dyn GDeviceDriver>;
168
169/// Logitech G device model series
170///
171/// Implementation is provided by a driver.
172pub trait GDeviceModel: Send + Sync {
173    fn get_sectors(&self) -> u8;
174
175    fn get_default_color(&self) -> RgbColor;
176
177    fn get_name(&self) -> &'static str;
178
179    fn get_type(&self) -> DeviceType;
180
181    fn usb_product_id(&self) -> u16;
182}
183
184pub type GDeviceModelRef = Arc<dyn GDeviceModel>;
185
186/// Logitech G device
187///
188/// Implementation is provided by a driver.
189pub trait GDevice: Display + Send {
190    /// Return USB device reference.
191    fn dev(&self) -> &UsbDevice;
192    /// Return serial number
193    fn serial_number(&self) -> &str;
194    /// Return device model information
195    fn get_model(&self) -> GDeviceModelRef;
196    /// Send command to device
197    fn send_command(&mut self, cmd: Command) -> CommandResult<()>;
198}
199
200pub type GDeviceRef = Box<dyn GDevice>;
201
202pub struct GDeviceInfo {
203    pub model: &'static str,
204    pub serial: String,
205}
206
207quick_error! {
208    #[derive(Debug)]
209    pub enum CommandError {
210        Usb(context: String, err: rusb::Error) {
211            display("USB error: {}: {}", context, err)
212            context(message: &'a str, err: rusb::Error)
213                -> (message.to_string(), err)
214        }
215        InvalidArgument(arg: &'static str, msg: String) {
216            display("Invalid argument {}: {}", arg, msg)
217        }
218        InvalidCommand {
219            display("Invalid command")
220        }
221    }
222}
223
224type CommandResult<T> = Result<T, CommandError>;
225
226impl PartialEq for Box<dyn GDeviceModel> {
227    fn eq(&self, other: &Self) -> bool {
228        self.get_name() == other.get_name()
229    }
230}
231
232impl Eq for Box<dyn GDeviceModel> {}
233
234impl Hash for Box<dyn GDeviceModel> {
235    fn hash<H: Hasher>(&self, state: &mut H) {
236        state.write(self.get_name().as_bytes())
237    }
238}
239
240struct GDeviceManagerState {
241    pub context: Context,
242    #[allow(dead_code)]
243    hotplug: Registration<Context>,
244    config: Config,
245    devices: Vec<GDeviceRef>,
246    drivers: Vec<GDeviceDriverRef>,
247}
248
249impl GDeviceManagerState {
250    pub fn new(tx: mpsc::SyncSender<GDeviceManagerEvent>) -> CommandResult<Self> {
251        let context = Context::new().context("creating USB context")?;
252        let config = Config::load();
253        Ok(Self {
254            devices: vec![],
255            config,
256            drivers: vec![
257                Box::<G213Driver>::default(),
258                Box::<G203LightsyncDriver>::default(),
259            ],
260            hotplug: HotplugBuilder::new()
261                .vendor_id(LOGITECH_USB_VENDOR_ID)
262                .register(&context, Box::new(HotPlugHandler { channel: tx }))
263                .context("registering hotplug callback")?,
264            context,
265        })
266    }
267
268    pub fn get_devices(&mut self) -> Vec<GDeviceInfo> {
269        self.devices
270            .iter()
271            .map(|dev| GDeviceInfo {
272                model: dev.get_model().get_name(),
273                serial: dev.serial_number().to_string(),
274            })
275            .collect()
276    }
277
278    pub fn get_drivers(&mut self) -> Vec<&'static str> {
279        self.drivers
280            .iter()
281            .map(|drv| drv.get_model().get_name())
282            .collect()
283    }
284
285    pub fn load_devices(&mut self) -> CommandResult<()> {
286        info!("Scan devices");
287        let usb_devices = self.context.devices().context("listing USB devices")?;
288        self.devices = usb_devices
289            .iter()
290            .filter_map(|device| self.try_open_device(&device))
291            .collect();
292        info!("Found {} device(s)", self.devices.len());
293        self.apply_config();
294        Ok(())
295    }
296
297    fn find_driver_for_device(&self, device: &Device<Context>) -> Option<&dyn GDeviceDriver> {
298        let descriptor = device.device_descriptor().unwrap();
299        if descriptor.vendor_id() == LOGITECH_USB_VENDOR_ID {
300            self.drivers
301                .iter()
302                .find(|driver| descriptor.product_id() == driver.get_model().usb_product_id())
303                .map(|driver| driver.deref())
304        } else {
305            None
306        }
307    }
308
309    fn try_open_device(&self, device: &UsbDevice) -> Option<Box<dyn GDevice>> {
310        if let Some(driver) = self.find_driver_for_device(device) {
311            info!("Found device {}", driver.get_model().get_name());
312            driver.open_device(device)
313        } else {
314            None
315        }
316    }
317
318    pub fn send_command(&mut self, cmd: Command) {
319        for device in &mut self.devices {
320            if let Err(err) = device.send_command(cmd.clone()) {
321                error!("Sending command failed for device: {:?}", err);
322            }
323
324            self.config.save_command(&*device.get_model(), cmd.clone())
325        }
326    }
327
328    fn apply_config(&mut self) {
329        for device in &mut self.devices {
330            Self::apply_device_config(device, &self.config);
331        }
332    }
333
334    fn apply_device_config(device: &mut GDeviceRef, config: &Config) {
335        info!("Setting config for {}", device.get_model().get_name());
336        for command in config.commands_for(&*device.get_model()) {
337            if let Err(err) = device.send_command(command.clone()) {
338                error!("Unable to send command to device {device}: {:?}", err);
339            }
340        }
341    }
342
343    pub fn refresh(&mut self) {
344        info!("Refreshing");
345        self.config = Config::load();
346        self.apply_config();
347    }
348
349    pub fn on_new_usb_device(&mut self, dev: UsbDevice) {
350        if let Some(mut gdev) = self.try_open_device(&dev) {
351            if self.devices.iter().any(|existing| existing.dev() == &dev) {
352                warn!("Plugged in device {} already exists", gdev)
353            } else {
354                info!("Device plugged in: {}", gdev);
355                Self::apply_device_config(&mut gdev, &self.config);
356                self.devices.push(gdev);
357            }
358        }
359    }
360
361    pub fn on_lost_usb_device(&mut self, dev: UsbDevice) {
362        self.devices.retain(|existing| {
363            if existing.dev() == &dev {
364                info!("Device unplugged: {}", existing);
365                false
366            } else {
367                true
368            }
369        });
370    }
371}
372
373pub struct GDeviceManager {
374    state: Mutex<GDeviceManagerState>,
375    rx: Mutex<mpsc::Receiver<GDeviceManagerEvent>>,
376    tx: mpsc::SyncSender<GDeviceManagerEvent>,
377}
378
379impl GDeviceManager {
380    /// Try to create device manager with USB connection
381    pub fn try_new() -> CommandResult<Self> {
382        let (tx, rx) = mpsc::sync_channel(1024);
383        let state = GDeviceManagerState::new(tx.clone())?;
384        Ok(Self {
385            tx,
386            rx: Mutex::new(rx),
387            state: Mutex::new(state),
388        })
389    }
390
391    pub fn context(&self) -> Context {
392        self.state().context.clone()
393    }
394
395    pub fn channel(&self) -> &mpsc::SyncSender<GDeviceManagerEvent> {
396        &self.tx
397    }
398
399    pub fn load_devices(&self) -> CommandResult<()> {
400        self.state().load_devices()
401    }
402
403    /// Send command to all devices
404    pub fn list(&self) -> Vec<GDeviceInfo> {
405        self.state().get_devices()
406    }
407
408    /// Send command to all devices
409    pub fn list_drivers(&self) -> Vec<&'static str> {
410        self.state().get_drivers()
411    }
412
413    /// Send command to all devices
414    pub fn send_command(&self, cmd: Command) {
415        self.state().send_command(cmd)
416    }
417
418    /// Send current config to device
419    pub fn apply_config(&mut self) {
420        self.state().apply_config()
421    }
422
423    /// Refresh config from filesystem and send config
424    pub fn refresh(&self) {
425        self.state().refresh()
426    }
427
428    pub fn run(&self) {
429        while let Ok(msg) = self.rx.lock().unwrap().recv() {
430            match msg {
431                GDeviceManagerEvent::DevicePluggedIn(dev) => self.state().on_new_usb_device(dev),
432                GDeviceManagerEvent::DevicePluggedOut(dev) => self.state().on_lost_usb_device(dev),
433                GDeviceManagerEvent::Shutdown => break,
434            }
435        }
436    }
437
438    fn state(&self) -> MutexGuard<'_, GDeviceManagerState> {
439        self.state.lock().unwrap()
440    }
441}
442
443impl fmt::Debug for GDeviceManager {
444    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
445        f.write_str("GDeviceManager")
446    }
447}
448
449struct HotPlugHandler {
450    channel: mpsc::SyncSender<GDeviceManagerEvent>,
451}
452
453impl HotPlugHandler {
454    fn send(&self, cmd: GDeviceManagerEvent) {
455        self.channel.send(cmd).expect("channel should be alive");
456    }
457}
458
459impl Hotplug<Context> for HotPlugHandler {
460    fn device_arrived(&mut self, device: UsbDevice) {
461        self.send(GDeviceManagerEvent::DevicePluggedIn(device));
462    }
463
464    fn device_left(&mut self, device: UsbDevice) {
465        self.send(GDeviceManagerEvent::DevicePluggedOut(device));
466    }
467}