gui_server_api/
lib.rs

1// SPDX-FileCopyrightText: 2024 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use {
5    num_derive::FromPrimitive,
6    num_traits::FromPrimitive,
7    server::{AsScalar, CheckedConn, CheckedPermissions, FromScalar, MessageAllowed},
8    xous::{MemoryRange, CID, PID, SID},
9};
10
11pub mod consts;
12pub mod error;
13pub mod msg;
14pub mod navigation;
15#[cfg(not(keyos))]
16pub mod simulator;
17pub mod touch;
18
19pub use error::GuiServerError;
20
21#[macro_export]
22macro_rules! use_api {
23    ($gui:path, $server:path) => {
24        mod gui_permissions {
25            use gui_server_api::msg::*;
26            pub use $gui as gui_server_api;
27            use $server as server;
28            #[derive(Clone, Default, server::Permissions)]
29            #[server_name = "os/gui-server"]
30            pub struct GuiPermissions;
31        }
32        type GuiApi = gui_permissions::gui_server_api::GuiApi<gui_permissions::GuiPermissions>;
33        type GuiApiLight = gui_permissions::gui_server_api::GuiApiLight<gui_permissions::GuiPermissions>;
34    };
35    () => {
36        gui_server_api::use_api!(gui_server_api, server);
37    };
38}
39
40pub type AppName = String;
41
42#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
43pub struct RegisterApp {
44    pub cid: CID,
45    pub name: AppName,
46    pub height: usize,
47}
48
49#[derive(Debug, Copy, Clone, FromPrimitive, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Default)]
50pub enum ModalStyle {
51    /// A regular modal card that slides up from the bottom of the screen.
52    /// The user can drag it and dismiss it by dragging or clicking away.
53    #[default]
54    SlideUpDraggablePopup = 0,
55
56    /// A modal card that slides up from the bottom of the screen.
57    /// The user can't drag it and dismiss it by clicking away.
58    SlideUpFixedPopup,
59
60    /// A modal card that slides up from the bottom of the screen and takes the entire screen.
61    SlideUpFullscreen,
62
63    /// A modal that appears instantly with no animation.
64    Instant,
65}
66
67/// Reduced GUI API, usable by non-gui daemons
68#[derive(Clone, Debug, Default)]
69pub struct GuiApiLight<P: CheckedPermissions> {
70    conn: CheckedConn<P>,
71}
72
73/// Full GUI API, usable by GUI apps
74#[derive(Debug)]
75pub struct GuiApi<P: CheckedPermissions> {
76    inner: GuiApiLight<P>,
77    cid_self: CID,
78    sid: SID,
79}
80
81impl<P: CheckedPermissions> GuiApiLight<P> {
82    /// Blocking connect: waits until gui-server has registered its name. gui-server is a
83    /// mandatory system service, so callers wait for it instead of timing out and failing.
84    pub fn connect() -> Self { Self { conn: CheckedConn::default() } }
85
86    /// Switches the focus to the app window of the given PID and the app zoom-in start position.
87    /// Used by the app launcher, app switcher, and usb-debug protocol.
88    pub fn switch_to(&self, next_pid: PID, x: usize, y: usize) -> Result<(), GuiServerError>
89    where
90        P: MessageAllowed<msg::SwitchTo>,
91    {
92        self.conn.try_send_scalar(msg::SwitchTo { next_pid: next_pid.get() as usize, x, y })?;
93        Ok(())
94    }
95
96    /// Switches the focus to the launcher app window. Used by apps.
97    pub fn switch_to_launcher(&self) -> Result<bool, GuiServerError>
98    where
99        P: MessageAllowed<msg::SwitchToLauncher>,
100    {
101        Ok(self.conn.try_send_blocking_scalar(msg::SwitchToLauncher)?)
102    }
103
104    pub fn is_locked(&self) -> Result<bool, GuiServerError>
105    where
106        P: MessageAllowed<msg::IsLocked>,
107    {
108        Ok(self.conn.try_send_blocking_scalar(msg::IsLocked)?)
109    }
110
111    pub fn shutdown(&self) -> Result<(), GuiServerError>
112    where
113        P: MessageAllowed<msg::Shutdown>,
114    {
115        Ok(self.conn.try_send_blocking_scalar(msg::Shutdown { reboot: false })?)
116    }
117
118    pub fn reboot(&self) -> Result<(), GuiServerError>
119    where
120        P: MessageAllowed<msg::Shutdown>,
121    {
122        Ok(self.conn.try_send_blocking_scalar(msg::Shutdown { reboot: true })?)
123    }
124
125    /// Closes the app window of the given PID.
126    /// Used by the launcher, switcher, and usb-debug protocol to gracefully close apps.
127    pub fn close_app(&self, pid: PID) -> Result<(), GuiServerError>
128    where
129        P: MessageAllowed<msg::CloseApp>,
130    {
131        Ok(self.conn.try_send_blocking_scalar(msg::CloseApp { pid: pid.get() as usize })??)
132    }
133
134    /// Captures the current composited screen as raw pixel data.
135    /// Returns a `DropDeallocate` of length `FB_SIZE` (SCREEN_WIDTH * SCREEN_HEIGHT * 4)
136    /// that auto-unmaps on drop. Dereferences to `MemoryRange` / `&[u8]`.
137    pub fn capture_screen(&self) -> Result<xous::DropDeallocate, GuiServerError>
138    where
139        P: MessageAllowed<msg::CaptureScreen>,
140    {
141        let mem = xous::map_memory(None, None, consts::FB_SIZE, xous::MemoryFlags::W)?;
142        self.conn.lend_mut(msg::CaptureScreen(mem));
143        Ok(xous::DropDeallocate::new(mem))
144    }
145
146    /// Injects a touch event as if it came from the hardware touch controller.
147    pub fn inject_touch(&self, touch: touch::Touch) -> Result<(), GuiServerError>
148    where
149        P: MessageAllowed<msg::InjectTouch>,
150    {
151        self.conn.try_send_scalar(msg::InjectTouch(touch))?;
152        Ok(())
153    }
154
155    /// Injects a key press or release event into the active app.
156    pub fn inject_key(&self, is_pressed: bool, key: Key) -> Result<(), GuiServerError>
157    where
158        P: MessageAllowed<msg::InjectKey>,
159    {
160        self.conn.try_send_scalar(msg::InjectKey { is_pressed, key })?;
161        Ok(())
162    }
163
164    /// Injects a power button press or release into gui-server's power-button state machine.
165    pub fn inject_power_button(&self, is_pressed: bool) -> Result<(), GuiServerError>
166    where
167        P: MessageAllowed<msg::InjectPowerButton>,
168    {
169        self.conn.try_send_scalar(msg::InjectPowerButton(is_pressed))?;
170        Ok(())
171    }
172
173    pub fn update_kiosk_policy(&self, policy: msg::UpdateKioskPolicy) -> Result<(), GuiServerError>
174    where
175        P: MessageAllowed<msg::UpdateKioskPolicy>,
176    {
177        self.conn.try_send_scalar(policy)?;
178        Ok(())
179    }
180}
181
182impl<P: CheckedPermissions> GuiApi<P> {
183    /// Registers an ordinary app window.
184    pub fn register(name: &str, height: usize) -> Result<Self, GuiServerError>
185    where
186        P: MessageAllowed<msg::RegisterAppMessage>,
187    {
188        let (api, cid) = Self::register_inner()?;
189        api.inner.conn.send_blocking_archive(msg::RegisterAppMessage(RegisterApp {
190            cid,
191            name: name.into(),
192            height,
193        }))?;
194        Ok(api)
195    }
196
197    /// Registers as the control center, which gui-server tracks as a dedicated
198    /// overlay window rather than an ordinary app.
199    pub fn register_control_center(height: usize) -> Result<Self, GuiServerError>
200    where
201        P: MessageAllowed<msg::RegisterControlCenter>,
202    {
203        let (api, cid) = Self::register_inner()?;
204        api.inner.conn.send_blocking_archive(msg::RegisterControlCenter { cid, height })?;
205        Ok(api)
206    }
207
208    /// Registers as the keyboard, which gui-server tracks as a dedicated overlay
209    /// window rather than an ordinary app.
210    pub fn register_keyboard(height: usize) -> Result<Self, GuiServerError>
211    where
212        P: MessageAllowed<msg::RegisterKeyboard>,
213    {
214        let (api, cid) = Self::register_inner()?;
215        api.inner.conn.send_blocking_archive(msg::RegisterKeyboard { cid, height })?;
216        Ok(api)
217    }
218
219    /// Claims a privileged role, then registers an ordinary app window. The role is
220    /// granted per message type, so an app can only claim a role its manifest permits.
221    pub fn register_with_role<M>(name: &str, height: usize) -> Result<Self, GuiServerError>
222    where
223        M: msg::RoleClaim,
224        P: MessageAllowed<msg::RegisterAppMessage> + MessageAllowed<M>,
225    {
226        let (api, cid) = Self::register_inner()?;
227        api.inner.conn.send_blocking_scalar(M::default());
228        api.inner.conn.send_blocking_archive(msg::RegisterAppMessage(RegisterApp {
229            cid,
230            name: name.into(),
231            height,
232        }))?;
233        Ok(api)
234    }
235
236    fn register_inner() -> Result<(Self, CID), GuiServerError> {
237        let sid = xous::create_server()?;
238        let cid_self = xous::connect(sid)?;
239        let inner = GuiApiLight::connect();
240        let api = Self { inner, sid, cid_self };
241        let gui_server_pid = api.inner.conn.get_remote_pid();
242
243        let gui_server_cid = xous::connect_for_process(gui_server_pid, api.sid)?;
244        xous::allow_messages_on_connection(gui_server_pid, gui_server_cid, 0..64)?;
245
246        Ok((api, gui_server_cid))
247    }
248
249    pub fn sid(&self) -> SID { self.sid }
250
251    /// Submit a frame for display.
252    pub fn submit_frame(&self, buffer: MemoryRange) -> Result<(), GuiServerError>
253    where
254        P: MessageAllowed<msg::SubmitFrame>,
255    {
256        Ok(self.conn.try_send_move(msg::SubmitFrame { buffer })?)
257    }
258
259    pub fn show_camera(&self, y_pos: u16) -> Result<(), GuiServerError>
260    where
261        P: MessageAllowed<msg::ShowCamera>,
262    {
263        self.conn.try_send_scalar(msg::ShowCamera { y_pos })?;
264        Ok(())
265    }
266
267    pub fn hide_camera(&self) -> Result<(), GuiServerError>
268    where
269        P: MessageAllowed<msg::HideCamera>,
270    {
271        self.conn.try_send_scalar(msg::HideCamera)?;
272        Ok(())
273    }
274
275    pub fn update_keyboard(&self, msg: msg::UpdateKeyboard) -> Result<(), GuiServerError>
276    where
277        P: MessageAllowed<msg::UpdateKeyboard>,
278    {
279        self.conn.try_send_archive(msg)?;
280        Ok(())
281    }
282
283    pub fn hide_keyboard(&self) -> Result<(), GuiServerError>
284    where
285        P: MessageAllowed<msg::HideKeyboard>,
286    {
287        self.conn.try_send_scalar(msg::HideKeyboard)?;
288        Ok(())
289    }
290
291    pub fn notify_login_success(&self) -> Result<(), GuiServerError>
292    where
293        P: MessageAllowed<msg::LoginSuccess>,
294    {
295        self.conn.try_send_scalar(msg::LoginSuccess)?;
296        Ok(())
297    }
298
299    pub fn wake_event_loop(&self) {
300        let msg = xous::Message::new_scalar(InputMessage::Noop as usize, 0, 0, 0, 0);
301        if let Err(e) = xous::send_message(self.cid_self, msg) {
302            log::error!("Failed to send wake event to self: {e:?}");
303        }
304    }
305
306    pub fn request_redraw(&self) -> Result<(), GuiServerError>
307    where
308        P: MessageAllowed<msg::RequestRedraw>,
309    {
310        self.conn.try_send_scalar(msg::RequestRedraw)?;
311        Ok(())
312    }
313
314    pub fn try_receive_input(&self) -> Option<(InputMessage, xous::MessageEnvelope)> {
315        if let Ok(Some(msg)) = xous::try_receive_message(self.sid) {
316            let opcode = FromPrimitive::from_usize(msg.body.id());
317            return opcode.map(|opcode| (opcode, msg));
318        }
319
320        None
321    }
322
323    pub fn receive_input(&self) -> Result<(InputMessage, xous::MessageEnvelope), GuiServerError> {
324        xous::receive_message(self.sid)
325            .map(|msg| {
326                let opcode = FromPrimitive::from_usize(msg.body.id());
327                (opcode.expect("input opcode"), msg)
328            })
329            .map_err(Into::into)
330    }
331
332    pub fn key_pressed(&self, key: Key) -> Result<(), GuiServerError>
333    where
334        P: MessageAllowed<msg::KeyPressed>,
335    {
336        self.conn.try_send_scalar(msg::KeyPressed(Some(key)))?;
337        Ok(())
338    }
339
340    pub fn key_released(&self, key: Key) -> Result<(), GuiServerError>
341    where
342        P: MessageAllowed<msg::KeyReleased>,
343    {
344        self.conn.try_send_scalar(msg::KeyReleased(Some(key)))?;
345        Ok(())
346    }
347
348    pub fn animate_next_frame(&self, animation_kind: NextFrameAnimationKind) -> Result<(), GuiServerError>
349    where
350        P: MessageAllowed<msg::AnimateNextFrame>,
351    {
352        self.conn.try_send_scalar(msg::AnimateNextFrame { animation_kind })?;
353        Ok(())
354    }
355}
356
357impl<P: CheckedPermissions> std::ops::Deref for GuiApi<P> {
358    type Target = GuiApiLight<P>;
359
360    fn deref(&self) -> &Self::Target { &self.inner }
361}
362
363#[derive(Debug, PartialEq, num_derive::FromPrimitive, num_derive::ToPrimitive, Copy, Clone)]
364pub enum InputMessage {
365    Touch = 0,
366    KeyPress,
367    KeyRelease,
368
369    /// Another app has navigated to this app, and the app is now in modal focus.
370    /// This input message is a notification to check the `GuiApi` for a navigation event.
371    NavigationFocused,
372
373    /// The app is being navigated away from and is no longer in modal focus.
374    NavigationCancelled,
375
376    /// The apps that block on input can unblock themselves by sending this message to themselves.
377    Noop,
378
379    /// The app is brought into the foreground.
380    Visible,
381
382    /// The app is getting minimized and hidden in the background.
383    Hidden,
384
385    /// A new framebuffer the app can draw to.
386    /// Can be the same as a previous buffer or a completely new one.
387    FrameBuffer,
388
389    Custom1,
390    Custom2,
391    Custom3,
392    Custom4,
393
394    /// The app should exit gracefully after receiving this.
395    CloseRequested,
396
397    /// Mouse/trackpad scroll in the emulator (hosted mode only).
398    /// Scalar args: arg1 = x (physical px), arg2 = y (physical px),
399    ///              arg3 = delta_x (f32 bits), arg4 = delta_y (f32 bits).
400    Scroll,
401}
402
403#[derive(Debug, Copy, Clone)]
404pub enum Key {
405    Char(usize),
406    Backspace,
407    Delete,
408    CursorLeft,
409    CursorRight,
410    Enter,
411    Tab,
412}
413
414impl server::AsScalar<2> for Key {
415    fn as_scalar(&self) -> [u32; 2] {
416        match self {
417            Key::Char(c) => [0, *c as _],
418            Key::Backspace => [1, 0],
419            Key::Delete => [2, 0],
420            Key::CursorLeft => [3, 0],
421            Key::CursorRight => [4, 0],
422            Key::Enter => [5, 0],
423            Key::Tab => [6, 0],
424        }
425    }
426}
427
428impl server::FromScalar<2> for Key {
429    fn from_scalar(value: [u32; 2]) -> Self {
430        match value[0] {
431            1 => Key::Backspace,
432            2 => Key::Delete,
433            3 => Key::CursorLeft,
434            4 => Key::CursorRight,
435            5 => Key::Enter,
436            6 => Key::Tab,
437            _ => Key::Char(value[1] as _),
438        }
439    }
440}
441
442impl<P: CheckedPermissions> Drop for GuiApi<P> {
443    fn drop(&mut self) {
444        if let Err(e) = xous::destroy_server(self.sid) {
445            log::error!("Error destroying gui api event server: {e:?}");
446        }
447    }
448}
449
450#[derive(Debug, Copy, Clone, FromPrimitive, Default)]
451pub enum NextFrameAnimationKind {
452    #[default]
453    SlideInLeft = 0,
454    SlideInRight,
455    SlideOutLeft,
456    SlideOutRight,
457}
458
459#[derive(
460    Debug,
461    Copy,
462    Clone,
463    FromPrimitive,
464    Default,
465    PartialEq,
466    Eq,
467    rkyv::Archive,
468    rkyv::Serialize,
469    rkyv::Deserialize,
470)]
471#[rkyv(derive(Debug))]
472pub enum KeyboardKind {
473    #[default]
474    Alphanumeric = 0,
475    Password,
476    Numbers,
477    Decimal,
478    Email,
479}
480
481impl FromScalar<1> for KeyboardKind {
482    fn from_scalar([value]: [u32; 1]) -> Self { Self::from_u32(value).unwrap_or_default() }
483}
484
485impl AsScalar<1> for KeyboardKind {
486    fn as_scalar(&self) -> [u32; 1] { [*self as u32] }
487}
488
489impl From<&ArchivedKeyboardKind> for KeyboardKind {
490    fn from(archived: &ArchivedKeyboardKind) -> Self {
491        rkyv::deserialize::<_, rkyv::rancor::Error>(archived).unwrap()
492    }
493}