app_manager/
messages.rs

1// SPDX-FileCopyrightText: 2025 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use num_traits::{FromPrimitive, ToPrimitive};
5use server::{rkyv_with::WithAppId, AsScalar, FromScalar};
6use xous::{AppId, PID};
7
8use crate::error::{AppManagerError, LaunchError};
9
10#[derive(Debug, server::Message)]
11#[response(Result<PID, AppManagerError>)]
12pub struct LaunchAppBlocking(pub AppId);
13
14#[derive(Debug, server::Message)]
15#[response(Result<(), AppManagerError>)]
16pub struct RefreshInstalledApps;
17
18impl AsScalar<3> for AppManagerError {
19    fn as_scalar(&self) -> [u32; 3] { [self.to_u32().unwrap(), 0, 0] }
20}
21
22impl FromScalar<3> for AppManagerError {
23    fn from_scalar([e, ..]: [u32; 3]) -> Self {
24        AppManagerError::from_u32(e).unwrap_or(AppManagerError::InternalError)
25    }
26}
27
28#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
29#[event(AppEvent)]
30pub struct SubscribeAppEvents;
31
32#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
33pub enum AppEvent {
34    AppLaunched {
35        #[rkyv(with = WithAppId)]
36        app_id: AppId,
37        pid: PID,
38        launched_by: PID,
39    },
40
41    AppCrashed {
42        #[rkyv(with = WithAppId)]
43        app_id: AppId,
44        pid: PID,
45        launched_by: PID,
46        exit_code: u32,
47        panic_message: Option<String>,
48    },
49
50    LaunchError {
51        #[rkyv(with = WithAppId)]
52        app_id: AppId,
53        error: LaunchError,
54    },
55}
56
57#[derive(Debug, server::Message)]
58pub struct LaunchApp(pub AppId);
59#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
60pub struct AppQrMatchRules {
61    #[rkyv(with = WithAppId)]
62    pub id: AppId,
63    pub rules_json: Vec<u8>,
64}
65
66#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
67pub struct InstalledAppPermissionGroup {
68    pub server: String,
69    pub messages: Vec<String>,
70}
71
72#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
73pub struct InstalledAppInfo {
74    pub app_id: String,
75    pub name: String,
76    /// On-disk path/reference for the app's bundled icon when it exists. Icon
77    /// bytes are fetched separately via [`GetAppIcon`] on device so a single
78    /// large icon (or many apps) cannot overflow the listing's IPC buffer.
79    pub bundled_icon_path: Option<String>,
80    pub publisher: String,
81    pub can_launch: bool,
82    pub can_remove: bool,
83    pub version: String,
84    pub size_bytes: u64,
85    pub description: String,
86    pub permissions: Vec<InstalledAppPermissionGroup>,
87}
88
89#[derive(
90    Debug, Clone, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize,
91)]
92pub struct ThirdPartyCertificateInfo {
93    pub name: String,
94    pub company: String,
95    pub contact_email: String,
96    pub support_url: String,
97    pub public_key: String,
98    #[serde(default)]
99    pub not_before_unix_seconds: Option<u64>,
100    #[serde(default)]
101    pub not_after_unix_seconds: Option<u64>,
102    pub serial_number: String,
103    pub issuer: String,
104    pub subject: String,
105    pub basic_constraints: String,
106    pub key_usage: String,
107    pub extended_key_usage: String,
108}
109
110#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
111pub enum ImportThirdPartyCertificateResult {
112    Imported(ThirdPartyCertificateInfo),
113    Invalid,
114}
115
116#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
117pub enum RemoveThirdPartyCertificateResult {
118    Removed,
119    NotFound,
120    AppRequiresKey(String),
121    InternalError,
122}
123
124#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
125pub enum RemoveInstalledAppResult {
126    Removed,
127    NotFound,
128    NotSideloaded,
129    Running,
130    InternalError,
131}
132
133#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
134#[response(Option<String>)]
135pub enum GetAppName {
136    ByAppId {
137        #[rkyv(with = WithAppId)]
138        id: AppId,
139        locale: String,
140    },
141
142    ByPid {
143        pid: PID,
144        locale: String,
145    },
146}
147
148impl GetAppName {
149    pub fn new_by_app_id(id: &AppId, locale: &str) -> Self {
150        Self::ByAppId { id: *id, locale: locale.to_string() }
151    }
152
153    pub fn new_by_pid(pid: PID, locale: &str) -> Self { Self::ByPid { pid, locale: locale.to_string() } }
154}
155
156#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
157#[response(Vec<AppQrMatchRules>)]
158pub struct GetQrMatchRules;
159
160/// Catalogue entry for a single installed app, returned by [`ListApps`].
161#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
162pub struct AppEntry {
163    pub app_id: String,
164    pub name: String,
165    /// `true` when the app lives under `/keyos/apps/gui-app-emu-flux/apps`.
166    pub is_flux: bool,
167}
168
169impl AppEntry {
170    pub fn new(app_id: &str, name: &str, is_flux: bool) -> Self {
171        AppEntry { app_id: app_id.to_string(), name: name.to_string(), is_flux }
172    }
173}
174
175/// Filter applied by [`ListApps`]. `None` on a field means "either"; `Some(v)`
176/// restricts the result to entries matching `v`.
177#[derive(Debug, Clone, Default, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
178pub struct AppFilter {
179    pub is_flux: Option<bool>,
180}
181
182impl AppFilter {
183    /// Convenience: filter to Flux apps only.
184    pub fn flux_only() -> Self { Self { is_flux: Some(true) } }
185
186    /// Convenience: filter to non-Flux apps only.
187    pub fn standard_only() -> Self { Self { is_flux: Some(false) } }
188}
189
190#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
191#[response(Vec<AppEntry>)]
192pub struct ListApps {
193    pub locale: String,
194    pub filter: AppFilter,
195}
196
197#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
198#[response(Vec<InstalledAppInfo>)]
199pub struct GetInstalledApps {
200    pub locale: String,
201}
202
203/// Fetch the raw bytes of a single app's bundled icon, keyed by its hex app id
204/// (as returned in [`InstalledAppInfo::app_id`]). Returns `None` when the app
205/// is unknown, has no bundled icon, or the icon cannot be read.
206#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
207#[response(Option<Vec<u8>>)]
208pub struct GetAppIcon {
209    pub app_id: String,
210}
211
212#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
213#[response(Vec<ThirdPartyCertificateInfo>)]
214pub struct GetThirdPartyCertificates;
215
216#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
217#[response(ImportThirdPartyCertificateResult)]
218pub struct ImportThirdPartyCertificate {
219    pub certificate_pem: Vec<u8>,
220}
221
222#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
223#[response(RemoveThirdPartyCertificateResult)]
224pub struct RemoveThirdPartyCertificate {
225    pub public_key: String,
226    pub locale: String,
227}
228
229#[derive(Debug, Clone, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
230#[response(RemoveInstalledAppResult)]
231pub struct RemoveInstalledApp {
232    #[rkyv(with = WithAppId)]
233    pub app_id: AppId,
234}