1use crate::device::error::DeviceError;
4use crate::device::metadata::DeviceMetadata;
5use crate::input::TouchProto;
6use lazy_static::lazy_static;
7use once_cell::sync::OnceCell;
8use std::env;
9use std::fmt::Debug;
10
11mod error;
12mod metadata;
13mod model;
14mod types;
15mod usb;
16mod wifi;
17
18pub use model::Model;
19pub use types::{FrontlightKind, Orientation};
20
21pub struct Device {
22 pub model: Model,
23 pub proto: TouchProto,
24 pub dims: (u32, u32),
25 pub dpi: u16,
26 metadata: OnceCell<DeviceMetadata>,
27 wifi_manager: OnceCell<Box<dyn crate::device::wifi::WifiManager>>,
28}
29
30impl Debug for Device {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 f.debug_struct("Device")
33 .field("model", &self.model)
34 .field("proto", &self.proto)
35 .field("dims", &self.dims)
36 .field("dpi", &self.dpi)
37 .finish()
38 }
39}
40impl Device {
41 fn new(product: &str, model_number: &str) -> Device {
43 match product {
44 "kraken" => Device {
45 model: Model::Glo,
46 proto: TouchProto::Single,
47 dims: (758, 1024),
48 dpi: 212,
49 metadata: OnceCell::new(),
50 wifi_manager: OnceCell::new(),
51 },
52 "pixie" => Device {
53 model: Model::Mini,
54 proto: TouchProto::Single,
55 dims: (600, 800),
56 dpi: 200,
57 metadata: OnceCell::new(),
58 wifi_manager: OnceCell::new(),
59 },
60 "dragon" => Device {
61 model: Model::AuraHD,
62 proto: TouchProto::Single,
63 dims: (1080, 1440),
64 dpi: 265,
65 metadata: OnceCell::new(),
66 wifi_manager: OnceCell::new(),
67 },
68 "phoenix" => Device {
69 model: Model::Aura,
70 proto: TouchProto::MultiA,
71 dims: (758, 1024),
72 dpi: 212,
73 metadata: OnceCell::new(),
74 wifi_manager: OnceCell::new(),
75 },
76 "dahlia" => Device {
77 model: Model::AuraH2O,
78 proto: TouchProto::MultiA,
79 dims: (1080, 1440),
80 dpi: 265,
81 metadata: OnceCell::new(),
82 wifi_manager: OnceCell::new(),
83 },
84 "alyssum" => Device {
85 model: Model::GloHD,
86 proto: TouchProto::MultiA,
87 dims: (1072, 1448),
88 dpi: 300,
89 metadata: OnceCell::new(),
90 wifi_manager: OnceCell::new(),
91 },
92 "pika" => Device {
93 model: Model::Touch2,
94 proto: TouchProto::MultiA,
95 dims: (600, 800),
96 dpi: 167,
97 metadata: OnceCell::new(),
98 wifi_manager: OnceCell::new(),
99 },
100 "daylight" => Device {
101 model: if model_number == "381" {
102 Model::AuraONELimEd
103 } else {
104 Model::AuraONE
105 },
106 proto: TouchProto::MultiA,
107 dims: (1404, 1872),
108 dpi: 300,
109 metadata: OnceCell::new(),
110 wifi_manager: OnceCell::new(),
111 },
112 "star" => Device {
113 model: if model_number == "379" {
114 Model::AuraEd2V2
115 } else {
116 Model::AuraEd2V1
117 },
118 proto: TouchProto::MultiA,
119 dims: (758, 1024),
120 dpi: 212,
121 metadata: OnceCell::new(),
122 wifi_manager: OnceCell::new(),
123 },
124 "snow" => Device {
125 model: if model_number == "378" {
126 Model::AuraH2OEd2V2
127 } else {
128 Model::AuraH2OEd2V1
129 },
130 proto: TouchProto::MultiB,
131 dims: (1080, 1440),
132 dpi: 265,
133 metadata: OnceCell::new(),
134 wifi_manager: OnceCell::new(),
135 },
136 "nova" => Device {
137 model: Model::ClaraHD,
138 proto: TouchProto::MultiB,
139 dims: (1072, 1448),
140 dpi: 300,
141 metadata: OnceCell::new(),
142 wifi_manager: OnceCell::new(),
143 },
144 "frost" => Device {
145 model: if model_number == "380" {
146 Model::Forma32GB
147 } else {
148 Model::Forma
149 },
150 proto: TouchProto::MultiB,
151 dims: (1440, 1920),
152 dpi: 300,
153 metadata: OnceCell::new(),
154 wifi_manager: OnceCell::new(),
155 },
156 "storm" => Device {
157 model: Model::LibraH2O,
158 proto: TouchProto::MultiB,
159 dims: (1264, 1680),
160 dpi: 300,
161 metadata: OnceCell::new(),
162 wifi_manager: OnceCell::new(),
163 },
164 "luna" => Device {
165 model: Model::Nia,
166 proto: TouchProto::MultiA,
167 dims: (758, 1024),
168 dpi: 212,
169 metadata: OnceCell::new(),
170 wifi_manager: OnceCell::new(),
171 },
172 "europa" => Device {
173 model: Model::Elipsa,
174 proto: TouchProto::MultiC,
175 dims: (1404, 1872),
176 dpi: 227,
177 metadata: OnceCell::new(),
178 wifi_manager: OnceCell::new(),
179 },
180 "cadmus" => Device {
181 model: Model::Sage,
182 proto: TouchProto::MultiC,
183 dims: (1440, 1920),
184 dpi: 300,
185 metadata: OnceCell::new(),
186 wifi_manager: OnceCell::new(),
187 },
188 "io" => Device {
189 model: Model::Libra2,
190 proto: TouchProto::MultiC,
191 dims: (1264, 1680),
192 dpi: 300,
193 metadata: OnceCell::new(),
194 wifi_manager: OnceCell::new(),
195 },
196 "goldfinch" => Device {
197 model: Model::Clara2E,
198 proto: TouchProto::MultiB,
199 dims: (1072, 1448),
200 dpi: 300,
201 metadata: OnceCell::new(),
202 wifi_manager: OnceCell::new(),
203 },
204 "condor" => Device {
205 model: Model::Elipsa2E,
206 proto: TouchProto::MultiC,
207 dims: (1404, 1872),
208 dpi: 227,
209 metadata: OnceCell::new(),
210 wifi_manager: OnceCell::new(),
211 },
212 "spaBW" | "spaBWTPV" => Device {
213 model: Model::ClaraBW,
214 proto: TouchProto::MultiB,
215 dims: (1072, 1448),
216 dpi: 300,
217 metadata: OnceCell::new(),
218 wifi_manager: OnceCell::new(),
219 },
220 "spaColour" => Device {
221 model: Model::ClaraColour,
222 proto: TouchProto::MultiB,
223 dims: (1072, 1448),
224 dpi: 300,
225 metadata: OnceCell::new(),
226 wifi_manager: OnceCell::new(),
227 },
228 "monza" => Device {
229 model: Model::LibraColour,
230 proto: TouchProto::MultiB,
231 dims: (1264, 1680),
232 dpi: 300,
233 metadata: OnceCell::new(),
234 wifi_manager: OnceCell::new(),
235 },
236 _ => Device {
237 model: if model_number == "320" {
238 Model::TouchC
239 } else {
240 Model::TouchAB
241 },
242 proto: TouchProto::Single,
243 dims: (600, 800),
244 dpi: 167,
245 metadata: OnceCell::new(),
246 wifi_manager: OnceCell::new(),
247 },
248 }
249 }
250
251 pub fn metadata(&self) -> Result<&DeviceMetadata, DeviceError> {
253 self.metadata.get_or_try_init(DeviceMetadata::read)
254 }
255
256 #[cfg(feature = "kobo")]
258 pub fn usb_manager(
259 &self,
260 ) -> Result<Box<dyn crate::device::usb::UsbManager>, crate::device::usb::UsbError> {
261 let metadata = self
262 .metadata()
263 .map_err(|e| crate::device::usb::UsbError::DeviceInfo(e.to_string()))?
264 .clone();
265 crate::device::usb::create_usb_manager(metadata)
266 }
267
268 #[cfg(not(feature = "kobo"))]
270 pub fn usb_manager(
271 &self,
272 ) -> Result<Box<dyn crate::device::usb::UsbManager>, crate::device::usb::UsbError> {
273 Ok(Box::new(crate::device::usb::StubUsbManager))
274 }
275
276 pub fn wifi_manager(
278 &self,
279 ) -> Result<&dyn crate::device::wifi::WifiManager, crate::device::wifi::WifiError> {
280 self.wifi_manager
281 .get_or_try_init(crate::device::wifi::create_wifi_manager)
282 .map(|b| b.as_ref())
283 }
284
285 pub fn color_samples(&self) -> usize {
287 match self.model {
288 Model::ClaraColour | Model::LibraColour => 3,
289 _ => 1,
290 }
291 }
292
293 pub fn frontlight_kind(&self) -> FrontlightKind {
295 match self.model {
296 Model::ClaraHD
297 | Model::Forma
298 | Model::Forma32GB
299 | Model::LibraH2O
300 | Model::Sage
301 | Model::Libra2
302 | Model::Clara2E
303 | Model::Elipsa2E
304 | Model::ClaraBW
305 | Model::ClaraColour
306 | Model::LibraColour => FrontlightKind::Premixed,
307 Model::AuraONE | Model::AuraONELimEd | Model::AuraH2OEd2V1 | Model::AuraH2OEd2V2 => {
308 FrontlightKind::Natural
309 }
310 _ => FrontlightKind::Standard,
311 }
312 }
313
314 pub fn has_natural_light(&self) -> bool {
316 self.frontlight_kind() != FrontlightKind::Standard
317 }
318
319 pub fn has_lightsensor(&self) -> bool {
321 matches!(self.model, Model::AuraONE | Model::AuraONELimEd)
322 }
323
324 pub fn has_gyroscope(&self) -> bool {
326 matches!(
327 self.model,
328 Model::Forma
329 | Model::Forma32GB
330 | Model::LibraH2O
331 | Model::Elipsa
332 | Model::Sage
333 | Model::Libra2
334 | Model::Elipsa2E
335 | Model::LibraColour
336 )
337 }
338
339 pub fn has_page_turn_buttons(&self) -> bool {
341 matches!(
342 self.model,
343 Model::Forma
344 | Model::Forma32GB
345 | Model::LibraH2O
346 | Model::Sage
347 | Model::Libra2
348 | Model::LibraColour
349 )
350 }
351
352 pub fn has_power_cover(&self) -> bool {
354 matches!(self.model, Model::Sage)
355 }
356
357 pub fn has_removable_storage(&self) -> bool {
359 matches!(
360 self.model,
361 Model::AuraH2O
362 | Model::Aura
363 | Model::AuraHD
364 | Model::Glo
365 | Model::TouchAB
366 | Model::TouchC
367 )
368 }
369
370 pub fn should_invert_buttons(&self, rotation: i8) -> bool {
372 let sr = self.startup_rotation();
373 let (_, dir) = self.mirroring_scheme();
374
375 rotation == (4 + sr - dir) % 4 || rotation == (4 + sr - 2 * dir) % 4
376 }
377
378 pub fn orientation(&self, rotation: i8) -> Orientation {
380 if self.should_swap_axes(rotation) {
381 Orientation::Portrait
382 } else {
383 Orientation::Landscape
384 }
385 }
386
387 pub fn mark(&self) -> u8 {
389 match self.model {
390 Model::LibraColour => 13,
391 Model::ClaraBW | Model::ClaraColour => 12,
392 Model::Elipsa2E => 11,
393 Model::Clara2E => 10,
394 Model::Libra2 => 9,
395 Model::Sage | Model::Elipsa => 8,
396 Model::Nia
397 | Model::LibraH2O
398 | Model::Forma32GB
399 | Model::Forma
400 | Model::ClaraHD
401 | Model::AuraH2OEd2V2
402 | Model::AuraEd2V2 => 7,
403 Model::AuraH2OEd2V1
404 | Model::AuraEd2V1
405 | Model::AuraONELimEd
406 | Model::AuraONE
407 | Model::Touch2
408 | Model::GloHD => 6,
409 Model::AuraH2O | Model::Aura => 5,
410 Model::AuraHD | Model::Mini | Model::Glo | Model::TouchC => 4,
411 Model::TouchAB => 3,
412 }
413 }
414
415 pub fn should_mirror_axes(&self, rotation: i8) -> (bool, bool) {
417 let (mxy, dir) = self.mirroring_scheme();
418 let mx = (4 + (mxy + dir)) % 4;
419 let my = (4 + (mxy - dir)) % 4;
420 let mirror_x = mxy == rotation || mx == rotation;
421 let mirror_y = mxy == rotation || my == rotation;
422 (mirror_x, mirror_y)
423 }
424
425 pub fn mirroring_scheme(&self) -> (i8, i8) {
427 match self.model {
428 Model::AuraH2OEd2V1 | Model::LibraH2O | Model::Libra2 => (3, 1),
429 Model::Sage => (0, 1),
430 Model::AuraH2OEd2V2 => (0, -1),
431 Model::Forma | Model::Forma32GB => (2, -1),
432 _ => (2, 1),
433 }
434 }
435
436 pub fn should_swap_axes(&self, rotation: i8) -> bool {
438 rotation % 2 == self.swapping_scheme()
439 }
440
441 fn swapping_scheme(&self) -> i8 {
443 match self.model {
444 Model::LibraH2O => 0,
445 _ => 1,
446 }
447 }
448
449 pub fn startup_rotation(&self) -> i8 {
451 match self.model {
452 Model::LibraH2O => 0,
453 Model::AuraH2OEd2V1
454 | Model::Forma
455 | Model::Forma32GB
456 | Model::Sage
457 | Model::Libra2
458 | Model::Elipsa2E
459 | Model::LibraColour => 1,
460 _ => 3,
461 }
462 }
463
464 pub fn to_canonical(&self, n: i8) -> i8 {
466 let (_, dir) = self.mirroring_scheme();
467 (4 + dir * (n - self.startup_rotation())) % 4
468 }
469
470 pub fn from_canonical(&self, n: i8) -> i8 {
472 let (_, dir) = self.mirroring_scheme();
473 (self.startup_rotation() + (4 + dir * n) % 4) % 4
474 }
475
476 pub fn transformed_rotation(&self, n: i8) -> i8 {
478 match self.model {
479 Model::AuraHD | Model::AuraH2O => n ^ 2,
480 Model::AuraH2OEd2V2 | Model::Forma | Model::Forma32GB => (4 - n) % 4,
481 _ => n,
482 }
483 }
484
485 pub fn transformed_gyroscope_rotation(&self, n: i8) -> i8 {
487 match self.model {
488 Model::LibraH2O => n ^ 1,
489 Model::Libra2 | Model::Sage | Model::Elipsa2E | Model::LibraColour => (6 - n) % 4,
490 Model::Elipsa => (4 - n) % 4,
491 _ => n,
492 }
493 }
494}
495
496lazy_static! {
497 pub static ref CURRENT_DEVICE: Device = {
501 let product = env::var("PRODUCT").unwrap_or_default();
502 let model_number = env::var("MODEL_NUMBER").unwrap_or_default();
503
504 Device::new(&product, &model_number)
505 };
506}
507
508#[cfg(test)]
509mod tests {
510 use super::Device;
511
512 #[test]
513 fn test_device_canonical_rotation() {
514 let forma = Device::new("frost", "377");
515 let aura_one = Device::new("daylight", "373");
516 for n in 0..4 {
517 assert_eq!(forma.from_canonical(forma.to_canonical(n)), n);
518 }
519 assert_eq!(aura_one.from_canonical(0), aura_one.startup_rotation());
520 assert_eq!(
521 forma.from_canonical(1) - forma.from_canonical(0),
522 aura_one.from_canonical(2) - aura_one.from_canonical(3)
523 );
524 }
525}