diff --git a/services/gam/src/modal.rs b/services/gam/src/modal.rs index 93cd63174..76369a872 100644 --- a/services/gam/src/modal.rs +++ b/services/gam/src/modal.rs @@ -47,6 +47,7 @@ General form for modals: - slider - left/right moves the slider, enter/select closes the modal */ use enum_dispatch::enum_dispatch; +use log_server::init; use xous::MessageEnvelope; use xous::send_message; @@ -788,9 +789,7 @@ impl ActionApi for Notification { } else { modal.canvas_width - modal.margin * 2 }; - log::info!("tw: {}", textwidth); let offset = (modal.canvas_width - textwidth) / 2; - log::info!("offset2: {}", offset); tv.bounds_computed = None; tv.bounds_hint = TextBounds::BoundingBox(Rectangle::new( Point::new(offset, at_height + modal.margin * 2), @@ -834,23 +833,197 @@ pub struct Slider { pub action_conn: xous::CID, pub action_opcode: u32, pub action_payload: u32, + pub is_progressbar: bool, + pub is_password: bool, + pub show_legend: bool, + pub units: xous_ipc::String::<8>, +} +impl Slider { + pub fn new(action_conn: xous::CID, action_opcode: u32, min: u32, max: u32, step: u32, units: Option<&str>, initial_setting: u32, is_progressbar: bool, show_legend: bool) -> Self { + let checked_units = if let Some(unit_str) = units { + if unit_str.len() < 8 { + String::<8>::from_str(unit_str) + } else { + log::error!("Unit string must be less than 8 *bytes* long (are you using unicode?), ignoring length {} string", unit_str.len()); + String::<8>::new() + } + } else { + String::<8>::new() // just populate with a blank string, easier than checking Some/None later on everywhere + }; + + Slider { + action_conn, + action_opcode, + is_password: false, + is_progressbar, + min, + max, + step, + action_payload: initial_setting, + units: checked_units, + show_legend, + } + } + 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 + // set because they can't achieve a high enough trust level. + self.is_password = true; + } + pub fn set_state(&mut self, state: u32) { + if state < self.min { + self.action_payload = self.min; + } else if state > self.max { + self.action_payload = self.max; + } else { + self.action_payload = state; + } + } } impl ActionApi for Slider { fn height(&self, glyph_height: i16, margin: i16) -> i16 { /* + margin min max <- glyph height -----O---------- <- glyph height - [ Okay ] <- glyph height + legend + margin */ - glyph_height * 3 + margin * 2 + if self.show_legend { + glyph_height * 3 + margin * 2 + } else { + glyph_height * 2 + margin * 2 + } } fn set_action_opcode(&mut self, op: u32) {self.action_opcode = op} + + fn redraw(&self, at_height: i16, modal: &Modal) { + let color = if self.is_password { + PixelColor::Light + } else { + PixelColor::Dark + }; + let fill_color = if self.is_password { + PixelColor::Dark + } else { + PixelColor::Light + }; + + // prime a textview with the correct general style parameters + let mut tv = TextView::new( + modal.canvas, + TextBounds::BoundingBox(Rectangle::new_coords(0, 0, 1, 1)) + ); + tv.ellipsis = true; + tv.style = modal.style; + tv.invert = self.is_password; + tv.draw_border= false; + tv.margin = Point::new(0, 0,); + tv.insertion = None; + + let maxwidth = (modal.canvas_width - modal.margin * 2) as u16; + if self.show_legend { + /* // min/max doesn't look good, leave it out for now + // render min + tv.bounds_computed = None; + tv.bounds_hint = TextBounds::GrowableFromTl( + Point::new(modal.margin, at_height + modal.margin), + maxwidth + ); + tv.text.clear(); + write!(tv, "{}{}", self.min, self.units.to_str()).unwrap(); + modal.gam.post_textview(&mut tv).expect("couldn't post tv"); + // render max + tv.bounds_computed = None; + tv.bounds_hint = TextBounds::GrowableFromBr( + Point::new(modal.canvas_width - modal.margin, at_height + modal.margin + modal.line_height), + maxwidth + ); + tv.text.clear(); + write!(tv, "{}{}", self.max, self.units.to_str()).unwrap(); + modal.gam.post_textview(&mut tv).expect("couldn't post tv"); + */ + // estimate width of current setting + tv.bounds_computed = None; + tv.bounds_hint = TextBounds::GrowableFromTl( + Point::new(0, 0), + maxwidth + ); + write!(tv, "{}{}", self.action_payload, self.units.to_str()).unwrap(); + modal.gam.bounds_compute_textview(&mut tv).expect("couldn't simulate text size"); + let textwidth = if let Some(bounds) = tv.bounds_computed { + bounds.br.x - bounds.tl.x + } else { + maxwidth as i16 + }; + let offset = (modal.canvas_width - textwidth) / 2; + // render current setting + tv.bounds_computed = None; + tv.bounds_hint = TextBounds::GrowableFromTl( + Point::new(offset, at_height + modal.margin + modal.line_height*2 + modal.margin), + maxwidth + ); + modal.gam.post_textview(&mut tv).expect("couldn't post tv"); + } + + // the actual slider + let outer_rect = Rectangle::new_with_style( + Point::new(modal.margin * 2, modal.margin + modal.line_height + at_height), + Point::new(modal.canvas_width - modal.margin * 2, modal.margin + modal.line_height * 2 + at_height), + DrawStyle::new(fill_color, color, 2) + ); + modal.gam.draw_rectangle(modal.canvas, outer_rect).expect("couldn't draw outer rectangle"); + let total_width = modal.canvas_width - modal.margin * 4; + let slider_point = (((modal.canvas_width - modal.margin * 4) as u32 * (self.action_payload - self.min)) / (self.max - self.min)) as i16; + let inner_rect = Rectangle::new_with_style( + Point::new(modal.margin * 2, modal.margin + modal.line_height + at_height), + Point::new(modal.margin * 2 + slider_point, modal.margin + modal.line_height * 2 + at_height), + DrawStyle::new(color, color, 1) + ); + modal.gam.draw_rectangle(modal.canvas, inner_rect).expect("couldn't draw inner rectangle"); + } + fn key_action(&mut self, k: char) -> (Option>, bool) { + log::trace!("key_action: {}", k); + if !self.is_progressbar { + match k { + '←' => { + if self.action_payload >= self.min + self.step { + self.action_payload -= self.step; + } else if self.action_payload >= self.min && self.action_payload < self.min + self.step { + self.action_payload = self.min + } + }, + '→' => { + if self.action_payload <= self.max - self.step { + self.action_payload += self.step; + } else if self.action_payload < self.max && self.action_payload > self.max - self.step { + self.action_payload = self.max + } + }, + '\u{0}' => { + // ignore null messages + } + '∴' | '\u{d}' => { + 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"); + return(None, true) + } + _ => { + // ignore all other messages + } + } + (None, false) + } else { + if k == '🛑' { // use the "stop" emoji as a signal that we should close the progress bar + (None, true) + } else { + (None, false) + } + } + } } - - - #[enum_dispatch] pub trait ActionApi { fn height(&self, glyph_height: i16, margin: i16) -> i16 {glyph_height + margin * 2} @@ -891,6 +1064,12 @@ pub struct Modal<'a> { pub style: GlyphStyle, pub helper_data: Option>, pub name: String::<128>, + + // optimize draw time + top_dirty: bool, + top_memoized_height: Option, + bot_dirty: bool, + bot_memoized_height: Option, } #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] @@ -1047,6 +1226,10 @@ impl<'a> Modal<'a> { style, helper_data: None, name: String::<128>::from_str(name), + top_dirty: true, + bot_dirty: true, + top_memoized_height: None, + bot_memoized_height: None, }; recompute_canvas(&mut modal, action, top_text, bot_text, style); modal @@ -1073,33 +1256,60 @@ impl<'a> Modal<'a> { xous::create_thread_3(crate::forwarding_thread, addr, size, offset).expect("couldn't spawn a helper thread"); } - pub fn redraw(&self) { + pub fn redraw(&mut self) { log::debug!("modal redraw"); let canvas_size = self.gam.get_canvas_bounds(self.canvas).unwrap(); + let do_redraw = self.top_dirty || self.bot_dirty; // draw the outer border - self.gam.draw_rounded_rectangle(self.canvas, - RoundedRectangle::new( - Rectangle::new_with_style(Point::new(0, 0), canvas_size, - DrawStyle::new(if self.inverted{PixelColor::Dark} else {PixelColor::Light}, PixelColor::Dark, 3) - ), 5 - )).unwrap(); + if do_redraw { + self.gam.draw_rounded_rectangle(self.canvas, + RoundedRectangle::new( + Rectangle::new_with_style(Point::new(0, 0), canvas_size, + DrawStyle::new(if self.inverted{PixelColor::Dark} else {PixelColor::Light}, PixelColor::Dark, 3) + ), 5 + )).unwrap(); + } let mut cur_height = self.margin; if let Some(mut tv) = self.top_text { - self.gam.post_textview(&mut tv).expect("couldn't draw text"); - if let Some(bounds) = tv.bounds_computed { - cur_height += bounds.br.y - bounds.tl.y; + if do_redraw { + self.gam.post_textview(&mut tv).expect("couldn't draw text"); + if let Some(bounds) = tv.bounds_computed { + cur_height += bounds.br.y - bounds.tl.y; + self.top_memoized_height = Some(bounds.br.y - bounds.tl.y); + } + self.top_dirty = false; + } else { + cur_height += self.top_memoized_height.expect("internal error: memoization didn't work correctly"); } + } else { + self.top_dirty = false; } + let action_height = self.action.height(self.line_height, self.margin); + if !do_redraw { + // the action area wasn't blanked, so blank it as prep for the action redraw + self.gam.draw_rectangle(self.canvas, + Rectangle::new_with_style(Point::new(0, cur_height), Point::new(canvas_size.x, cur_height + action_height), + DrawStyle::new(if self.inverted{PixelColor::Dark} else {PixelColor::Light}, PixelColor::Dark, 2) + )).unwrap(); + } self.action.redraw(cur_height, &self); - cur_height += self.action.height(self.line_height, self.margin); + cur_height += action_height; if let Some(mut tv) = self.bot_text { - self.gam.post_textview(&mut tv).expect("couldn't draw text"); - if let Some(bounds) = tv.bounds_computed { - cur_height += bounds.br.y - bounds.tl.y; + if do_redraw { + self.gam.post_textview(&mut tv).expect("couldn't draw text"); + if let Some(bounds) = tv.bounds_computed { + cur_height += bounds.br.y - bounds.tl.y; + self.bot_memoized_height = Some(bounds.br.y - bounds.tl.y); + } + self.bot_dirty = false; + } else { + cur_height += self.bot_memoized_height.expect("internal error: memoization didn't work correctly"); } + } else { + self.bot_dirty = false; } log::trace!("total height: {}", cur_height); self.gam.redraw().unwrap(); @@ -1139,11 +1349,19 @@ impl<'a> Modal<'a> { }; if remove_top { + self.top_dirty = true; self.top_text = None; } if remove_bot { + self.bot_dirty = true; self.bot_text = None; } + if update_top_text.is_some() { + self.top_dirty = true; + } + if update_bot_text.is_some() { + self.bot_dirty = true; + } let mut top_tv_temp = String::<3072>::new(); // size matches that used in TextView if let Some(top_text) = update_top_text { diff --git a/services/root-keys/src/implementation.rs b/services/root-keys/src/implementation.rs index dabce82cb..d2b116b69 100755 --- a/services/root-keys/src/implementation.rs +++ b/services/root-keys/src/implementation.rs @@ -318,7 +318,8 @@ impl RootKeys { } pub fn get_ux_password_type(&self) -> Option {self.cur_password_type} - pub fn is_initialized(&self) -> bool { + pub fn is_initialized(&mut self) -> bool { + self.keyrom.wfo(utra::keyrom::ADDRESS_ADDRESS, KeyRomLocations::CONFIG as u32); let config = self.keyrom.rf(utra::keyrom::DATA_DATA); if config & keyrom_config::INITIALIZED.ms(1) != 0 { true @@ -345,7 +346,18 @@ impl RootKeys { } pub fn do_key_init(&mut self) { - + // here: + // - generate signing private key (encrypted with update password) + // - generate rootkey (encrypted with boot password) + // - generate signing public key + // - set the init bit + // - sign the loader + // - sign the kernel + // - compute the patch set for the FPGA bitstream + // - do the patch (whatever that means - gotta deal with the AES key, HMAC etc.) + // - verify the FPGA image hmac + // - sign the FPGA image + // - get ready for a reboot } pub fn finish_key_init(&mut self) { diff --git a/services/root-keys/src/main.rs b/services/root-keys/src/main.rs index 89614f903..da2d61aaa 100644 --- a/services/root-keys/src/main.rs +++ b/services/root-keys/src/main.rs @@ -139,6 +139,10 @@ fn xmain() -> ! { }; let mut dismiss_modal_action = Notification::new(main_cid, Opcode::UxGutter.to_u32().unwrap()); dismiss_modal_action.set_is_password(true); + let mut progress_action = Slider::new(main_cid, Opcode::UxGutter.to_u32().unwrap(), + 0, 100, 10, Some("%"), 0, true, true + ); + progress_action.set_is_password(true); let mut rootkeys_modal = Modal::new( crate::api::ROOTKEY_MODAL_NAME, @@ -253,16 +257,29 @@ fn xmain() -> ! { PasswordType::Update => { keys.set_ux_password_type(None); // now show the init wait note... - dismiss_modal_action.set_action_opcode(Opcode::UxGutter.to_u32().unwrap()); rootkeys_modal.modify( - Some(ActionType::Notification(dismiss_modal_action)), + Some(ActionType::Slider(progress_action)), Some(t!("rootkeys.setup_wait", xous::LANG)), false, None, true, None); rootkeys_modal.activate(); - xous:: yield_slice(); // give some time to the GAM to render + xous::yield_slice(); // give some time to the GAM to render keys.do_key_init(); + log::info!("trying out progress bar"); + let ticktimer = ticktimer_server::Ticktimer::new().unwrap(); + for i in 1..11 { + log::info!("progress: {}", i); + progress_action.set_state(i * 10); + rootkeys_modal.modify( + Some(ActionType::Slider(progress_action)), + Some(t!("rootkeys.setup_wait", xous::LANG)), false, + None, true, None); + rootkeys_modal.redraw(); + rootkeys_modal.gam.redraw(); + xous::yield_slice(); + ticktimer.sleep_ms(3000); + } } } } else {