diff --git a/Cargo.lock b/Cargo.lock index 1d75ca68a9..5bd694432b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "log", "redox-daemon", "redox-log", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", "spin", ] @@ -59,7 +59,7 @@ dependencies = [ "common", "netutils", "redox-daemon", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", ] @@ -377,7 +377,7 @@ dependencies = [ "common", "netutils", "redox-daemon", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", ] @@ -387,6 +387,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fbcond" +version = "0.1.0" +dependencies = [ + "inputd", + "orbclient", + "ransid", + "redox-daemon", + "redox_event 0.2.1", + "redox_syscall 0.4.1", +] + [[package]] name = "fdt" version = "0.1.5" @@ -599,7 +611,7 @@ dependencies = [ "pcid", "redox-daemon", "redox-log", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", "spin", ] @@ -650,7 +662,7 @@ dependencies = [ "common", "netutils", "redox-daemon", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", ] @@ -675,6 +687,17 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.0", + "libc", + "redox_syscall 0.4.1", +] + [[package]] name = "lived" version = "0.1.0" @@ -740,7 +763,7 @@ dependencies = [ "ntpclient", "pbr", "redox-daemon", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.3.5", "redox_termios", "termion", @@ -1117,6 +1140,17 @@ dependencies = [ "redox_syscall 0.4.1", ] +[[package]] +name = "redox_event" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "475e252d7add4825405d2248d530d33e22364ac5477eab816b56efbeec1e2712" +dependencies = [ + "bitflags 2.4.0", + "libredox", + "redox_syscall 0.4.1", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1164,7 +1198,7 @@ dependencies = [ "pcid", "redox-daemon", "redox-log", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", ] @@ -1179,7 +1213,7 @@ dependencies = [ "pcid", "redox-daemon", "redox-log", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", ] @@ -1204,7 +1238,7 @@ dependencies = [ "log", "redox-daemon", "redox-log", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", "spin", ] @@ -1629,7 +1663,7 @@ dependencies = [ "common", "orbclient", "redox-daemon", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", ] @@ -1694,7 +1728,7 @@ dependencies = [ "log", "pcid", "redox-log", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", "static_assertions", "thiserror", @@ -1930,7 +1964,7 @@ dependencies = [ "plain", "redox-daemon", "redox-log", - "redox_event", + "redox_event 0.1.0", "redox_syscall 0.4.1", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 2298868ad1..444a5947c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "block-io-wrapper", "common", "e1000d", + "fbcond", "ided", "ihdad", "ixgbed", diff --git a/fbcond/Cargo.toml b/fbcond/Cargo.toml new file mode 100644 index 0000000000..465876c071 --- /dev/null +++ b/fbcond/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "fbcond" +version = "0.1.0" +edition = "2021" + +[dependencies] +orbclient = "0.3.27" +ransid = "0.4" +redox_event = "0.2" +redox_syscall = "0.4" +redox-daemon = "0.1" + +inputd = { path = "../inputd" } + +[features] +default = [] diff --git a/fbcond/src/display.rs b/fbcond/src/display.rs new file mode 100644 index 0000000000..31f930756d --- /dev/null +++ b/fbcond/src/display.rs @@ -0,0 +1,154 @@ +use std::mem; +use std::{ + fs::File, + io, + os::fd::RawFd, + os::unix::io::{AsRawFd, FromRawFd}, + slice, +}; +use syscall::{O_CLOEXEC, O_NONBLOCK, O_RDWR}; + +// Keep synced with vesad +#[derive(Clone, Copy)] +#[repr(packed)] +pub struct SyncRect { + pub x: i32, + pub y: i32, + pub w: i32, + pub h: i32, +} + +fn display_fd_map(width: usize, height: usize, display_fd: usize) -> syscall::Result<*mut [u32]> { + unsafe { + let display_ptr = syscall::fmap( + display_fd, + &syscall::Map { + offset: 0, + size: (width * height * 4), + flags: syscall::PROT_READ | syscall::PROT_WRITE | syscall::MAP_SHARED, + address: 0, + }, + )?; + let display_slice = slice::from_raw_parts_mut(display_ptr as *mut u32, width * height); + Ok(display_slice) + } +} + +unsafe fn display_fd_unmap(image: *mut [u32], size: usize) { + // FIXME use image.len() instead of size once it is stable. + let _ = syscall::funmap(image as *mut u32 as usize, size * 4); +} + +pub struct Display { + pub input_handle: File, + pub display_file: File, + pub offscreen: *mut [u32], + pub width: usize, + pub height: usize, +} + +impl Display { + pub fn open_vt(vt: usize) -> io::Result { + let mut buffer = [0; 1024]; + + let input_handle = File::open(format!("input:consumer/{vt}"))?; + let fd = input_handle.as_raw_fd(); + + let written = syscall::fpath(fd as usize, &mut buffer) + .expect("init: failed to get the path to the display device"); + + assert!(written <= buffer.len()); + + let display_path = + std::str::from_utf8(&buffer[..written]).expect("init: display path UTF-8 check failed"); + + let display_file = syscall::open(display_path, O_CLOEXEC | O_NONBLOCK | O_RDWR) + .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) }) + .unwrap_or_else(|err| { + panic!("failed to open display {}: {}", display_path, err); + }); + + let mut buf: [u8; 4096] = [0; 4096]; + let count = + syscall::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 = Self::url_parts(&url)?; + let (width, height) = Self::parse_display_path(path); + + let offscreen_buffer = display_fd_map(width, height, display_file.as_raw_fd() as usize) + .unwrap_or_else(|e| panic!("failed to map display '{display_path}: {e}")); + Ok(Self { + input_handle, + display_file, + offscreen: offscreen_buffer, + width, + height, + }) + } + + fn url_parts(url: &str) -> io::Result<&str> { + let mut url_parts = url.split(':'); + url_parts + .next() + .expect("Could not get scheme name from url"); + let path = url_parts.next().expect("Could not get path from url"); + Ok(path) + } + + fn parse_display_path(path: &str) -> (usize, usize) { + 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); + + (width, height) + } + + pub fn resize(&mut self, width: usize, height: usize) { + match display_fd_map(width, height, self.display_file.as_raw_fd() as usize) { + Ok(ok) => { + unsafe { + display_fd_unmap(self.offscreen, (self.width * self.height) as usize); + } + self.offscreen = ok; + self.width = width; + self.height = height; + } + Err(err) => { + eprintln!("failed to resize display to {}x{}: {}", width, height, err); + } + } + } + + pub fn sync_rect(&mut self, sync_rect: SyncRect) -> syscall::Result<()> { + unsafe { + syscall::write( + self.display_file.as_raw_fd().as_raw_fd() as usize, + slice::from_raw_parts( + &sync_rect as *const SyncRect as *const u8, + mem::size_of::(), + ), + )?; + Ok(()) + } + } +} + +impl Drop for Display { + fn drop(&mut self) { + unsafe { + display_fd_unmap(self.offscreen, self.width * self.height); + } + } +} diff --git a/fbcond/src/main.rs b/fbcond/src/main.rs new file mode 100644 index 0000000000..5349f038e2 --- /dev/null +++ b/fbcond/src/main.rs @@ -0,0 +1,165 @@ +use event::EventQueue; +use orbclient::Event; +use std::fs::File; +use std::io::{ErrorKind, Read, Write}; +use std::os::fd::AsRawFd; +use std::{env, io, mem, slice}; +use syscall::{Packet, SchemeMut, EVENT_READ}; + +use crate::scheme::{FbconScheme, VtIndex}; + +mod display; +mod scheme; +mod text; + +fn read_to_slice(mut r: R, buf: &mut [T]) -> io::Result { + unsafe { + r.read(slice::from_raw_parts_mut( + buf.as_mut_ptr() as *mut u8, + buf.len() * mem::size_of::(), + )) + .map(|count| count / mem::size_of::()) + } +} + +fn main() { + let vt_ids = env::args() + .skip(1) + .map(|arg| arg.parse().expect("invalid vt number")) + .collect::>(); + + redox_daemon::Daemon::new(|daemon| inner(daemon, &vt_ids)).expect("failed to create daemon"); +} +fn inner(daemon: redox_daemon::Daemon, vt_ids: &[usize]) -> ! { + let mut event_queue = EventQueue::new().expect("fbcond: failed to create event queue"); + + // FIXME listen for resize events from inputd and handle them + + let mut socket = File::create(":fbcon").expect("fbcond: failed to create fbcon scheme"); + event_queue + .subscribe( + socket.as_raw_fd().as_raw_fd() as usize, + VtIndex::SCHEMA_SENTINEL, + event::EventFlags::READ, + ) + .expect("fbcond: failed to subscribe to scheme events"); + + let mut scheme = FbconScheme::new(vt_ids, &mut event_queue); + + syscall::setrens(0, 0).expect("fbcond: failed to enter null namespace"); + + daemon.ready().expect("failed to notify parent"); + + scheme + .inputd_handle + .activate(1, inputd::VtMode::Default) + .unwrap(); + + let mut blocked = Vec::new(); + for event in event_queue { + let event = event.expect("fbcond: failed to read event from event queue"); + + match event.user_data { + VtIndex::SCHEMA_SENTINEL => { + let mut packet = Packet::default(); + if socket + .read(&mut packet) + .expect("fbcond: failed to read display scheme") + == 0 + { + //TODO: Handle blocked + break; + } + + // If it is a read packet, and there is no data, block it. Otherwise, handle packet + if packet.a == syscall::number::SYS_READ + && packet.d > 0 + && scheme.can_read(packet.b).is_none() + { + blocked.push(packet); + } else { + scheme.handle(&mut packet); + socket + .write(&packet) + .expect("fbcond: failed to write display scheme"); + } + } + vt_i => { + let vt = scheme.vts.get_mut(&vt_i).unwrap(); + + let mut events = [Event::new(); 16]; + loop { + match read_to_slice(&mut vt.display.input_handle, &mut events) { + Ok(0) => break, + Err(err) if err.kind() == ErrorKind::WouldBlock => { + break; + } + + Ok(count) => { + let events = &mut events[..count]; + for event in events.iter_mut() { + vt.input(event) + } + } + Err(err) => { + panic!("fbcond: Error while reading events: {err}"); + } + } + } + } + } + + // If there are blocked readers, and data is available, handle them + { + let mut i = 0; + while i < blocked.len() { + if scheme.can_read(blocked[i].b).is_some() { + let mut packet = blocked.remove(i); + scheme.handle(&mut packet); + socket + .write(&packet) + .expect("fbcond: failed to write display scheme"); + } else { + i += 1; + } + } + } + + for (handle_id, handle) in scheme.handles.iter_mut() { + if !handle.events.contains(EVENT_READ) { + continue; + } + + // Can't use scheme.can_read() because we borrow handles as mutable. + // (and because it'd treat O_NONBLOCK sockets differently) + let count = scheme + .vts + .get(&handle.vt_i) + .and_then(|console| console.can_read()) + .unwrap_or(0); + + if count > 0 { + if !handle.notified_read { + handle.notified_read = true; + let event_packet = Packet { + id: 0, + pid: 0, + uid: 0, + gid: 0, + a: syscall::number::SYS_FEVENT, + b: *handle_id, + c: EVENT_READ.bits(), + d: count, + }; + + socket + .write(&event_packet) + .expect("fbcond: failed to write display event"); + } + } else { + handle.notified_read = false; + } + } + } + std::process::exit(0); +} diff --git a/fbcond/src/scheme.rs b/fbcond/src/scheme.rs new file mode 100644 index 0000000000..a64a62cade --- /dev/null +++ b/fbcond/src/scheme.rs @@ -0,0 +1,186 @@ +use std::collections::BTreeMap; +use std::os::fd::AsRawFd; + +use event::{EventQueue, UserData}; +use syscall::{Error, EventFlags, Result, SchemeMut, EBADF, EINVAL, ENOENT, O_NONBLOCK}; + +use crate::display::Display; +use crate::text::TextScreen; + +#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Debug)] +pub struct VtIndex(usize); + +impl VtIndex { + pub const SCHEMA_SENTINEL: VtIndex = VtIndex(usize::MAX); +} + +impl UserData for VtIndex { + fn into_user_data(self) -> usize { + self.0 + } + + fn from_user_data(user_data: usize) -> Self { + VtIndex(user_data) + } +} + +#[derive(Clone)] +pub struct Handle { + pub vt_i: VtIndex, + pub flags: usize, + pub events: EventFlags, + pub notified_read: bool, +} + +pub struct FbconScheme { + pub vts: BTreeMap, + next_id: usize, + pub handles: BTreeMap, + pub inputd_handle: inputd::Handle, +} + +impl FbconScheme { + pub fn new(vt_ids: &[usize], event_queue: &mut EventQueue) -> FbconScheme { + let inputd_handle = inputd::Handle::new("vesa").unwrap(); + + let mut vts = BTreeMap::new(); + + for &vt_i in vt_ids { + let display = Display::open_vt(vt_i).expect("Failed to open display for vt"); + event_queue.subscribe( + display.input_handle.as_raw_fd().as_raw_fd() as usize, + VtIndex(vt_i), + event::EventFlags::READ, + ).expect("Failed to subscribe to input events for vt"); + vts.insert(VtIndex(vt_i), TextScreen::new(display)); + } + + FbconScheme { + vts, + next_id: 0, + handles: BTreeMap::new(), + inputd_handle, + } + } + + pub fn can_read(&self, id: usize) -> Option { + if let Some(handle) = self.handles.get(&id) { + if let Some(console) = self.vts.get(&handle.vt_i) { + console + .can_read() + .or(if handle.flags & O_NONBLOCK == O_NONBLOCK { + Some(0) + } else { + None + }); + } + } + + Some(0) + } + + fn resize(&mut self, width: usize, height: usize, stride: usize) { + for console in self.vts.values_mut() { + console.resize(width, height); + } + } +} + +impl SchemeMut for FbconScheme { + fn open(&mut self, path_str: &str, flags: usize, _uid: u32, _gid: u32) -> Result { + let vt_i = VtIndex(path_str.parse::().map_err(|_| Error::new(ENOENT))?); + if let Some(_console) = self.vts.get_mut(&vt_i) { + let id = self.next_id; + self.next_id += 1; + + self.handles.insert( + id, + Handle { + vt_i, + flags, + events: EventFlags::empty(), + notified_read: false, + }, + ); + + Ok(id) + } else { + Err(Error::new(ENOENT)) + } + } + + 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_str = format!("fbcon:{}", handle.vt_i.0); + + let path = path_str.as_bytes(); + + 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]) -> Result { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + if let Some(screen) = self.vts.get_mut(&handle.vt_i) { + return screen.read(buf); + } + + Err(Error::new(EBADF)) + } + + fn write(&mut self, id: usize, buf: &[u8]) -> Result { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + if let Some(console) = self.vts.get_mut(&handle.vt_i) { + console.write(buf) + } else { + Err(Error::new(EBADF)) + } + } + + fn close(&mut self, id: usize) -> Result { + self.handles.remove(&id).ok_or(Error::new(EBADF))?; + Ok(0) + } +} diff --git a/fbcond/src/text.rs b/fbcond/src/text.rs new file mode 100644 index 0000000000..bdc24d66f5 --- /dev/null +++ b/fbcond/src/text.rs @@ -0,0 +1,327 @@ +extern crate ransid; + +use std::collections::{BTreeSet, VecDeque}; +use std::convert::{TryFrom, TryInto}; +use std::os::fd::AsRawFd; +use std::{cmp, ptr}; + +use orbclient::{Event, EventOption, FONT}; +use syscall::error::*; + +use crate::display::{Display, SyncRect}; + +pub struct TextScreen { + console: ransid::Console, + pub display: Display, + changed: BTreeSet, + ctrl: bool, + input: VecDeque, +} + +impl TextScreen { + pub fn new(display: Display) -> TextScreen { + TextScreen { + console: ransid::Console::new(display.width / 8, display.height / 16), + display, + changed: BTreeSet::new(), + ctrl: false, + input: VecDeque::new(), + } + } + + /// Draw a rectangle + fn rect(display: &mut Display, x: usize, y: usize, w: usize, h: usize, color: u32) { + let start_y = cmp::min(display.height, y); + let end_y = cmp::min(display.height, y + h); + + let start_x = cmp::min(display.width, x); + let len = cmp::min(display.width, x + w) - start_x; + + let mut offscreen_ptr = display.offscreen as *mut u8 as usize; + + let stride = display.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(display: &mut Display, x: usize, y: usize, w: usize, h: usize) { + let start_y = cmp::min(display.height, y); + let end_y = cmp::min(display.height, y + h); + + let start_x = cmp::min(display.width, x); + let len = cmp::min(display.width, x + w) - start_x; + + let mut offscreen_ptr = display.offscreen as *mut u8 as usize; + + let stride = display.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( + display: &mut Display, + x: usize, + y: usize, + character: char, + color: u32, + _bold: bool, + _italic: bool, + ) { + if x + 8 <= display.width && y + 16 <= display.height { + let mut dst = display.offscreen as *mut u8 as usize + (y * display.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 += display.width * 4; + } + } + } + } + + pub fn resize(&mut self, width: usize, height: usize) { + self.display + .resize(width.try_into().unwrap(), height.try_into().unwrap()); + self.console.state.w = width / 8; + self.console.state.h = height / 16; + } + + pub fn input(&mut self, event: &Event) { + let mut buf = vec![]; + + match event.to_option() { + EventOption::Key(key_event) => { + if key_event.scancode == 0x1D { + self.ctrl = key_event.pressed; + } else if key_event.pressed { + match key_event.scancode { + 0x0E => { + // Backspace + buf.extend_from_slice(b"\x7F"); + } + 0x47 => { + // Home + buf.extend_from_slice(b"\x1B[H"); + } + 0x48 => { + // Up + buf.extend_from_slice(b"\x1B[A"); + } + 0x49 => { + // Page up + buf.extend_from_slice(b"\x1B[5~"); + } + 0x4B => { + // Left + buf.extend_from_slice(b"\x1B[D"); + } + 0x4D => { + // Right + buf.extend_from_slice(b"\x1B[C"); + } + 0x4F => { + // End + buf.extend_from_slice(b"\x1B[F"); + } + 0x50 => { + // Down + buf.extend_from_slice(b"\x1B[B"); + } + 0x51 => { + // Page down + buf.extend_from_slice(b"\x1B[6~"); + } + 0x52 => { + // Insert + buf.extend_from_slice(b"\x1B[2~"); + } + 0x53 => { + // Delete + buf.extend_from_slice(b"\x1B[3~"); + } + _ => { + let c = match key_event.character { + c @ 'A'..='Z' if self.ctrl => ((c as u8 - b'A') + b'\x01') as char, + c @ 'a'..='z' if self.ctrl => ((c as u8 - b'a') + b'\x01') as char, + c => c, + }; + + if c != '\0' { + let mut b = [0; 4]; + buf.extend_from_slice(c.encode_utf8(&mut b).as_bytes()); + } + } + } + } + } + _ => (), //TODO: Mouse in terminal + } + + for &b in buf.iter() { + self.input.push_back(b); + } + } + + pub fn can_read(&self) -> Option { + if self.input.is_empty() { + None + } else { + Some(self.input.len()) + } + } +} + +impl TextScreen { + pub fn read(&mut self, buf: &mut [u8]) -> Result { + let mut i = 0; + + while i < buf.len() && !self.input.is_empty() { + buf[i] = self.input.pop_front().unwrap(); + i += 1; + } + + Ok(i) + } + + pub fn write(&mut self, buf: &[u8]) -> Result { + 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 self.display, x * 8, y * 16, 8, 16); + self.changed.insert(y); + } + + { + let display = &mut self.display; + 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(display, 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(display, 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 = display.width; + let pixels = unsafe { &mut *display.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 self.display, x * 8, y * 16, 8, 16); + self.changed.insert(y); + } + + let width = self.display.width.try_into().unwrap(); + for &change in self.changed.iter() { + self.display.sync_rect(SyncRect { + x: 0, + y: i32::try_from(change).unwrap() * 16, + w: width, + h: 16, + })?; + } + self.changed.clear(); + syscall::fsync(self.display.display_file.as_raw_fd().as_raw_fd() as usize)?; + + Ok(buf.len()) + } +}