1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
use super::ecodes;
use super::InputDevice;
use cgmath::Vector2;
use log::debug;
use once_cell::sync::Lazy;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::Duration;

pub const INITIAL_DEVS_AVAILABLE_FOR: Duration = Duration::from_millis(150);

/// A singleton of the EvDevsScan object
pub static SCANNED: Lazy<EvDevs> = Lazy::new(EvDevs::new);

/// This struct contains the results of initially scaning all evdev devices,
/// which allows for device model independancy.
/// Some of its data is used by other constants.
///
/// EvDevsScan has some internal mutability to allow resuing the opened devices
/// for some time to increase performance.
/// TODO: Call this `EvDevsScanOutcome` or EvScanOutcome instead ??
pub struct EvDevs {
    pub wacom_path: PathBuf,
    pub multitouch_path: PathBuf,
    pub gpio_path: PathBuf,

    pub wacom_width: u16,
    pub wacom_height: u16,
    pub mt_width: u16,
    pub mt_height: u16,

    /// The resolution of the wacom no rotation applied
    pub wacom_orig_size: Vector2<u16>,
    pub multitouch_orig_size: Vector2<u16>,

    // Those will be preserved in case they are needed fairly fast
    // to prevent any additional delay of re-opening the fds.
    // They will get removed fairly quickly though.
    wacom_initial_dev: Arc<Mutex<Option<evdev::Device>>>,
    multitouch_initial_dev: Arc<Mutex<Option<evdev::Device>>>,
    gpio_initial_dev: Arc<Mutex<Option<evdev::Device>>>,
}

impl EvDevs {
    /// Scan all the evdev devices, figure out which is which
    /// and get some additional data for lazy constants.
    fn new() -> Self {
        // All of these have to be found
        let mut wacom = None;
        let mut multitouch = None;
        let mut gpio = None;

        // Get all /dev/input/event* file paths
        let mut event_file_paths: Vec<PathBuf> = Vec::new();
        let input_dir = Path::new("/dev/input");
        for entry in input_dir
            .read_dir()
            .unwrap_or_else(|_| panic!("Failed to list {:?}", input_dir))
        {
            let entry = entry.unwrap();
            let file_name = entry.file_name().as_os_str().to_str().unwrap().to_owned();
            if !file_name.starts_with("event") {
                continue;
            }

            let evdev_path = input_dir.join(&file_name);
            event_file_paths.push(evdev_path);
        }

        // Open and check capabilities of each event device
        for evdev_path in event_file_paths {
            let dev = evdev::Device::open(&evdev_path)
                .unwrap_or_else(|_| panic!("Failed to scan {:?}", &evdev_path));
            if dev.supported_events().contains(evdev::EventType::KEY) {
                if dev
                    .supported_keys()
                    .map(|s| s.contains(evdev::Key::BTN_STYLUS))
                    .unwrap_or(false)
                    && dev.supported_events().contains(evdev::EventType::ABSOLUTE)
                {
                    // The device with the wacom digitizer has the BTN_STYLUS event
                    // and support KEY as well as ABSOLUTE event types
                    wacom = Some((evdev_path.clone(), dev));
                    continue;
                }

                if dev
                    .supported_keys()
                    .map(|s| s.contains(evdev::Key::KEY_POWER))
                    .unwrap_or(false)
                {
                    // The device for buttons has the KEY_POWER button and support KEY event types
                    gpio = Some((evdev_path.clone(), dev));
                    continue;
                }
            }

            if dev.supported_events().contains(evdev::EventType::RELATIVE)
                && dev
                    .supported_absolute_axes()
                    .map(|s| s.contains(evdev::AbsoluteAxisType::ABS_MT_SLOT))
                    .unwrap_or(false)
            {
                // The touchscreen device has the ABS_MT_SLOT event and supports RELATIVE event types
                multitouch = Some((evdev_path.clone(), dev));
                continue;
            }
        }

        // Ensure that all devices were found
        let (wacom_path, wacom_dev) = wacom.expect("Failed to find the wacom digitizer evdev!");
        let (multitouch_path, multitouch_dev) =
            multitouch.expect("Failed to find the multitouch evdev!");
        let (gpio_path, gpio_dev) = gpio.expect("Failed to find the gpio evdev!");

        // SIZES
        let wacom_state = wacom_dev.get_abs_state().unwrap();
        let wacom_orig_size = Vector2 {
            x: wacom_state[ecodes::ABS_X as usize].maximum as u16,
            y: wacom_state[ecodes::ABS_Y as usize].maximum as u16,
        };
        // X and Y are swapped for the wacom since rM1 and probably also rM2 have it rotated
        let (wacom_width, wacom_height) = crate::device::CURRENT_DEVICE
            .get_wacom_placement()
            .rotation
            .rotated_size(&wacom_orig_size)
            .into();

        let mt_state = multitouch_dev.get_abs_state().unwrap();
        let multitouch_orig_size = Vector2 {
            x: mt_state[ecodes::ABS_MT_POSITION_X as usize].maximum as u16,
            y: mt_state[ecodes::ABS_MT_POSITION_Y as usize].maximum as u16,
        };
        // Axes are swapped on the rM2 (see InputDeviceRotation for more)
        let (mt_width, mt_height) = crate::device::CURRENT_DEVICE
            .get_multitouch_placement()
            .rotation
            .rotated_size(&multitouch_orig_size)
            .into();

        // DEVICES
        let wacom_initial_dev = Arc::new(Mutex::new(Some(wacom_dev)));
        let multitouch_initial_dev = Arc::new(Mutex::new(Some(multitouch_dev)));
        let gpio_initial_dev = Arc::new(Mutex::new(Some(gpio_dev)));

        // Spawn a thread to remove close the initial devices after some time
        let wacom_initial_dev2 = wacom_initial_dev.clone();
        let multitouch_initial_dev2 = multitouch_initial_dev.clone();
        let gpio_initial_dev2 = gpio_initial_dev.clone();
        std::thread::spawn(move || {
            std::thread::sleep(INITIAL_DEVS_AVAILABLE_FOR);
            // Remove devices (and thereby closing them)
            (*(*wacom_initial_dev2).lock().unwrap()) = None;
            (*(*multitouch_initial_dev2).lock().unwrap()) = None;
            (*(*gpio_initial_dev2).lock().unwrap()) = None;
            debug!("Closed initially opened evdev fds (if not used by now).");
        });

        Self {
            wacom_path,
            multitouch_path,
            gpio_path,

            wacom_width,
            wacom_height,

            mt_width,
            mt_height,

            multitouch_orig_size,
            wacom_orig_size,

            wacom_initial_dev,
            multitouch_initial_dev,
            gpio_initial_dev,
        }
    }

    /// Get the path to a InputDevice
    pub fn get_path(&self, device: InputDevice) -> &PathBuf {
        match device {
            InputDevice::Wacom => &self.wacom_path,
            InputDevice::Multitouch => &self.multitouch_path,
            InputDevice::GPIO => &self.gpio_path,
            InputDevice::Unknown => panic!("\"InputDevice::Unkown\" is no device!"),
        }
    }

    /// Get a ev device. If this is called early, it can get the device used for the initial scan.
    /// If an early device is returned, it might contain a few events from the past!
    pub fn get_device(&self, device: InputDevice) -> Result<evdev::Device, impl std::error::Error> {
        let dev_arc = match device {
            InputDevice::Wacom => self.wacom_initial_dev.clone(),
            InputDevice::Multitouch => self.multitouch_initial_dev.clone(),
            InputDevice::GPIO => self.gpio_initial_dev.clone(),
            InputDevice::Unknown => panic!("\"InputDevice::Unkown\" is no device!"),
        };

        let mut resuable_device = dev_arc.lock().unwrap();
        if resuable_device.is_some() {
            Ok(resuable_device.take().unwrap())
        } else {
            evdev::Device::open(self.get_path(device))
        }
    }
}