Skip to main content
KeyOS API Reference

app_manager/
lib.rs

1// SPDX-FileCopyrightText: 2025 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3pub use error::*;
4pub use messages::*;
5
6pub mod error;
7pub mod messages;
8
9use server::{CheckedConn, CheckedPermissions, MessageAllowed};
10use xous::{AppId, PID};
11
12/// The sideload bundle directory, relative to the system volume root. Every sideloaded app,
13/// Flux children included, installs into `<SIDELOADED_APPS_DIR>/<hex app id>`.
14pub const SIDELOADED_APPS_DIR: &str = "keyos/sideloaded-apps";
15
16#[macro_export]
17macro_rules! use_api {
18    () => {
19        mod app_manager_permissions {
20            use app_manager::messages::*;
21            #[derive(Clone, Default, server::Permissions)]
22            #[server_name = "os/app-manager"]
23            pub struct AppManagerPermissions;
24        }
25        type AppManagerApi = app_manager::AppManagerApi<app_manager_permissions::AppManagerPermissions>;
26    };
27}
28
29#[derive(Clone, Default)]
30pub struct AppManagerApi<P: CheckedPermissions>(pub(crate) CheckedConn<P>);
31
32impl<P: CheckedPermissions> AppManagerApi<P> {
33    pub fn launch_app_blocking(&self, app_id: &AppId) -> Result<PID, AppManagerError>
34    where
35        P: MessageAllowed<LaunchAppBlocking>,
36    {
37        self.0
38            .try_send_blocking_scalar(LaunchAppBlocking(*app_id))
39            .map_err(|_| AppManagerError::InternalError)?
40    }
41
42    pub fn launch_app(&self, app_id: &AppId) -> Result<(), xous::Error>
43    where
44        P: MessageAllowed<LaunchApp>,
45    {
46        self.0.try_send_scalar(LaunchApp(*app_id))?;
47        Ok(())
48    }
49
50    pub fn refresh_installed_apps(&self) -> Result<(), AppManagerError>
51    where
52        P: MessageAllowed<RefreshInstalledApps>,
53    {
54        self.0.try_send_blocking_scalar(RefreshInstalledApps).map_err(|_| AppManagerError::InternalError)?
55    }
56
57    /// Rescan after an app bundle has been completely replaced, forcing subscribers to refresh
58    /// that app's resources even when its manifest is unchanged
59    pub fn refresh_installed_app(&self, app_id: AppId) -> Result<(), AppManagerError>
60    where
61        P: MessageAllowed<RefreshInstalledApp>,
62    {
63        self.0
64            .try_send_blocking_scalar(RefreshInstalledApp(app_id))
65            .map_err(|_| AppManagerError::InternalError)?
66    }
67
68    pub fn app_name_by_app_id(&self, id: &AppId, locale: &str) -> Option<String>
69    where
70        P: MessageAllowed<GetAppName>,
71    {
72        self.0.send_blocking_archive(GetAppName::new_by_app_id(id, locale))
73    }
74
75    pub fn app_name_by_pid(&self, pid: PID, locale: &str) -> Option<String>
76    where
77        P: MessageAllowed<GetAppName>,
78    {
79        self.0.send_blocking_archive(GetAppName::new_by_pid(pid, locale))
80    }
81
82    pub fn get_qr_match_rules(&self) -> Vec<AppQrMatchRules>
83    where
84        P: MessageAllowed<GetQrMatchRules>,
85    {
86        self.0.send_blocking_archive(GetQrMatchRules { app_ids: Vec::new() })
87    }
88
89    /// List installed apps, optionally narrowed by `filter`. Pass `AppFilter::default()`
90    /// for everything, `AppFilter::sideloaded_only()` for sideloaded apps, etc.
91    pub fn list_apps(&self, locale: &str, filter: AppFilter) -> Vec<InstalledAppInfo>
92    where
93        P: MessageAllowed<ListApps>,
94    {
95        self.0.send_blocking_archive(ListApps { locale: locale.to_string(), filter })
96    }
97
98    pub fn get_app_icon(&self, app_id: &str, variant: IconVariant) -> Option<Vec<u8>>
99    where
100        P: MessageAllowed<GetAppIcon>,
101    {
102        self.0.send_blocking_archive(GetAppIcon { app_id: app_id.to_string(), variant })
103    }
104
105    pub fn set_app_permission_grant(
106        &self,
107        app_id: &str,
108        subgroup: &str,
109        decision: PermissionGrantDecision,
110    ) -> SetAppPermissionGrantResult
111    where
112        P: MessageAllowed<SetAppPermissionGrant>,
113    {
114        self.0.send_blocking_archive(SetAppPermissionGrant {
115            app_id: app_id.to_string(),
116            subgroup: subgroup.to_string(),
117            decision,
118        })
119    }
120
121    pub fn get_permission_request_info(
122        &self,
123        sender_app_id: [u8; 16],
124        server_sid: [u32; 4],
125        message_id: usize,
126        locale: &str,
127    ) -> PermissionRequestInfoResult
128    where
129        P: MessageAllowed<GetPermissionRequestInfo>,
130    {
131        self.0.send_blocking_archive(GetPermissionRequestInfo {
132            sender_app_id,
133            server_sid,
134            message_id,
135            locale: locale.to_string(),
136        })
137    }
138
139    pub fn get_third_party_certificates(&self) -> Vec<ThirdPartyCertificateInfo>
140    where
141        P: MessageAllowed<GetThirdPartyCertificates>,
142    {
143        self.0.send_blocking_archive(GetThirdPartyCertificates)
144    }
145
146    pub fn preview_third_party_certificate(
147        &self,
148        certificate_pem: Vec<u8>,
149    ) -> Result<Result<ThirdPartyCertificateInfo, ThirdPartyCertificateError>, xous::Error>
150    where
151        P: MessageAllowed<PreviewThirdPartyCertificate>,
152    {
153        self.0.try_send_blocking_archive(PreviewThirdPartyCertificate { certificate_pem })
154    }
155
156    pub fn import_third_party_certificate(
157        &self,
158        certificate_pem: Vec<u8>,
159        expected_fingerprint: impl Into<String>,
160    ) -> Result<Result<ThirdPartyCertificateInfo, ThirdPartyCertificateError>, xous::Error>
161    where
162        P: MessageAllowed<ImportThirdPartyCertificate>,
163    {
164        self.0.try_send_blocking_archive(ImportThirdPartyCertificate {
165            certificate_pem,
166            expected_fingerprint: expected_fingerprint.into(),
167        })
168    }
169
170    pub fn remove_third_party_certificate(
171        &self,
172        fingerprint: impl Into<String>,
173        locale: &str,
174    ) -> Result<RemoveThirdPartyCertificateResult, xous::Error>
175    where
176        P: MessageAllowed<RemoveThirdPartyCertificate>,
177    {
178        self.0.try_send_blocking_archive(RemoveThirdPartyCertificate {
179            fingerprint: fingerprint.into(),
180            locale: locale.to_string(),
181        })
182    }
183
184    /// Sends a non-blocking app removal request
185    pub fn remove_app(&self, app_id: &AppId) -> Result<(), xous::Error>
186    where
187        P: MessageAllowed<RemoveApp>,
188    {
189        self.0.try_send_scalar(RemoveApp(*app_id))
190    }
191
192    pub fn install_app_archive(
193        &self,
194        path: impl Into<String>,
195        location: ArchiveLocation,
196        locale: &str,
197    ) -> Result<Result<InstallAppArchiveResult, InstallError>, xous::Error>
198    where
199        P: MessageAllowed<InstallAppArchive>,
200    {
201        self.0.try_send_blocking_archive(InstallAppArchive {
202            path: path.into(),
203            location,
204            locale: locale.to_string(),
205        })
206    }
207
208    /// Subscribe the calling server to app lifecycle events (launch/crash).
209    ///
210    /// The subscriber must implement `server::ArchiveEventHandler<AppEvent>`.
211    pub fn server_subscribe_app_events<S>(&self, context: &mut server::ServerContext<S>)
212    where
213        P: 'static,
214        P: MessageAllowed<SubscribeAppEvents>,
215        S: server::Server + server::ArchiveEventHandler<AppEvent>,
216    {
217        self.0.subscribe_archive_infallible(SubscribeAppEvents, context)
218    }
219}
220
221pub fn decode_app_id_str(id: &str) -> anyhow::Result<AppId> {
222    Ok(AppId(app_manifest::parse_app_id_bytes(id)?))
223}