Skip to main content

kobo_core/device/
config.rs

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