Skip to main content

ferroui_linuxframebuffer/input/ev_dev/
ev_dev_device_description.rs

1//! The description of an event device (the port of
2//! `EvDevDeviceDescription.cs`): which file it is and what kind of device.
3//!
4//! The reference has an abstract class with one subclass, the touch
5//! screen. The kinds are the cases of an enumeration here; the keyboard,
6//! the mouse and the multi-touch screen are additions
7//! (docs/porting/linux-framebuffer-platform.md, section 7).
8
9use super::{
10    EvDevKeyboardDeviceDescription, EvDevMouseDeviceDescription, EvDevMultiTouchScreenDeviceDescription,
11    EvDevTouchScreenDeviceDescription,
12};
13use std::collections::HashMap;
14
15#[derive(Clone, Debug, PartialEq)]
16pub enum EvDevDeviceDescription {
17    TouchScreen(EvDevTouchScreenDeviceDescription),
18    Keyboard(EvDevKeyboardDeviceDescription),
19    Mouse(EvDevMouseDeviceDescription),
20    MultiTouchScreen(EvDevMultiTouchScreenDeviceDescription),
21}
22
23const FORMAT_ERROR: &str =
24    "Invalid device format, expected `(path):type=(touchscreen|multitouch|keyboard|mouse):[calibration=m11,m12,m21,m22,m31,m32]";
25
26impl EvDevDeviceDescription {
27    pub fn path(&self) -> Option<&str> {
28        match self {
29            Self::TouchScreen(description) => description.path.as_deref(),
30            Self::Keyboard(description) => description.path.as_deref(),
31            Self::Mouse(description) => description.path.as_deref(),
32            Self::MultiTouchScreen(description) => description.path.as_deref(),
33        }
34    }
35
36    pub fn set_path(&mut self, value: Option<String>) {
37        match self {
38            Self::TouchScreen(description) => description.path = value,
39            Self::Keyboard(description) => description.path = value,
40            Self::Mouse(description) => description.path = value,
41            Self::MultiTouchScreen(description) => description.path = value,
42        }
43    }
44
45    /// Parses the value of an environment variable that names a device.
46    pub(crate) fn parse_from_env(env: &str) -> Result<Self, String> {
47        let format_ex = || FORMAT_ERROR.to_owned();
48
49        let items: Vec<&str> = env.split(':').filter(|item| !item.is_empty()).collect();
50        if items.len() < 2 {
51            return Err(format_ex());
52        }
53
54        let path = items[0];
55        let mut dic = HashMap::new();
56        for item in &items[1..] {
57            // An item without a value is an error (the reference fails
58            // with an index out of range there).
59            let (key, value) = item.split_once('=').ok_or_else(format_ex)?;
60            if dic.insert(key.to_owned(), value.to_owned()).is_some() {
61                return Err(format_ex());
62            }
63        }
64
65        let type_ = dic.get("type").ok_or_else(format_ex)?;
66
67        match type_.as_str() {
68            "touchscreen" => Ok(Self::TouchScreen(EvDevTouchScreenDeviceDescription::parse_from_env(path, &dic)?)),
69            // Additions.
70            "multitouch" => {
71                let touch = EvDevTouchScreenDeviceDescription::parse_from_env(path, &dic)?;
72                Ok(Self::MultiTouchScreen(EvDevMultiTouchScreenDeviceDescription {
73                    path: touch.path,
74                    calibration_matrix: touch.calibration_matrix,
75                }))
76            }
77            "keyboard" => Ok(Self::Keyboard(EvDevKeyboardDeviceDescription { path: Some(path.to_owned()) })),
78            "mouse" => Ok(Self::Mouse(EvDevMouseDeviceDescription { path: Some(path.to_owned()) })),
79            _ => Err(format_ex()),
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    // Not from the reference, which has no tests for this file.
87    use super::*;
88    use ferroui_base::Matrix;
89
90    #[test]
91    fn a_touch_screen_is_a_path_a_type_and_an_optional_calibration() {
92        let description = EvDevDeviceDescription::parse_from_env("/dev/input/event3:type=touchscreen").unwrap();
93        assert_eq!(description.path(), Some("/dev/input/event3"));
94        assert_eq!(
95            description,
96            EvDevDeviceDescription::TouchScreen(EvDevTouchScreenDeviceDescription {
97                path: Some("/dev/input/event3".to_owned()),
98                calibration_matrix: Matrix::IDENTITY,
99            })
100        );
101
102        let description =
103            EvDevDeviceDescription::parse_from_env("/dev/input/event3:type=touchscreen:calibration=0,1,-1,0,10,20")
104                .unwrap();
105        let EvDevDeviceDescription::TouchScreen(touch) = description else {
106            panic!("not a touch screen");
107        };
108        assert_eq!(touch.calibration_matrix, Matrix::new(0.0, 1.0, -1.0, 0.0, 10.0, 20.0));
109    }
110
111    #[test]
112    fn the_path_is_set_on_the_description_of_every_kind() {
113        for env in [
114            "/dev/input/event0:type=touchscreen",
115            "/dev/input/event0:type=multitouch",
116            "/dev/input/event0:type=keyboard",
117            "/dev/input/event0:type=mouse",
118        ] {
119            let mut description = EvDevDeviceDescription::parse_from_env(env).unwrap();
120            description.set_path(Some("/dev/input/event7".to_owned()));
121            assert_eq!(description.path(), Some("/dev/input/event7"), "{env}");
122            description.set_path(None);
123            assert_eq!(description.path(), None, "{env}");
124        }
125    }
126
127    #[test]
128    fn the_added_types_are_named_by_type() {
129        let parse = |env: &str| EvDevDeviceDescription::parse_from_env(env).unwrap();
130        assert!(matches!(parse("/dev/input/event0:type=keyboard"), EvDevDeviceDescription::Keyboard(_)));
131        assert!(matches!(parse("/dev/input/event1:type=mouse"), EvDevDeviceDescription::Mouse(_)));
132        assert!(matches!(parse("/dev/input/event2:type=multitouch"), EvDevDeviceDescription::MultiTouchScreen(_)));
133    }
134
135    #[test]
136    fn what_is_not_in_the_format_is_an_error() {
137        for env in [
138            "",
139            "/dev/input/event3",
140            "/dev/input/event3:calibration=1,0,0,1,0,0",
141            "/dev/input/event3:type=joystick",
142            "/dev/input/event3:type",
143            "/dev/input/event3:type=touchscreen:calibration=x",
144        ] {
145            assert!(EvDevDeviceDescription::parse_from_env(env).is_err(), "{env}");
146        }
147    }
148}