From 5243e8c2583c835129f680e984b4c3fde7eaafd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Mon, 14 Nov 2022 19:36:25 +0100 Subject: [PATCH 1/2] feat: user interaction-enabled slider This commit adds `modals::Modals::slider()` with the same interface as `modals::Modals::start_progress()`, with the striking difference that users can move the slider, and press the center keypad button to confirm the selection. Useful for objects like audio volume controls. --- services/gam/src/modal.rs | 8 ++++++ services/gam/src/modal/slider.rs | 12 ++++++-- services/modals/src/api.rs | 4 +++ services/modals/src/lib.rs | 27 ++++++++++++++++++ services/modals/src/main.rs | 48 +++++++++++++++++++++++++++++++- 5 files changed, 96 insertions(+), 3 deletions(-) diff --git a/services/gam/src/modal.rs b/services/gam/src/modal.rs index c5fef7a7a..54a08319b 100644 --- a/services/gam/src/modal.rs +++ b/services/gam/src/modal.rs @@ -115,6 +115,14 @@ impl TextEntryPayload { } } +#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub struct SliderPayload(pub u32); +impl SliderPayload { + pub fn new(value: u32) -> Self { + SliderPayload(value) + } +} + #[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct RadioButtonPayload(pub ItemName); // returns the name of the item corresponding to the radio button selection impl RadioButtonPayload { diff --git a/services/gam/src/modal/slider.rs b/services/gam/src/modal/slider.rs index 603c2231a..c28a29dcd 100644 --- a/services/gam/src/modal/slider.rs +++ b/services/gam/src/modal/slider.rs @@ -47,6 +47,11 @@ impl Slider { show_legend, } } + + pub fn set_is_progressbar(&mut self, setting: bool) { + self.is_progressbar = setting; + } + pub fn set_is_password(&mut self, setting: bool) { // this will cause text to be inverted. Untrusted entities can try to set this, // but the GAM should defeat this for dialog boxes outside of the trusted boot @@ -194,8 +199,11 @@ impl ActionApi for Slider { gam.relinquish_focus().unwrap(); xous::yield_slice(); - send_message(self.action_conn, - xous::Message::new_scalar(self.action_opcode as usize, self.action_payload as usize, 0, 0, 0)).expect("couldn't pass on action payload"); + let ret_payload = SliderPayload(self.action_payload); + + let buf = Buffer::into_buf(ret_payload).expect("couldn't convert message to payload"); + buf.send(self.action_conn, self.action_opcode).map(|_| ()).expect("couldn't send action message"); + return None; } _ => { diff --git a/services/modals/src/api.rs b/services/modals/src/api.rs index bb570e57c..1aa6feb4f 100644 --- a/services/modals/src/api.rs +++ b/services/modals/src/api.rs @@ -69,6 +69,8 @@ pub struct ManagedProgress { pub end_work: u32, /// current quanta of work. Used to int the bar, updates are just a scalar with the same value. pub current_work: u32, + /// can user interact with it? + pub user_interaction: bool, } /// This isn't a terribly useful notification -- it's basically read-only, no interactivity, @@ -98,6 +100,8 @@ pub(crate) enum Opcode { Bip39 = 31, // ---- note op number Bip39Input = 32, // ----- note op number Bip39Return = 33, // ----- note op number + SliderReturn = 34, + Slider = 35, /// display an image #[cfg(feature = "ditherpunk")] Image = 3, diff --git a/services/modals/src/lib.rs b/services/modals/src/lib.rs index 1c81dc4dc..06a6e033f 100644 --- a/services/modals/src/lib.rs +++ b/services/modals/src/lib.rs @@ -302,6 +302,7 @@ impl Modals { start_work: start, end_work: end, current_work: current, + user_interaction: false, }; let buf = Buffer::into_buf(spec).or(Err(xous::Error::InternalError))?; buf.lend(self.conn, Opcode::StartProgress.to_u32().unwrap()) @@ -309,6 +310,32 @@ impl Modals { Ok(()) } + pub fn slider( + &self, + title: &str, + start: u32, + end: u32, + current: u32, + ) -> Result { + self.lock(); + let spec = ManagedProgress { + token: self.token, + title: xous_ipc::String::from_str(title), + start_work: start, + end_work: end, + current_work: current, + user_interaction: true, + }; + let mut buf = Buffer::into_buf(spec).or(Err(xous::Error::InternalError))?; + buf.lend_mut(self.conn, Opcode::Slider.to_u32().unwrap()) + .or(Err(xous::Error::InternalError))?; + + let orig = buf.to_original::().unwrap(); + + self.unlock(); + Ok(orig.0) + } + /// note that this API is not atomically token-locked, so, someone could mess with the progress bar state /// but, progress updates are meant to be fast and frequent, and generally if a progress bar shows /// something whacky it's not going to affect a security outcome diff --git a/services/modals/src/main.rs b/services/modals/src/main.rs index acebddbf6..08b9a3fda 100644 --- a/services/modals/src/main.rs +++ b/services/modals/src/main.rs @@ -113,7 +113,7 @@ fn wrapped_main() -> ! { let mut fixed_items = Vec::::new(); let mut progress_action = Slider::new( renderer_cid, - Opcode::Gutter.to_u32().unwrap(), + Opcode::SliderReturn.to_u32().unwrap(), 0, 100, 1, @@ -332,6 +332,24 @@ fn wrapped_main() -> ! { ) .expect("couldn't initiate UX op"); } + Some(Opcode::Slider) => { + let spec = { + let buffer = + unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) }; + buffer.to_original::().unwrap() + }; + if spec.token != token_lock.unwrap_or(default_nonce) { + log::warn!("Attempt to access modals without a mutex lock. Ignoring."); + continue; + } + op = RendererState::RunProgress(spec); + dr = Some(msg); + send_message( + renderer_cid, + Message::new_scalar(Opcode::InitiateOp.to_usize().unwrap(), 0, 0, 0, 0), + ) + .expect("couldn't initiate UX op"); + } Some(Opcode::StopProgress) => msg_blocking_scalar_unpack!(msg, t0, t1, t2, t3, { let token = [t0 as u32, t1 as u32, t2 as u32, t3 as u32]; if token != token_lock.unwrap_or(default_nonce) { @@ -593,6 +611,7 @@ fn wrapped_main() -> ! { end_work ); progress_action.set_state(last_percentage); + progress_action.set_is_progressbar(!config.user_interaction); #[cfg(feature = "tts")] tts.tts_simple(config.title.as_str().unwrap()).unwrap(); renderer_modal.modify( @@ -603,6 +622,7 @@ fn wrapped_main() -> ! { true, Some(DEFAULT_STYLE), ); + renderer_modal.activate(); } RendererState::RunRadio(config) => { @@ -764,6 +784,32 @@ fn wrapped_main() -> ! { xous::return_scalar2(sender, 1, k).unwrap(); } }), + Some(Opcode::SliderReturn) => match op { + RendererState::RunProgress(_) => { + let buffer = + unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; + let item = buffer.to_original::().unwrap(); + + if let Some(mut origin) = dr.take() { + let mut response = unsafe { + Buffer::from_memory_message_mut( + origin.body.memory_message_mut().unwrap(), + ) + }; + + response.replace(item).unwrap(); + op = RendererState::None; + + token_lock = next_lock(&mut work_queue); + } else { + log::error!("Ux routine returned but no origin was recorded"); + panic!("Ux routine returned but no origin was recorded"); + } + }, + _ => { + log::warn!("got weird stuff on slider return, ignoring"); + } + }, Some(Opcode::TextEntryReturn) => match op { RendererState::RunText(_config) => { log::trace!("validating text entry modal"); From d57088a592637e883a676bad6f0edc7cfed3aa8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Wed, 16 Nov 2022 14:45:46 +0100 Subject: [PATCH 2/2] feat: add tests for slider --- services/modals/src/tests.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/services/modals/src/tests.rs b/services/modals/src/tests.rs index d7206528d..cf9119f6e 100644 --- a/services/modals/src/tests.rs +++ b/services/modals/src/tests.rs @@ -96,6 +96,20 @@ pub fn spawn_test() { } Err(e) => log::error!("couldn't get input: {:?}", e), } + + // 6. human interaction-enabled slider + log::info!("testing human interaction-enabled slider"); + let result = modals.slider( + "Human interaction-enabled slider!", + 0, + 100, + 50, + ).expect("slider test failed"); + + modals.show_notification(&format!("Slider value: {}", result), None) + .expect("cannot show slider result notification"); + + log::info!("slider test done"); } }); @@ -165,6 +179,9 @@ pub fn spawn_test() { modals.show_image(bm).expect("show image modal failed"); log::info!("image modal test done"); } + + // 6. test that human-interactable slider modal + log::info!("testing human interaction-enabled modal"); } }); }