diff --git a/Cargo.lock b/Cargo.lock index fc46dcb053..3bd00c9a93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,6 +322,15 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "console-draw" +version = "0.1.0" +dependencies = [ + "inputd", + "orbclient", + "ransid", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -415,10 +424,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fbbootlogd" +version = "0.1.0" +dependencies = [ + "console-draw", + "inputd", + "libredox", + "orbclient", + "ransid", + "redox-daemon", + "redox-scheme", + "redox_event", + "redox_syscall", +] + [[package]] name = "fbcond" version = "0.1.0" dependencies = [ + "console-draw", "inputd", "libredox", "orbclient", diff --git a/Cargo.toml b/Cargo.toml index a8d2519edb..2181fb2841 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ members = [ "audio/sb16d", "graphics/bgad", + "graphics/console-draw", + "graphics/fbbootlogd", "graphics/fbcond", "graphics/vesad", "graphics/virtio-gpud", diff --git a/graphics/bgad/src/scheme.rs b/graphics/bgad/src/scheme.rs index 197f0ec01d..0d536e4e9d 100644 --- a/graphics/bgad/src/scheme.rs +++ b/graphics/bgad/src/scheme.rs @@ -1,5 +1,5 @@ -use std::str; use inputd::ProducerHandle; +use std::str; use syscall::data::Stat; use syscall::{Error, Result, SchemeMut, EACCES, EINVAL, MODE_CHR}; diff --git a/graphics/console-draw/Cargo.toml b/graphics/console-draw/Cargo.toml new file mode 100644 index 0000000000..56d8d98562 --- /dev/null +++ b/graphics/console-draw/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "console-draw" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbclient = "0.3.27" +ransid = "0.4" + +inputd = { path = "../../inputd" } + +[features] +default = [] diff --git a/graphics/console-draw/src/lib.rs b/graphics/console-draw/src/lib.rs new file mode 100644 index 0000000000..d52e46c57f --- /dev/null +++ b/graphics/console-draw/src/lib.rs @@ -0,0 +1,230 @@ +extern crate ransid; + +use std::collections::{BTreeSet, VecDeque}; +use std::convert::{TryFrom, TryInto}; +use std::{cmp, ptr}; + +use inputd::Damage; +use orbclient::FONT; + +pub struct DisplayMap { + pub offscreen: *mut [u32], + pub width: usize, + pub height: usize, +} + +pub struct TextScreen { + console: ransid::Console, + changed: BTreeSet, +} + +impl TextScreen { + pub fn new() -> TextScreen { + TextScreen { + // Width and height will be filled in on the next write to the console + console: ransid::Console::new(0, 0), + changed: BTreeSet::new(), + } + } + + /// Draw a rectangle + fn rect(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize, color: u32) { + let start_y = cmp::min(map.height, y); + let end_y = cmp::min(map.height, y + h); + + let start_x = cmp::min(map.width, x); + let len = cmp::min(map.width, x + w) - start_x; + + let mut offscreen_ptr = map.offscreen as *mut u8 as usize; + + let stride = map.width * 4; + + let offset = y * stride + start_x * 4; + offscreen_ptr += offset; + + let mut rows = end_y - start_y; + while rows > 0 { + for i in 0..len { + unsafe { + *(offscreen_ptr as *mut u32).add(i) = color; + } + } + offscreen_ptr += stride; + rows -= 1; + } + } + + /// Invert a rectangle + fn invert(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize) { + let start_y = cmp::min(map.height, y); + let end_y = cmp::min(map.height, y + h); + + let start_x = cmp::min(map.width, x); + let len = cmp::min(map.width, x + w) - start_x; + + let mut offscreen_ptr = map.offscreen as *mut u8 as usize; + + let stride = map.width * 4; + + let offset = y * stride + start_x * 4; + offscreen_ptr += offset; + + let mut rows = end_y - start_y; + while rows > 0 { + let mut row_ptr = offscreen_ptr; + let mut cols = len; + while cols > 0 { + unsafe { + let color = *(row_ptr as *mut u32); + *(row_ptr as *mut u32) = !color; + } + row_ptr += 4; + cols -= 1; + } + offscreen_ptr += stride; + rows -= 1; + } + } + + /// Draw a character + fn char( + map: &mut DisplayMap, + x: usize, + y: usize, + character: char, + color: u32, + _bold: bool, + _italic: bool, + ) { + if x + 8 <= map.width && y + 16 <= map.height { + let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4; + + let font_i = 16 * (character as usize); + if font_i + 16 <= FONT.len() { + for row in 0..16 { + let row_data = FONT[font_i + row]; + for col in 0..8 { + if (row_data >> (7 - col)) & 1 == 1 { + unsafe { + *((dst + col * 4) as *mut u32) = color; + } + } + } + dst += map.width * 4; + } + } + } + } +} + +impl TextScreen { + pub fn write( + &mut self, + map: &mut DisplayMap, + buf: &[u8], + input: &mut VecDeque, + ) -> Vec { + self.console.state.w = map.width / 8; + self.console.state.h = map.height / 16; + self.console.state.bottom_margin = (map.height / 16).saturating_sub(1); + + if self.console.state.cursor + && self.console.state.x < self.console.state.w + && self.console.state.y < self.console.state.h + { + let x = self.console.state.x; + let y = self.console.state.y; + Self::invert(map, x * 8, y * 16, 8, 16); + self.changed.insert(y); + } + + { + let changed = &mut self.changed; + self.console.write(buf, |event| match event { + ransid::Event::Char { + x, + y, + c, + color, + bold, + .. + } => { + Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false); + changed.insert(y); + } + ransid::Event::Input { data } => input.extend(data), + ransid::Event::Rect { x, y, w, h, color } => { + Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb()); + for y2 in y..y + h { + changed.insert(y2); + } + } + ransid::Event::ScreenBuffer { .. } => (), + ransid::Event::Move { + from_x, + from_y, + to_x, + to_y, + w, + h, + } => { + let width = map.width; + let pixels = unsafe { &mut *map.offscreen }; + + for raw_y in 0..h { + let y = if from_y > to_y { raw_y } else { h - raw_y - 1 }; + + for pixel_y in 0..16 { + { + let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8; + let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8; + let len = w * 8; + + if off_from + len <= pixels.len() && off_to + len <= pixels.len() { + unsafe { + let data_ptr = pixels.as_mut_ptr() as *mut u32; + ptr::copy( + data_ptr.offset(off_from as isize), + data_ptr.offset(off_to as isize), + len, + ); + } + } + } + } + + changed.insert(to_y + y); + } + } + ransid::Event::Resize { .. } => (), + ransid::Event::Title { .. } => (), + }); + } + + if self.console.state.cursor + && self.console.state.x < self.console.state.w + && self.console.state.y < self.console.state.h + { + let x = self.console.state.x; + let y = self.console.state.y; + Self::invert(map, x * 8, y * 16, 8, 16); + self.changed.insert(y); + } + + let width = map.width.try_into().unwrap(); + let damage = self + .changed + .iter() + .map(|&change| Damage { + x: 0, + y: i32::try_from(change).unwrap() * 16, + width, + height: 16, + }) + .collect(); + + self.changed.clear(); + + damage + } +} diff --git a/graphics/fbbootlogd/Cargo.toml b/graphics/fbbootlogd/Cargo.toml new file mode 100644 index 0000000000..90862d2780 --- /dev/null +++ b/graphics/fbbootlogd/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "fbbootlogd" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbclient = "0.3.27" +ransid = "0.4" +redox_event = "0.4" +redox_syscall = "0.5" +redox-daemon = "0.1" +redox-scheme = { git = "https://gitlab.redox-os.org/redox-os/redox-scheme.git" } + +console-draw = { path = "../console-draw" } +inputd = { path = "../../inputd" } +libredox = "0.1.3" + +[features] +default = [] diff --git a/graphics/fbbootlogd/src/display.rs b/graphics/fbbootlogd/src/display.rs new file mode 100644 index 0000000000..ad3b39d4cb --- /dev/null +++ b/graphics/fbbootlogd/src/display.rs @@ -0,0 +1,202 @@ +use event::{user_data, EventQueue}; +use inputd::{ConsumerHandle, Damage}; +use libredox::errno::ESTALE; +use libredox::flag; +use orbclient::Event; +use std::mem; +use std::os::fd::BorrowedFd; +use std::sync::mpsc::{self, Receiver, Sender}; +use std::sync::{Arc, Mutex}; +use std::{fs::File, io, os::unix::io::AsRawFd, slice}; + +fn read_to_slice( + file: BorrowedFd, + buf: &mut [T], +) -> Result { + unsafe { + libredox::call::read( + file.as_raw_fd() as usize, + slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len() * mem::size_of::()), + ) + .map(|count| count / mem::size_of::()) + } +} + +fn display_fd_map(width: usize, height: usize, display_file: File) -> syscall::Result { + unsafe { + let display_ptr = libredox::call::mmap(libredox::call::MmapArgs { + fd: display_file.as_raw_fd() as usize, + offset: 0, + length: (width * height * 4), + prot: flag::PROT_READ | flag::PROT_WRITE, + flags: flag::MAP_SHARED, + addr: core::ptr::null_mut(), + })?; + let display_slice = slice::from_raw_parts_mut(display_ptr as *mut u32, width * height); + Ok(DisplayMap { + display_file: Arc::new(display_file), + offscreen: display_slice, + width, + height, + }) + } +} + +unsafe fn display_fd_unmap(image: *mut [u32]) { + let _ = libredox::call::munmap(image as *mut (), image.len()); +} + +pub struct DisplayMap { + display_file: Arc, + pub offscreen: *mut [u32], + pub width: usize, + pub height: usize, +} + +unsafe impl Send for DisplayMap {} +unsafe impl Sync for DisplayMap {} + +impl Drop for DisplayMap { + fn drop(&mut self) { + unsafe { + display_fd_unmap(self.offscreen); + } + } +} + +enum DisplayCommand { + SyncRects(Vec), +} + +pub struct Display { + cmd_tx: Sender, + pub map: Arc>, +} + +impl Display { + pub fn open_first_vt() -> io::Result { + let input_handle = ConsumerHandle::for_vt(1)?; + + let (display_file, width, height) = Self::open_display(&input_handle)?; + + let map = Arc::new(Mutex::new( + display_fd_map(width, height, display_file) + .unwrap_or_else(|e| panic!("failed to map display: {e}")), + )); + + let map_clone = map.clone(); + std::thread::spawn(move || { + Self::handle_input_events(map_clone, input_handle); + }); + + let (cmd_tx, cmd_rx) = mpsc::channel(); + let map_clone = map.clone(); + std::thread::spawn(move || { + Self::handle_sync_rect(map_clone, cmd_rx); + }); + + Ok(Self { cmd_tx, map }) + } + + fn open_display(input_handle: &ConsumerHandle) -> io::Result<(File, usize, usize)> { + let display_file = input_handle.open_display()?; + + let mut buf: [u8; 4096] = [0; 4096]; + let count = libredox::call::fpath(display_file.as_raw_fd() as usize, &mut buf) + .unwrap_or_else(|e| { + panic!("Could not read display path with fpath(): {e}"); + }); + + let url = + String::from_utf8(Vec::from(&buf[..count])).expect("Could not create Utf8 Url String"); + let path = url.split(':').nth(1).expect("Could not get path from url"); + + let mut path_parts = path.split('/').skip(1); + let width = path_parts + .next() + .unwrap_or("") + .parse::() + .unwrap_or(0); + let height = path_parts + .next() + .unwrap_or("") + .parse::() + .unwrap_or(0); + + Ok((display_file, width, height)) + } + + fn handle_input_events(map: Arc>, input_handle: ConsumerHandle) { + let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue"); + + user_data! { + enum Source { + Input, + } + } + + // FIXME listen for resize events from inputd and handle them + + event_queue + .subscribe( + input_handle.inner().as_raw_fd() as usize, + Source::Input, + event::EventFlags::READ, + ) + .expect("fbbootlogd: failed to subscribe to scheme events"); + + let mut events = [Event::new(); 16]; + for Source::Input in event_queue.map(|event| event.unwrap().user_data) { + match read_to_slice(input_handle.inner(), &mut events) { + Err(err) if err.errno() == ESTALE => { + eprintln!("fbbootlogd: handoff requested"); + + let (new_display_file, width, height) = + Self::open_display(&input_handle).unwrap(); + + match display_fd_map(width, height, new_display_file) { + Ok(ok) => { + *map.lock().unwrap() = ok; + + eprintln!("fbbootlogd: handoff finished"); + } + Err(err) => { + eprintln!("fbbootlogd: failed to open display: {}", err); + } + } + } + + Ok(_count) => {} + Err(err) => { + panic!("fbbootlogd: error while reading events: {err}"); + } + } + } + } + + fn handle_sync_rect(map: Arc>, cmd_rx: Receiver) { + while let Ok(cmd) = cmd_rx.recv() { + match cmd { + DisplayCommand::SyncRects(sync_rects) => unsafe { + // We may not hold this lock across the write call to avoid deadlocking if the + // graphics driver tries to write to the bootlog. + let display_file = map.lock().unwrap().display_file.clone(); + libredox::call::write( + display_file.as_raw_fd() as usize, + slice::from_raw_parts( + sync_rects.as_ptr() as *const u8, + sync_rects.len() * mem::size_of::(), + ), + ) + .unwrap(); + }, + } + } + } + + pub fn sync_rects(&mut self, sync_rects: Vec) { + self.cmd_tx + .send(DisplayCommand::SyncRects(sync_rects)) + .unwrap(); + } +} diff --git a/graphics/fbbootlogd/src/main.rs b/graphics/fbbootlogd/src/main.rs new file mode 100644 index 0000000000..a45601820d --- /dev/null +++ b/graphics/fbbootlogd/src/main.rs @@ -0,0 +1,68 @@ +//! Fbbootlogd renders the boot log and presents it on VT1. +//! +//! While fbbootlogd is superficially similar to fbcond, there are two major differences: +//! +//! * Fbbootlogd doesn't accept input coming from the keyboard. It only allows getting written to. +//! * Writing to fbbootlogd will never block. Not even on the graphics driver or inputd. This makes +//! it safe for graphics drivers and inputd to write to the boot log without risking deadlocks. +//! Fbcond will block on the graphics driver during handoff and will continously block on inputd +//! to get new input. Fbbootlogd does all blocking operations in background threads such that the +//! main thread will always keep accepting new input and writing it to the framebuffer. + +#![feature(io_error_more)] + +use redox_scheme::{RequestKind, SignalBehavior, Socket, V2}; + +use crate::scheme::FbbootlogScheme; + +mod display; +mod scheme; +mod text; + +fn main() { + redox_daemon::Daemon::new(|daemon| inner(daemon)).expect("failed to create daemon"); +} +fn inner(daemon: redox_daemon::Daemon) -> ! { + let socket: Socket = + Socket::create("fbbootlog").expect("fbbootlogd: failed to create fbbootlog scheme"); + + let mut scheme = FbbootlogScheme::new(); + + let mut inputd_control_handle = inputd::ControlHandle::new().unwrap(); + + // This is not possible for now as fbbootlogd needs to open new displays at runtime for graphics + // driver handoff. In the future inputd may directly pass a handle to the display instead. + //libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace"); + + daemon.ready().expect("failed to notify parent"); + + inputd_control_handle.activate_vt(1).unwrap(); + + loop { + let request = match socket + .next_request(SignalBehavior::Restart) + .expect("fbbootlogd: failed to read display scheme") + { + Some(request) => request, + None => { + // Scheme likely got unmounted + std::process::exit(0); + } + }; + + match request.kind() { + RequestKind::Call(call_request) => { + socket + .write_response( + call_request.handle_scheme_mut(&mut scheme), + SignalBehavior::Restart, + ) + .expect("fbbootlogd: failed to write display scheme"); + } + RequestKind::Cancellation(_cancellation_request) => {} + RequestKind::MsyncMsg | RequestKind::MunmapMsg | RequestKind::MmapMsg => { + unreachable!() + } + } + } +} diff --git a/graphics/fbbootlogd/src/scheme.rs b/graphics/fbbootlogd/src/scheme.rs new file mode 100644 index 0000000000..fa258d6c53 --- /dev/null +++ b/graphics/fbbootlogd/src/scheme.rs @@ -0,0 +1,124 @@ +use std::collections::BTreeMap; + +use redox_scheme::SchemeMut; +use syscall::{Error, EventFlags, Result, EBADF, EINVAL, ENOENT}; + +use crate::display::Display; +use crate::text::TextScreen; + +#[derive(Clone)] +pub struct Handle { + pub events: EventFlags, + pub notified_read: bool, +} + +pub struct FbbootlogScheme { + pub screen: TextScreen, + next_id: usize, + pub handles: BTreeMap, +} + +impl FbbootlogScheme { + pub fn new() -> FbbootlogScheme { + let display = Display::open_first_vt().expect("Failed to open display for vt"); + let screen = TextScreen::new(display); + + FbbootlogScheme { + screen, + next_id: 0, + handles: BTreeMap::new(), + } + } +} + +impl SchemeMut for FbbootlogScheme { + fn open(&mut self, path_str: &str, _flags: usize, _uid: u32, _gid: u32) -> Result { + if !path_str.is_empty() { + return Err(Error::new(ENOENT)); + } + + let id = self.next_id; + self.next_id += 1; + + self.handles.insert( + id, + Handle { + events: EventFlags::empty(), + notified_read: false, + }, + ); + + Ok(id) + } + + fn dup(&mut self, id: usize, buf: &[u8]) -> Result { + if !buf.is_empty() { + return Err(Error::new(EINVAL)); + } + + let handle = self + .handles + .get(&id) + .map(|handle| handle.clone()) + .ok_or(Error::new(EBADF))?; + + let new_id = self.next_id; + self.next_id += 1; + + self.handles.insert(new_id, handle); + + Ok(new_id) + } + + fn fevent(&mut self, id: usize, flags: syscall::EventFlags) -> Result { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + handle.notified_read = false; + + handle.events = flags; + Ok(syscall::EventFlags::empty()) + } + + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + let path = b"fbbootlog:"; + + let mut i = 0; + while i < buf.len() && i < path.len() { + buf[i] = path[i]; + i += 1; + } + + Ok(i) + } + + fn fsync(&mut self, id: usize) -> Result { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + return Ok(0); + } + + fn read( + &mut self, + id: usize, + _buf: &mut [u8], + _offset: u64, + _fcntl_flags: u32, + ) -> Result { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + Err(Error::new(EINVAL)) + } + + fn write(&mut self, id: usize, buf: &[u8], _offset: u64, _fcntl_flags: u32) -> Result { + let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + self.screen.write(buf) + } + + fn close(&mut self, id: usize) -> Result { + self.handles.remove(&id).ok_or(Error::new(EBADF))?; + Ok(0) + } +} diff --git a/graphics/fbbootlogd/src/text.rs b/graphics/fbbootlogd/src/text.rs new file mode 100644 index 0000000000..5dff07d565 --- /dev/null +++ b/graphics/fbbootlogd/src/text.rs @@ -0,0 +1,41 @@ +extern crate ransid; + +use std::collections::VecDeque; + +use syscall::error::*; + +use crate::display::Display; + +pub struct TextScreen { + pub display: Display, + inner: console_draw::TextScreen, +} + +impl TextScreen { + pub fn new(display: Display) -> TextScreen { + TextScreen { + display, + inner: console_draw::TextScreen::new(), + } + } +} + +impl TextScreen { + pub fn write(&mut self, buf: &[u8]) -> Result { + let map = self.display.map.lock().unwrap(); + let damage = self.inner.write( + &mut console_draw::DisplayMap { + offscreen: map.offscreen, + width: map.width, + height: map.height, + }, + buf, + &mut VecDeque::new(), + ); + drop(map); + + self.display.sync_rects(damage); + + Ok(buf.len()) + } +} diff --git a/graphics/fbcond/Cargo.toml b/graphics/fbcond/Cargo.toml index a22c102f40..fffb1a0729 100644 --- a/graphics/fbcond/Cargo.toml +++ b/graphics/fbcond/Cargo.toml @@ -11,6 +11,7 @@ redox_syscall = "0.5" redox-daemon = "0.1" redox-scheme = { git = "https://gitlab.redox-os.org/redox-os/redox-scheme.git" } +console-draw = { path = "../console-draw" } inputd = { path = "../../inputd" } libredox = "0.1.3" diff --git a/graphics/fbcond/src/main.rs b/graphics/fbcond/src/main.rs index 11ac25cec2..7d270eb0d0 100644 --- a/graphics/fbcond/src/main.rs +++ b/graphics/fbcond/src/main.rs @@ -51,16 +51,12 @@ fn inner(daemon: redox_daemon::Daemon, vt_ids: &[usize]) -> ! { let mut scheme = FbconScheme::new(vt_ids, &mut event_queue); - let mut inputd_control_handle = inputd::ControlHandle::new().unwrap(); - // This is not possible for now as fbcond needs to open new displays at runtime for graphics // driver handoff. In the future inputd may directly pass a handle to the display instead. //libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace"); daemon.ready().expect("failed to notify parent"); - inputd_control_handle.activate_vt(1).unwrap(); - let mut blocked = Vec::new(); // Handle all events that could have happened before registering with the event queue. diff --git a/graphics/fbcond/src/text.rs b/graphics/fbcond/src/text.rs index bbe519b117..696adf0ba2 100644 --- a/graphics/fbcond/src/text.rs +++ b/graphics/fbcond/src/text.rs @@ -1,19 +1,16 @@ extern crate ransid; -use std::collections::{BTreeSet, VecDeque}; -use std::convert::{TryFrom, TryInto}; -use std::{cmp, ptr}; +use std::collections::VecDeque; +use std::convert::TryInto; -use inputd::Damage; -use orbclient::{Event, EventOption, FONT}; +use orbclient::{Event, EventOption}; use syscall::error::*; -use crate::display::{Display, DisplayMap}; +use crate::display::Display; pub struct TextScreen { - console: ransid::Console, pub display: Display, - changed: BTreeSet, + inner: console_draw::TextScreen, ctrl: bool, input: VecDeque, } @@ -21,104 +18,13 @@ pub struct TextScreen { impl TextScreen { pub fn new(display: Display) -> TextScreen { TextScreen { - // Width and height will be filled in on the next write to the console - console: ransid::Console::new(0, 0), display, - changed: BTreeSet::new(), + inner: console_draw::TextScreen::new(), ctrl: false, input: VecDeque::new(), } } - /// Draw a rectangle - fn rect(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize, color: u32) { - let start_y = cmp::min(map.height, y); - let end_y = cmp::min(map.height, y + h); - - let start_x = cmp::min(map.width, x); - let len = cmp::min(map.width, x + w) - start_x; - - let mut offscreen_ptr = map.offscreen as *mut u8 as usize; - - let stride = map.width * 4; - - let offset = y * stride + start_x * 4; - offscreen_ptr += offset; - - let mut rows = end_y - start_y; - while rows > 0 { - for i in 0..len { - unsafe { - *(offscreen_ptr as *mut u32).add(i) = color; - } - } - offscreen_ptr += stride; - rows -= 1; - } - } - - /// Invert a rectangle - fn invert(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize) { - let start_y = cmp::min(map.height, y); - let end_y = cmp::min(map.height, y + h); - - let start_x = cmp::min(map.width, x); - let len = cmp::min(map.width, x + w) - start_x; - - let mut offscreen_ptr = map.offscreen as *mut u8 as usize; - - let stride = map.width * 4; - - let offset = y * stride + start_x * 4; - offscreen_ptr += offset; - - let mut rows = end_y - start_y; - while rows > 0 { - let mut row_ptr = offscreen_ptr; - let mut cols = len; - while cols > 0 { - unsafe { - let color = *(row_ptr as *mut u32); - *(row_ptr as *mut u32) = !color; - } - row_ptr += 4; - cols -= 1; - } - offscreen_ptr += stride; - rows -= 1; - } - } - - /// Draw a character - fn char( - map: &mut DisplayMap, - x: usize, - y: usize, - character: char, - color: u32, - _bold: bool, - _italic: bool, - ) { - if x + 8 <= map.width && y + 16 <= map.height { - let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4; - - let font_i = 16 * (character as usize); - if font_i + 16 <= FONT.len() { - for row in 0..16 { - let row_data = FONT[font_i + row]; - for col in 0..8 { - if (row_data >> (7 - col)) & 1 == 1 { - unsafe { - *((dst + col * 4) as *mut u32) = color; - } - } - } - dst += map.width * 4; - } - } - } - } - pub fn handle_handoff(&mut self) { self.display.reopen_for_handoff(); } @@ -222,112 +128,19 @@ impl TextScreen { } pub fn write(&mut self, buf: &[u8]) -> Result { - let mut map = self.display.map.lock().unwrap(); - - self.console.state.w = map.width / 8; - self.console.state.h = map.height / 16; - self.console.state.bottom_margin = (map.height / 16).saturating_sub(1); - - if self.console.state.cursor - && self.console.state.x < self.console.state.w - && self.console.state.y < self.console.state.h - { - let x = self.console.state.x; - let y = self.console.state.y; - Self::invert(&mut map, x * 8, y * 16, 8, 16); - self.changed.insert(y); - } - - { - let changed = &mut self.changed; - let input = &mut self.input; - self.console.write(buf, |event| match event { - ransid::Event::Char { - x, - y, - c, - color, - bold, - .. - } => { - Self::char(&mut map, x * 8, y * 16, c, color.as_rgb(), bold, false); - changed.insert(y); - } - ransid::Event::Input { data } => { - input.extend(data); - } - ransid::Event::Rect { x, y, w, h, color } => { - Self::rect(&mut map, x * 8, y * 16, w * 8, h * 16, color.as_rgb()); - for y2 in y..y + h { - changed.insert(y2); - } - } - ransid::Event::ScreenBuffer { .. } => (), - ransid::Event::Move { - from_x, - from_y, - to_x, - to_y, - w, - h, - } => { - let width = map.width; - let pixels = unsafe { &mut *map.offscreen }; - - for raw_y in 0..h { - let y = if from_y > to_y { raw_y } else { h - raw_y - 1 }; - - for pixel_y in 0..16 { - { - let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8; - let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8; - let len = w * 8; - - if off_from + len <= pixels.len() && off_to + len <= pixels.len() { - unsafe { - let data_ptr = pixels.as_mut_ptr() as *mut u32; - ptr::copy( - data_ptr.offset(off_from as isize), - data_ptr.offset(off_to as isize), - len, - ); - } - } - } - } - - changed.insert(to_y + y); - } - } - ransid::Event::Resize { .. } => (), - ransid::Event::Title { .. } => (), - }); - } - - if self.console.state.cursor - && self.console.state.x < self.console.state.w - && self.console.state.y < self.console.state.h - { - let x = self.console.state.x; - let y = self.console.state.y; - Self::invert(&mut map, x * 8, y * 16, 8, 16); - self.changed.insert(y); - } - - let width = map.width.try_into().unwrap(); - drop(map); - self.display.sync_rects( - self.changed - .iter() - .map(|&change| Damage { - x: 0, - y: i32::try_from(change).unwrap() * 16, - width, - height: 16, - }) - .collect(), + let map = self.display.map.lock().unwrap(); + let damage = self.inner.write( + &mut console_draw::DisplayMap { + offscreen: map.offscreen, + width: map.width, + height: map.height, + }, + buf, + &mut self.input, ); - self.changed.clear(); + drop(map); + + self.display.sync_rects(damage); Ok(buf.len()) }