gui_server_api/navigation/
qrscanner.rs

1// SPDX-FileCopyrightText: 2024-2025 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! QR code scanner navigation request and response formats.
5
6use app_manifest::QrPriority;
7use server::rkyv_with::WithAppId;
8use xous::AppId;
9
10/// Options for the QR Scanner navigation request.
11///
12/// Example with a left back arrow and a simple message:
13///
14/// ```rust,ignore
15/// # use navigation::api::qrscanner::{ScanQrOptions};
16/// let options = ScanQrOptions::default()
17///     .with_start_location(Location::External)
18///     .with_allowed_locations(AllowedLocations::specific(&[Location::External]))
19///     .with_allowed_extensions(AllowedExtensions::specific(&["bin"]));
20/// ```
21/// A single rule (and the first sub-rule that triggered it) that matched a scanned QR code.
22#[derive(Debug, Clone, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
23pub struct ScanQrMatchedRule {
24    pub rule_id: String,
25    pub priority: QrPriority,
26    /// The ID of the first sub-rule that matched — used for dispatch hints without bloating the
27    /// message with every matching sub-rule.
28    pub sub_rule_id: String,
29}
30
31/// One entry per app that has at least one matching rule for the scanned QR code.
32/// All matched rules for that app are collected here so the same app never appears
33/// more than once in the disambiguation list.
34#[derive(Debug, Clone, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
35pub struct ScanQrMatchingApp {
36    #[rkyv(with = WithAppId)]
37    pub id: AppId,
38    pub matched_rules: Vec<ScanQrMatchedRule>,
39}
40
41#[derive(Debug, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
42pub struct ScanQrOptions {
43    pub header_title: String,
44    pub header_left_icon: String,
45    pub header_left_text: String,
46    pub header_right_icon: String,
47    pub header_right_text: String,
48    pub message: String,
49    pub button_icon: String,
50    pub button_text: String,
51    pub request_matching_apps: bool,
52}
53
54impl Default for ScanQrOptions {
55    fn default() -> Self {
56        Self {
57            header_title: String::new(),
58            header_left_icon: String::from("chevron-left"),
59            header_left_text: String::new(),
60            header_right_icon: String::new(),
61            header_right_text: String::new(),
62            message: String::new(),
63            button_icon: String::new(),
64            button_text: String::new(),
65            request_matching_apps: false,
66        }
67    }
68}
69
70impl ScanQrOptions {
71    pub fn new() -> Self {
72        Self {
73            header_title: String::new(),
74            header_left_icon: String::new(),
75            header_left_text: String::new(),
76            header_right_icon: String::new(),
77            header_right_text: String::new(),
78            message: String::new(),
79            button_icon: String::new(),
80            button_text: String::new(),
81            request_matching_apps: false,
82        }
83    }
84
85    pub fn from_slice(data: &[u8]) -> Option<Self> {
86        let Ok(archived) = rkyv::access::<ArchivedScanQrOptions, rkyv::rancor::Error>(data) else {
87            return None;
88        };
89        rkyv::deserialize::<Self, rkyv::rancor::Error>(archived).ok()
90    }
91
92    pub fn serialize(&self) -> Vec<u8> { rkyv::to_bytes::<rkyv::rancor::Error>(self).unwrap().to_vec() }
93}
94
95#[derive(Debug, Clone, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
96pub struct MatchedQrResult {
97    pub scan_result: ScanQrResult,
98    pub matched_rules: Vec<ScanQrMatchedRule>,
99}
100
101impl MatchedQrResult {
102    pub fn from_slice(data: &[u8]) -> Option<Self> {
103        let Ok(archived) = rkyv::access::<ArchivedMatchedQrResult, rkyv::rancor::Error>(data) else {
104            return None;
105        };
106        rkyv::deserialize::<Self, rkyv::rancor::Error>(archived).ok()
107    }
108
109    pub fn serialize(&self) -> Vec<u8> { rkyv::to_bytes::<rkyv::rancor::Error>(self).unwrap().to_vec() }
110}
111
112#[derive(Debug, Clone, rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)]
113pub enum ScanQrResult {
114    Qr { data: Vec<u8>, matching_apps: Option<Vec<ScanQrMatchingApp>> },
115    Ur2 { ur_type: String, data: Vec<u8>, matching_apps: Option<Vec<ScanQrMatchingApp>> },
116    LeftClicked,
117    RightClicked,
118    ButtonClicked,
119}
120
121impl ScanQrResult {
122    pub fn new_qr(data: &[u8]) -> Self { Self::Qr { data: data.to_vec(), matching_apps: None } }
123
124    pub fn new_ur2(ur_type: String, data: &[u8]) -> Self {
125        Self::Ur2 { ur_type, data: data.to_vec(), matching_apps: None }
126    }
127
128    pub fn with_matching_apps(self, matching_apps: Vec<ScanQrMatchingApp>) -> Self {
129        match self {
130            Self::Qr { data, .. } => Self::Qr { data, matching_apps: Some(matching_apps) },
131            Self::Ur2 { ur_type, data, .. } => {
132                Self::Ur2 { ur_type, data, matching_apps: Some(matching_apps) }
133            }
134            other => other,
135        }
136    }
137
138    pub fn new_cancelled() -> Self { Self::LeftClicked }
139
140    pub fn from_slice(data: &[u8]) -> Option<Self> {
141        let Ok(archived) = rkyv::access::<ArchivedScanQrResult, rkyv::rancor::Error>(data) else {
142            return None;
143        };
144        rkyv::deserialize::<Self, rkyv::rancor::Error>(archived).ok()
145    }
146
147    pub fn serialize(&self) -> Vec<u8> { rkyv::to_bytes::<rkyv::rancor::Error>(self).unwrap().to_vec() }
148}