Skip to main content

kobo_core/device/
config.rs

1//! Pure device configuration types and lookup - no IO.
2//!
3//! These types describe per-model hardware capabilities. IO-bound detection
4//! (`detect_device`, `scan_input_devices`, `automagic_*`) lives in the app
5//! crate's `device::hw` module.
6
7use crate::device::registry::DEVICES;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum SocFamily {
11    Mtk,
12    Nxp,
13    Sunxi,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum TouchProtocol {
18    Snow,
19    Phoenix,
20    Legacy,
21}
22
23#[derive(Debug, Clone)]
24#[allow(dead_code)]
25pub struct FrontlightConfig {
26    pub brightness_path: String,
27    pub mixer_path: Option<String>,
28    pub nl_min: u32,
29    pub nl_max: u32,
30    pub nl_inverted: bool,
31}
32
33#[derive(Debug, Clone)]
34#[allow(dead_code)]
35pub struct DeviceConfig {
36    pub codename: &'static str,
37    pub model: &'static str,
38    pub soc: SocFamily,
39    pub display_dpi: u32,
40    pub has_color_screen: bool,
41    pub has_natural_light: bool,
42    pub has_bt: bool,
43    pub has_keys: bool,
44    pub has_gsensor: bool,
45    pub has_eclipse_wfm: bool,
46    pub is_smp: bool,
47    pub frontlight: FrontlightConfig,
48    pub battery_sysfs: String,
49    pub touch_protocol: TouchProtocol,
50    pub touch_switch_xy: bool,
51    pub touch_mirrored_x: bool,
52    pub touch_mirrored_y: bool,
53    pub standby_state: &'static str,
54    pub automagic_sysfs: bool,
55}
56
57impl Default for FrontlightConfig {
58    fn default() -> Self {
59        FrontlightConfig {
60            brightness_path: "/sys/class/backlight/mxc_msp430.0/brightness".into(),
61            mixer_path: None,
62            nl_min: 0,
63            nl_max: 10,
64            nl_inverted: true,
65        }
66    }
67}
68
69#[derive(Debug, Clone)]
70pub struct InputDevices {
71    pub touch_dev: String,
72    pub power_dev: String,
73}
74
75pub fn lookup_device(codename: &str) -> Option<&'static DeviceConfig> {
76    DEVICES.iter().find(|d| d.codename == codename)
77}
78
79/// Pure device resolution: the matching entry for `codename`, or the Libra
80/// Colour (`monza`) fallback used when the codename is unrecognised. No IO.
81pub fn detect_from_codename(codename: &str) -> &'static DeviceConfig {
82    lookup_device(codename).unwrap_or_else(|| {
83        DEVICES
84            .iter()
85            .find(|d| d.codename == "monza")
86            .expect("monza entry must exist in DEVICES")
87    })
88}
89
90/// Pure parser for `/proc/bus/input/devices` text. Returns (touch_path,
91/// power_path) extracted from the Handlers + Phys lines, before any fallback
92/// to /dev/input/event{1,2}. No IO.
93pub fn parse_input_devices(content: &str) -> (Option<String>, Option<String>) {
94    let mut touch_path: Option<String> = None;
95    let mut power_path: Option<String> = None;
96    let mut current_handlers = Vec::new();
97    for line in content.lines() {
98        if let Some(handlers) = line.strip_prefix("H: Handlers=") {
99            current_handlers.clear();
100            current_handlers = handlers.split_whitespace().map(String::from).collect();
101        }
102        if let Some(dev) = line.strip_prefix("P: Phys=") {
103            let is_touch = current_handlers
104                .iter()
105                .any(|h| h.starts_with("event") && (dev.contains("touch") || dev.contains("ts")));
106            let is_power = current_handlers.iter().any(|h| {
107                h.starts_with("event")
108                    && (dev.contains("gpio") || dev.contains("power") || dev.contains("keys"))
109            });
110            if is_touch && touch_path.is_none() {
111                for h in &current_handlers {
112                    if h.starts_with("event") {
113                        touch_path = Some(format!("/dev/input/{}", h));
114                        break;
115                    }
116                }
117            }
118            if is_power && power_path.is_none() {
119                for h in &current_handlers {
120                    if h.starts_with("event") {
121                        power_path = Some(format!("/dev/input/{}", h));
122                        break;
123                    }
124                }
125            }
126        }
127    }
128    (touch_path, power_path)
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn detect_from_codename_known() {
137        let d = detect_from_codename("monza");
138        assert_eq!(d.model, "Kobo Libra Colour");
139        assert_eq!(d.soc, SocFamily::Mtk);
140    }
141
142    #[test]
143    fn detect_from_codename_unknown_falls_back() {
144        let fallback = detect_from_codename("does-not-exist");
145        assert_eq!(
146            fallback.codename, "monza",
147            "unknown codename must fall back to monza"
148        );
149        assert!(lookup_device("does-not-exist").is_none());
150    }
151
152    #[test]
153    fn parse_input_devices_extracts_event_nodes() {
154        let sample = "\
155I: Bus=0018 Vendor=0000 Product=0000 Version=0000
156N: Name=\"cyttsp5_mt\"
157H: Handlers=event1 mouse0
158P: Phys=cyttsp5_mt/input0 touch\n\
159I: Bus=0019 Vendor=0001 Product=0001 Version=0100
160N: Name=\"gpio-keys\"
161H: Handlers=event2
162P: Phys=gpio-keys/power0 power\n";
163        let (touch, power) = parse_input_devices(sample);
164        assert_eq!(touch.as_deref(), Some("/dev/input/event1"));
165        assert_eq!(power.as_deref(), Some("/dev/input/event2"));
166    }
167}