diff --git a/Cargo.lock b/Cargo.lock index 8632b42e3..b38d7cdd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,10 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blitstr" +version = "0.3.0" + [[package]] name = "blitstr" version = "0.3.0" @@ -256,7 +260,7 @@ name = "gam" version = "0.1.0" dependencies = [ "bitflags", - "blitstr", + "blitstr 0.3.0", "com", "graphics-server", "hash32", @@ -315,7 +319,7 @@ dependencies = [ name = "graphics-server" version = "0.1.0" dependencies = [ - "blitstr", + "blitstr 0.3.0", "keyboard", "log", "log-server", @@ -893,7 +897,7 @@ name = "shell" version = "0.1.0" dependencies = [ "bitflags", - "blitstr", + "blitstr 0.3.0 (git+https://github.com/betrusted-io/blitstr.git?rev=ea27e611d5ee165d8be894f9ca1c1130156c1931)", "com", "graphics-server", "heapless", diff --git a/services/gam/Cargo.toml b/services/gam/Cargo.toml index 00601950f..90da848a3 100644 --- a/services/gam/Cargo.toml +++ b/services/gam/Cargo.toml @@ -21,8 +21,8 @@ hash32-derive = "0.1.0" com = { path = "../com"} -# blitstr = { path = "../../../blitstr" } -blitstr = { git = "https://github.com/betrusted-io/blitstr.git", rev = "ea27e611d5ee165d8be894f9ca1c1130156c1931" } +blitstr = { path = "../../../blitstr" } +# blitstr = { git = "https://github.com/betrusted-io/blitstr.git", rev = "ea27e611d5ee165d8be894f9ca1c1130156c1931" } [target.'cfg(not(any(windows,unix)))'.dependencies] diff --git a/services/gam/src/api.rs b/services/gam/src/api.rs index ff3412232..636f9415d 100644 --- a/services/gam/src/api.rs +++ b/services/gam/src/api.rs @@ -120,6 +120,7 @@ impl<'a> TextView<'a> { } pub fn set_op(&mut self, op: TextOp) { self.operation = op; } pub fn get_op(&self) -> TextOp { self.operation } + pub fn get_canvas_gid(&self) -> Gid { self.canvas } } impl<'a> core::fmt::Debug for TextView<'a> { diff --git a/services/gam/src/canvas.rs b/services/gam/src/canvas.rs index 22a93d07c..d126f3945 100644 --- a/services/gam/src/canvas.rs +++ b/services/gam/src/canvas.rs @@ -5,16 +5,17 @@ use heapless::Vec; use heapless::consts::*; use graphics_server::{Rectangle, Point}; use xous::ipc::Sendable; +use log::{error, info}; use crate::api::*; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum CanvasState { // the initial state of every Canvas. Not drawable. Created, - // this state indicates the Canvas can be drawn, but has yet to be. + // this state indicates the Canvas can be drawn, and may or may not need to be flushed to the screen. DrawableDirty, - // this state indicates the Canvas has been drawn. + // this state indicates the Canvas has been flushed to the screen. DrawableDrawn, // indicates that the Canvas is not drawable, but needs to be defaced NotDrawableDirty, @@ -41,59 +42,6 @@ pub struct Canvas { // enables scroll/pan of objects within a region pan_offset: Point, } - -// we need the "screen" parameter so we can turn off drawing to canvases that are off-screen -pub fn recompute_canvases(mut canvases: BinaryHeap, screen: Rectangle) -> (BinaryHeap, FnvIndexMap) { - let mut higher_clipregions: BinaryHeap = BinaryHeap::new(); - - let mut trust_level: u8 = 255; - // canvases is a Max heap keyed on trust, so popping the elements off will return them sorted from most to least trusted - if let Some(c) = canvases.pop() { - let mut canvas = c.clone(); - - let mut drawable: bool = true; - let clip_region = canvas.clip_rect(); - if trust_level < canvas.trust_level() { - trust_level = canvas.trust_level(); - } - // note that this .iter() is *not* sorted by trust level, but all elements will be of greater than or equal to the current trust level - for ®ion in higher_clipregions.iter() { - // regions of the same trust level can draw over each other. Draw order is arbitrary. - if region.clip_rect().intersects(clip_region) && (region.trust_level() < trust_level) || - !region.clip_rect().intersects(screen) { - drawable = false; - } - } - match canvas.state() { - CanvasState::Created => - if drawable { canvas.set_state(CanvasState::DrawableDirty) } - else { canvas.set_state(CanvasState::NotDrawableDirty) }, - CanvasState::DrawableDirty | CanvasState::DrawableDrawn => - if !drawable { canvas.set_state(CanvasState::NotDrawableDirty) }, - CanvasState::NotDrawableDefaced | CanvasState::NotDrawableDirty => - if drawable { canvas.set_state(CanvasState::DrawableDirty) } - } - higher_clipregions.push(canvas).unwrap(); - } - - let mut map: FnvIndexMap = FnvIndexMap::new(); - for &c in higher_clipregions.iter() { - map.insert(c.gid(), c).unwrap(); - } - - (higher_clipregions, map) -} - -// Crate-level draw_textview() call for "local" services that can't use the lib.rs API -pub fn draw_textview(gam_cid: xous::CID, tv: &mut TextView) -> Result<(), xous::Error> { - let mut sendable_tv = Sendable::new(tv).expect("can't create sendable textview"); - sendable_tv.set_op(TextOp::Render); - sendable_tv.lend_mut(gam_cid, sendable_tv.get_op().into()).expect("draw_textview operation failure"); - - sendable_tv.set_op(TextOp::Nop); - Ok(()) -} - impl Canvas { pub fn new(clip_rect: Rectangle, trust_level: u8, trng_conn: xous::CID, pan_offset: Option) -> Result { @@ -120,7 +68,57 @@ impl Canvas { pub fn gid(&self) -> Gid { self.gid } pub fn trust_level(&self) -> u8 { self.trust_level } pub fn state(&self) -> CanvasState { self.state } - pub fn set_state(&mut self, state: CanvasState) { self.state = state; } + pub fn is_drawable(&self) -> bool { + if self.state == CanvasState::DrawableDirty || self.state == CanvasState::DrawableDrawn { + true + } else { + false + } + } + pub fn set_drawable(&mut self, drawable: bool) { + if drawable { + if self.state != CanvasState::DrawableDrawn { + self.state = CanvasState::DrawableDirty; + } + } else { + if self.state != CanvasState::NotDrawableDefaced { + self.state = CanvasState::NotDrawableDirty; + } + } + } + // call this after the screen has been flushed + pub fn do_flushed(&mut self) -> Result<(), xous::Error> { + if self.state == CanvasState::DrawableDirty || self.state == CanvasState::DrawableDrawn { + self.state = CanvasState::DrawableDrawn; + Ok(()) + } else if self.state == CanvasState::NotDrawableDefaced { + Ok(()) + } else { + error!("Canvas: flush happened before not drawable regions were defaced, or before initialized!"); + Err(xous::Error::UseBeforeInit) + } + } + pub fn do_drawn(&mut self) -> Result<(), xous::Error> { + if self.state == CanvasState::DrawableDirty || self.state == CanvasState::DrawableDrawn { + self.state = CanvasState::DrawableDirty; + Ok(()) + } else { + error!("Canvas: attempt to draw on regions that are not drawable, or not initialized!"); + Err(xous::Error::AccessDenied) + } + } + pub fn do_defaced(&mut self) -> Result<(), xous::Error> { + if self.state == CanvasState::NotDrawableDirty { + self.state = CanvasState::NotDrawableDefaced; + Ok(()) + } else if self.state == CanvasState::DrawableDirty || self.state == CanvasState::DrawableDrawn { + info!("Canvas: drawable region was defaced. Allowing it, but this could be a logic bug"); + Ok(()) + } else { + error!("Canvas: attempt to deface region already defaced, or not initialized!"); + Err(xous::Error::DoubleFree) + } + } } impl Ord for Canvas { @@ -139,3 +137,57 @@ impl PartialEq for Canvas { } } impl Eq for Canvas {} + + + +// we use the "screen" parameter to determine when we can turn off drawing to canvases that are off-screen +pub fn recompute_canvases(mut canvases: FnvIndexMap, screen: Rectangle) -> FnvIndexMap { + // first, sort canvases by trust_level. Canvas implements ord/eq based on the trust_level attribute + // so jush pushing it into a max binary heap does the trick. + let mut sorted_clipregions: BinaryHeap = BinaryHeap::new(); + for (_, &c) in canvases.iter() { + sorted_clipregions.push(c); + } + + // now, descend through trust levels and compute intersections, putting the updated drawable states into higher_clipregions + let mut higher_clipregions: BinaryHeap = BinaryHeap::new(); + let mut trust_level: u8 = 255; + // sorted_clipregions is a Max heap keyed on trust, so popping the elements off will return them sorted from most to least trusted + if let Some(c) = sorted_clipregions.pop() { + let mut canvas = c.clone(); + + let mut drawable: bool = true; + let clip_region = canvas.clip_rect(); + if trust_level < canvas.trust_level() { + trust_level = canvas.trust_level(); + } + // note that this .iter() is *not* sorted by trust level, but all elements will be of greater than or equal to the current trust level + for ®ion in higher_clipregions.iter() { + // regions of the same trust level can draw over each other. Draw order is arbitrary. + if region.clip_rect().intersects(clip_region) && (region.trust_level() < trust_level) || + !region.clip_rect().intersects(screen) { + drawable = false; + } + } + canvas.set_drawable(drawable); + higher_clipregions.push(canvas).unwrap(); + } + + // create a new index map out of the recomputed higher_clipregions + let mut map: FnvIndexMap = FnvIndexMap::new(); + for &c in higher_clipregions.iter() { + map.insert(c.gid(), c).unwrap(); + } + + map +} + +// Crate-level draw_textview() call for "local" services that can't use the lib.rs API +pub fn draw_textview(gam_cid: xous::CID, tv: &mut TextView) -> Result<(), xous::Error> { + let mut sendable_tv = Sendable::new(tv).expect("can't create sendable textview"); + sendable_tv.set_op(TextOp::Render); + sendable_tv.lend_mut(gam_cid, sendable_tv.get_op().into()).expect("draw_textview operation failure"); + + sendable_tv.set_op(TextOp::Nop); + Ok(()) +} diff --git a/services/gam/src/main.rs b/services/gam/src/main.rs index 3e283da03..03d0a326d 100644 --- a/services/gam/src/main.rs +++ b/services/gam/src/main.rs @@ -38,24 +38,24 @@ struct ModalCanvases { pub alert: Gid, } -fn add_modal_layout(trng_conn: xous::CID, canvases: &mut BinaryHeap) -> Result { +fn add_modal_layout(trng_conn: xous::CID, canvases: &mut FnvIndexMap) -> Result { let password_canvas = Canvas::new( Rectangle::new_coords(-1, -1, -1, -1), 0, trng_conn, None ).expect("GAM: couldn't create password canvas"); - canvases.push(password_canvas).expect("GAM: can't store password canvas"); + canvases.insert(password_canvas.gid(), password_canvas).expect("GAM: can't store password canvas"); let menu_canvas = Canvas::new( Rectangle::new_coords(-1, -1, -1, -1), 0, trng_conn, None ).expect("GAM: couldn't create menu canvas"); - canvases.push(menu_canvas).expect("GAM: can't store menu canvas"); + canvases.insert(menu_canvas.gid(), menu_canvas).expect("GAM: can't store menu canvas"); let alert_canvas = Canvas::new( Rectangle::new_coords(-1, -1, -1, -1), 0, trng_conn, None ).expect("GAM: couldn't create alert canvas"); - canvases.push(alert_canvas).expect("GAM: can't store alert canvas"); + canvases.insert(alert_canvas.gid(), alert_canvas).expect("GAM: can't store alert canvas"); Ok(ModalCanvases { password: password_canvas.gid(), @@ -64,7 +64,7 @@ fn add_modal_layout(trng_conn: xous::CID, canvases: &mut BinaryHeap) -> Result { +fn add_chat_layout(gfx_conn: xous::CID, trng_conn: xous::CID, canvases: &mut FnvIndexMap) -> Result { let screensize = graphics_server::screen_size(gfx_conn).expect("GAM: Couldn't get screen size"); // get the height of various text regions to compute the layout let small_height: i16 = graphics_server::glyph_height_hint(gfx_conn, GlyphStyle::Small).expect("GAM: couldn't get glyph height") as i16; @@ -75,26 +75,26 @@ fn add_chat_layout(gfx_conn: xous::CID, trng_conn: xous::CID, canvases: &mut Bin Rectangle::new_coords(0, 0, screensize.x, small_height), 0, trng_conn, None ).expect("GAM: couldn't create status canvas"); - canvases.push(status_canvas).expect("GAM: can't store status canvus"); + canvases.insert(status_canvas.gid(), status_canvas).expect("GAM: can't store status canvus"); let predictive_canvas = Canvas::new( Rectangle::new_coords(0, screensize.y - regular_height, screensize.x, screensize.y), 1, trng_conn, None ).expect("GAM: couldn't create predictive text canvas"); - canvases.push(predictive_canvas).expect("GAM: couldn't store predictive canvas"); + canvases.insert(predictive_canvas.gid(), predictive_canvas).expect("GAM: couldn't store predictive canvas"); let input_canvas = Canvas::new( Rectangle::new_v_stack(predictive_canvas.clip_rect(), -regular_height), 1, trng_conn, None ).expect("GAM: couldn't create input text canvas"); - canvases.push(input_canvas).expect("GAM: couldn't store input canvas"); + canvases.insert(input_canvas.gid(), input_canvas).expect("GAM: couldn't store input canvas"); let content_canvas = Canvas::new( Rectangle::new_v_span(status_canvas.clip_rect(), input_canvas.clip_rect()), 2, trng_conn, None ).expect("GAM: couldn't create content canvas"); - canvases.push(content_canvas).expect("GAM: can't store content canvas"); + canvases.insert(content_canvas.gid(), content_canvas).expect("GAM: can't store content canvas"); Ok(ChatLayout { status: status_canvas.gid(), @@ -104,7 +104,7 @@ fn add_chat_layout(gfx_conn: xous::CID, trng_conn: xous::CID, canvases: &mut Bin }) } -fn tv_draw(gfx_conn: xous::CID, trng_conn: xous::CID, tv: &mut TextView) -> Result<(), xous::Error> { +fn tv_draw(gfx_conn: xous::CID, trng_conn: xous::CID, canvases: &mut FnvIndexMap, tv: &mut TextView) -> Result<(), xous::Error> { /* 1. figure out text bounds 2. clear background, if requested @@ -112,9 +112,24 @@ fn tv_draw(gfx_conn: xous::CID, trng_conn: xous::CID, tv: &mut TextView) -> Resu 4. draw text */ - // figure out text bounds: figure out how wide our text is, to start with. + if let Some(canvas) = canvases.get_mut(&tv.get_canvas_gid()) { + // first, figure out if we should even be drawing to this canvas. + if canvas.is_drawable() { + // figure out text bounds: figure out how wide our text is, to start with. + + } else { + info!("GAM: attempt to draw TextView on non-drawable canvas. Not fatal, but request ignored."); + return Ok(()) + } + + + } else { + info!("GAM: bogus GID in TextView, not doing anything in response to draw request."); + return Ok(()) // silently fail if a bogus Gid is given??? + } Ok(()) + } #[xous::xous_main] @@ -130,18 +145,15 @@ fn xmain() -> ! { let screensize = graphics_server::screen_size(gfx_conn).expect("GAM: Couldn't get screen size"); - let mut canvases: BinaryHeap = BinaryHeap::new(); + // a map of canvases accessable by Gid + let mut canvases: FnvIndexMap = FnvIndexMap::new(); let modallayout = add_modal_layout(trng_conn, &mut canvases).expect("GAM: can't add modal layouts"); let chatlayout = add_chat_layout(gfx_conn, trng_conn, &mut canvases).expect("GAM: couldn't create chat layout"); - // canvas_map is a copy of the canvases, but searchable by GID - let canvas_map: FnvIndexMap; - // now that all the initial canvases have been allocated, compute what canvases are drawable // this _replaces_ the original canvas structure, to avoid complications of tracking mutable references through compound data structures // this is broken into two steps because of https://github.com/rust-lang/rust/issues/71126 - let (c_tuple, m_tuple) = recompute_canvases(canvases, Rectangle::new(Point::new(0, 0), screensize)); - canvases = c_tuple; canvas_map = m_tuple; + canvases = recompute_canvases(canvases, Rectangle::new(Point::new(0, 0), screensize)); // make a thread to manage the status bar xous::create_thread_simple(status_thread, chatlayout.status).expect("GAM: couldn't create status thread"); @@ -157,7 +169,7 @@ fn xmain() -> ! { if let Ok(opcode) = Opcode::try_from(&envelope.body) { match opcode { Opcode::ClearCanvas(gid) => { - match canvas_map.get(&gid) { + match canvases.get(&gid) { Some(c) => { let mut rect = c.clip_rect(); rect.style = DrawStyle { @@ -180,7 +192,7 @@ fn xmain() -> ! { &mut *(m.buf.as_mut_ptr() as *mut TextView) }; info!("GAM: render request for {:?}", tv); - tv_draw(gfx_conn, trng_conn, &mut tv).expect("GAM: can't render TextView"); + tv_draw(gfx_conn, trng_conn, &mut canvases, &mut tv).expect("GAM: can't render TextView"); }, TextOp::ComputeBounds => { diff --git a/services/graphics-server/Cargo.toml b/services/graphics-server/Cargo.toml index d307da1d1..6d7119638 100644 --- a/services/graphics-server/Cargo.toml +++ b/services/graphics-server/Cargo.toml @@ -11,8 +11,8 @@ log-server = { path = "../log-server" } xous-names = { path = "../xous-names" } log = "0.4" keyboard = { path = "../keyboard" } -blitstr = { git = "https://github.com/betrusted-io/blitstr.git", rev = "ea27e611d5ee165d8be894f9ca1c1130156c1931" } -# blitstr = { path = "../../../blitstr" } +# blitstr = { git = "https://github.com/betrusted-io/blitstr.git", rev = "ea27e611d5ee165d8be894f9ca1c1130156c1931" } +blitstr = { path = "../../../blitstr" } [target.'cfg(not(any(windows,unix)))'.dependencies] utralib = { path = "../../utralib"} diff --git a/services/graphics-server/src/api.rs b/services/graphics-server/src/api.rs index 1659064fb..2e4c88987 100644 --- a/services/graphics-server/src/api.rs +++ b/services/graphics-server/src/api.rs @@ -466,6 +466,9 @@ pub enum Opcode<'a> { /// Xor the string inside the clipping region. StringXor(&'a str), + /// Simulate the string on the clipping region (for computing text widths) + SimulateString(&'a str), + /// Retrieve the X and Y dimensions of the screen ScreenSize, @@ -538,6 +541,15 @@ impl<'a> core::convert::TryFrom<&'a Message> for Opcode<'a> { }; Ok(Opcode::StringXor(core::str::from_utf8(s).unwrap())) }, + 3 => { + let s = unsafe { + core::slice::from_raw_parts( + m.buf.as_ptr(), + m.valid.map(|x| x.get()).unwrap_or_else(|| m.buf.len()), + ) + }; + Ok(Opcode::SimulateString(core::str::from_utf8(s).unwrap())) + }, _ => Err("unrecognized opcode"), }, _ => Err("unhandled message type"), @@ -619,6 +631,10 @@ impl<'a> Into for Opcode<'a> { let region = xous::carton::Carton::from_bytes(string.as_bytes()); Message::Borrow(region.into_message(2)) } + Opcode::SimulateString(string) => { + let region = xous::carton::Carton::from_bytes(string.as_bytes()); + Message::Borrow(region.into_message(3)) + } Opcode::SetCursor(c) => Message::Scalar(ScalarMessage { id: 12, arg1: c.pt.x, diff --git a/services/graphics-server/src/main.rs b/services/graphics-server/src/main.rs index 4a6b54221..87eaf5a32 100644 --- a/services/graphics-server/src/main.rs +++ b/services/graphics-server/src/main.rs @@ -71,6 +71,7 @@ fn xmain() -> ! { current_glyph.into(), s, false, + blitstr::xor_char ); } Opcode::StringXor(s) => { @@ -81,6 +82,18 @@ fn xmain() -> ! { current_glyph.into(), s, true, + blitstr::xor_char + ); + } + Opcode::SimulateString(s) => { + blitstr::paint_str( + display.native_buffer(), + current_string_clip.into(), + &mut current_cursor, + current_glyph.into(), + s, + false, + blitstr::simulate_char ); } Opcode::SetGlyphStyle(glyph) => {