From bc1a068a4ffa45cd1dbd3f1d4bade5c89201692e Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 14 Jan 2024 18:14:40 +0100 Subject: [PATCH] Introduce fbcond to replace text console handling inside vesad This will in the future allow switching between graphical mode and text mode virtual terminals while keeping alternative graphics drivers like virtio-gpud enabled at all times rather than having to reset back to VGA mode every time. --- Cargo.lock | 58 ++++++-- Cargo.toml | 1 + fbcond/Cargo.toml | 16 +++ fbcond/src/display.rs | 154 ++++++++++++++++++++ fbcond/src/main.rs | 165 +++++++++++++++++++++ fbcond/src/scheme.rs | 186 ++++++++++++++++++++++++ fbcond/src/text.rs | 327 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 895 insertions(+), 12 deletions(-) create mode 100644 fbcond/Cargo.toml create mode 100644 fbcond/src/display.rs create mode 100644 fbcond/src/main.rs create mode 100644 fbcond/src/scheme.rs create mode 100644 fbcond/src/text.rs 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()) + } +}