milestone: desktop path Phases 1-5
Phase 1 (Runtime Substrate): 4 check binaries, --probe, POSIX tests Phase 2 (Wayland Compositor): bounded scaffold, zero warnings Phase 3 (KWin Session): preflight checker (KWin stub, gated on Qt6Quick) Phase 4 (KDE Plasma): 18 KF6 enabled, preflight checker Phase 5 (Hardware GPU): DRM/firmware/Mesa preflight checker Build: zero warnings, all scripts syntax-clean. Oracle-verified.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "fbcond"
|
||||
description = "Terminal daemon"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
drm.workspace = true
|
||||
log.workspace = true
|
||||
orbclient.workspace = true
|
||||
ransid.workspace = true
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
|
||||
common = { path = "../../common" }
|
||||
console-draw = { path = "../console-draw" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
graphics-ipc = { path = "../graphics-ipc" }
|
||||
inputd = { path = "../../inputd" }
|
||||
libredox.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,83 @@
|
||||
use console_draw::{Damage, TextScreen, V2DisplayMap};
|
||||
use drm::buffer::Buffer;
|
||||
use drm::control::Device;
|
||||
use graphics_ipc::V2GraphicsHandle;
|
||||
use inputd::ConsumerHandle;
|
||||
use std::io;
|
||||
|
||||
pub struct Display {
|
||||
pub input_handle: ConsumerHandle,
|
||||
pub map: Option<V2DisplayMap>,
|
||||
}
|
||||
|
||||
impl Display {
|
||||
pub fn open_new_vt() -> io::Result<Self> {
|
||||
let mut display = Self {
|
||||
input_handle: ConsumerHandle::new_vt()?,
|
||||
map: None,
|
||||
};
|
||||
|
||||
display.reopen_for_handoff();
|
||||
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
/// Re-open the display after a handoff.
|
||||
pub fn reopen_for_handoff(&mut self) {
|
||||
let display_file = match self.input_handle.open_display_v2() {
|
||||
Ok(display_file) => display_file,
|
||||
Err(err) => {
|
||||
log::error!("fbcond: No display present yet: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap();
|
||||
|
||||
log::debug!("fbcond: Opened new display");
|
||||
|
||||
match V2DisplayMap::new(new_display_handle) {
|
||||
Ok(map) => {
|
||||
log::debug!(
|
||||
"fbcond: Mapped new display with size {}x{}",
|
||||
map.buffer.buffer().size().0,
|
||||
map.buffer.buffer().size().1,
|
||||
);
|
||||
self.map = Some(map)
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("fbcond: failed to map new display: {err}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) {
|
||||
let mode = match map
|
||||
.display_handle
|
||||
.first_display()
|
||||
.and_then(|handle| Ok(map.display_handle.get_connector(handle, true)?.modes()[0]))
|
||||
{
|
||||
Ok(mode) => mode,
|
||||
Err(err) => {
|
||||
eprintln!("fbcond: failed to get display size: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if (u32::from(mode.size().0), u32::from(mode.size().1)) != map.buffer.buffer().size() {
|
||||
match text_screen.resize(map, mode) {
|
||||
Ok(()) => eprintln!("fbcond: mapped display"),
|
||||
Err(err) => {
|
||||
eprintln!("fbcond: failed to create or map framebuffer: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_rect(&mut self, damage: Damage) {
|
||||
if let Some(map) = &mut self.map {
|
||||
map.dirty_fb(damage).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
use event::EventQueue;
|
||||
use inputd::ConsumerHandleEvent;
|
||||
use libredox::errno::{EAGAIN, EINTR};
|
||||
use orbclient::Event;
|
||||
use redox_scheme::{
|
||||
scheme::{Op, SchemeResponse, SchemeState, SchemeSync},
|
||||
CallerCtx, RequestKind, Response, SignalBehavior, Socket,
|
||||
};
|
||||
use std::env;
|
||||
use syscall::{EOPNOTSUPP, EVENT_READ};
|
||||
|
||||
use crate::scheme::{FbconScheme, Handle, VtIndex};
|
||||
|
||||
mod display;
|
||||
mod scheme;
|
||||
mod text;
|
||||
|
||||
fn main() {
|
||||
daemon::SchemeDaemon::new(daemon);
|
||||
}
|
||||
fn daemon(daemon: daemon::SchemeDaemon) -> ! {
|
||||
let vt_ids = env::args()
|
||||
.skip(1)
|
||||
.map(|arg| arg.parse().expect("invalid vt number"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
common::setup_logging(
|
||||
"graphics",
|
||||
"fbcond",
|
||||
"fbcond",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
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 = Socket::nonblock().expect("fbcond: failed to create fbcon scheme");
|
||||
event_queue
|
||||
.subscribe(
|
||||
socket.inner().raw(),
|
||||
VtIndex::SCHEMA_SENTINEL,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.expect("fbcond: failed to subscribe to scheme events");
|
||||
|
||||
let mut state = SchemeState::new();
|
||||
let mut scheme = FbconScheme::new(&vt_ids, &mut event_queue);
|
||||
|
||||
let _ = daemon.ready_sync_scheme(&socket, &mut scheme);
|
||||
|
||||
// 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");
|
||||
|
||||
let mut blocked = Vec::new();
|
||||
|
||||
// Handle all events that could have happened before registering with the event queue.
|
||||
handle_event(
|
||||
&mut socket,
|
||||
&mut scheme,
|
||||
&mut state,
|
||||
&mut blocked,
|
||||
VtIndex::SCHEMA_SENTINEL,
|
||||
);
|
||||
for vt_i in scheme.vts.keys().copied().collect::<Vec<_>>() {
|
||||
handle_event(&mut socket, &mut scheme, &mut state, &mut blocked, vt_i);
|
||||
}
|
||||
|
||||
for event in event_queue {
|
||||
let event = event.expect("fbcond: failed to read event from event queue");
|
||||
handle_event(
|
||||
&mut socket,
|
||||
&mut scheme,
|
||||
&mut state,
|
||||
&mut blocked,
|
||||
event.user_data,
|
||||
);
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
socket: &mut Socket,
|
||||
scheme: &mut FbconScheme,
|
||||
state: &mut SchemeState,
|
||||
blocked: &mut Vec<(Op, CallerCtx)>,
|
||||
event: VtIndex,
|
||||
) {
|
||||
match event {
|
||||
VtIndex::SCHEMA_SENTINEL => loop {
|
||||
let request = match socket.next_request(SignalBehavior::Restart) {
|
||||
Ok(Some(request)) => request,
|
||||
Ok(None) => {
|
||||
// Scheme likely got unmounted
|
||||
std::process::exit(0);
|
||||
}
|
||||
Err(err) if err.errno == EAGAIN => {
|
||||
break;
|
||||
}
|
||||
Err(err) => panic!("fbcond: failed to read display scheme: {err}"),
|
||||
};
|
||||
|
||||
match request.kind() {
|
||||
RequestKind::Call(req) => {
|
||||
let caller = req.caller();
|
||||
let mut op = match req.op() {
|
||||
Ok(op) => op,
|
||||
Err(req) => {
|
||||
let _ = socket
|
||||
.write_response(
|
||||
Response::err(EOPNOTSUPP, req),
|
||||
SignalBehavior::Restart,
|
||||
)
|
||||
.expect("fbcond: failed to write responses to fbcon scheme");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match op.handle_sync_dont_consume(&caller, scheme, state) {
|
||||
SchemeResponse::Opened(Err(e)) | SchemeResponse::Regular(Err(e))
|
||||
if libredox::error::Error::from(e).is_wouldblock()
|
||||
&& !op.is_explicitly_nonblock() =>
|
||||
{
|
||||
blocked.push((op, caller));
|
||||
}
|
||||
SchemeResponse::Regular(r) => {
|
||||
let _ = socket
|
||||
.write_response(Response::new(r, op), SignalBehavior::Restart)
|
||||
.expect("fbcond: failed to write responses to fbcon scheme");
|
||||
}
|
||||
SchemeResponse::Opened(o) => {
|
||||
let _ = socket
|
||||
.write_response(
|
||||
Response::open_dup_like(o, op),
|
||||
SignalBehavior::Restart,
|
||||
)
|
||||
.expect("fbcond: failed to write responses to fbcon scheme");
|
||||
}
|
||||
SchemeResponse::RegularAndNotifyOnDetach(status) => {
|
||||
let _ = socket
|
||||
.write_response(
|
||||
Response::new_notify_on_detach(status, op),
|
||||
SignalBehavior::Restart,
|
||||
)
|
||||
.expect("fbcond: failed to write scheme");
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestKind::OnClose { id } => {
|
||||
scheme.on_close(id);
|
||||
}
|
||||
RequestKind::Cancellation(cancellation_request) => {
|
||||
if let Some(i) = blocked
|
||||
.iter()
|
||||
.position(|(_op, caller)| caller.id == cancellation_request.id)
|
||||
{
|
||||
let (blocked_req, _) = blocked.remove(i);
|
||||
let resp = Response::err(EINTR, blocked_req);
|
||||
socket
|
||||
.write_response(resp, SignalBehavior::Restart)
|
||||
.expect("vesad: failed to write display scheme");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
vt_i => {
|
||||
let vt = scheme.vts.get_mut(&vt_i).unwrap();
|
||||
|
||||
let mut events = [Event::new(); 16];
|
||||
loop {
|
||||
match vt
|
||||
.display
|
||||
.input_handle
|
||||
.read_events(&mut events)
|
||||
.expect("fbcond: Error while reading events")
|
||||
{
|
||||
ConsumerHandleEvent::Events(&[]) => break,
|
||||
|
||||
ConsumerHandleEvent::Events(events) => {
|
||||
for event in events {
|
||||
vt.input(event)
|
||||
}
|
||||
}
|
||||
ConsumerHandleEvent::Handoff => vt.handle_handoff(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are blocked readers, try to handle them.
|
||||
{
|
||||
let mut i = 0;
|
||||
while i < blocked.len() {
|
||||
let (op, caller) = blocked
|
||||
.get_mut(i)
|
||||
.expect("vesad: Failed to get blocked request");
|
||||
let resp = match op.handle_sync_dont_consume(&caller, scheme, state) {
|
||||
SchemeResponse::Opened(Err(e)) | SchemeResponse::Regular(Err(e))
|
||||
if libredox::error::Error::from(e).is_wouldblock()
|
||||
&& !op.is_explicitly_nonblock() =>
|
||||
{
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
SchemeResponse::Regular(r) => {
|
||||
let (op, _) = blocked.remove(i);
|
||||
Response::new(r, op)
|
||||
}
|
||||
SchemeResponse::Opened(o) => {
|
||||
let (op, _) = blocked.remove(i);
|
||||
Response::open_dup_like(o, op)
|
||||
}
|
||||
SchemeResponse::RegularAndNotifyOnDetach(status) => {
|
||||
let (op, _) = blocked.remove(i);
|
||||
Response::new_notify_on_detach(status, op)
|
||||
}
|
||||
};
|
||||
let _ = socket
|
||||
.write_response(resp, SignalBehavior::Restart)
|
||||
.expect("vesad: failed to write display scheme");
|
||||
}
|
||||
}
|
||||
|
||||
for (handle_id, handle) in scheme.handles.iter_mut() {
|
||||
let handle = match handle {
|
||||
Handle::SchemeRoot => continue,
|
||||
Handle::Vt(handle) => handle,
|
||||
};
|
||||
|
||||
if !handle.events.contains(EVENT_READ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let can_read = scheme
|
||||
.vts
|
||||
.get(&handle.vt_i)
|
||||
.map_or(false, |console| console.can_read());
|
||||
|
||||
if can_read {
|
||||
if !handle.notified_read {
|
||||
handle.notified_read = true;
|
||||
let response = Response::post_fevent(*handle_id, EVENT_READ.bits());
|
||||
socket
|
||||
.write_response(response, SignalBehavior::Restart)
|
||||
.expect("fbcond: failed to write display event");
|
||||
}
|
||||
} else {
|
||||
handle.notified_read = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use event::{EventQueue, UserData};
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::{CallerCtx, OpenResult};
|
||||
use scheme_utils::{FpathWriter, HandleMap};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, 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)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FdHandle {
|
||||
pub vt_i: VtIndex,
|
||||
pub flags: usize,
|
||||
pub events: EventFlags,
|
||||
pub notified_read: bool,
|
||||
}
|
||||
|
||||
pub enum Handle {
|
||||
Vt(FdHandle),
|
||||
SchemeRoot,
|
||||
}
|
||||
|
||||
pub struct FbconScheme {
|
||||
pub vts: BTreeMap<VtIndex, TextScreen>,
|
||||
pub handles: HandleMap<Handle>,
|
||||
}
|
||||
|
||||
impl FbconScheme {
|
||||
pub fn new(vt_ids: &[usize], event_queue: &mut EventQueue<VtIndex>) -> FbconScheme {
|
||||
let mut vts = BTreeMap::new();
|
||||
|
||||
for &vt_i in vt_ids {
|
||||
let display = Display::open_new_vt().expect("Failed to open display for vt");
|
||||
event_queue
|
||||
.subscribe(
|
||||
display.input_handle.event_handle().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,
|
||||
handles: HandleMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_vt_handle_mut(&mut self, id: usize) -> Result<&mut FdHandle> {
|
||||
match self.handles.get_mut(id)? {
|
||||
Handle::Vt(handle) => Ok(handle),
|
||||
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for FbconScheme {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(self.handles.insert(Handle::SchemeRoot))
|
||||
}
|
||||
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path_str: &str,
|
||||
flags: usize,
|
||||
fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
|
||||
let vt_i = VtIndex(path_str.parse::<usize>().map_err(|_| Error::new(ENOENT))?);
|
||||
if self.vts.contains_key(&vt_i) {
|
||||
let id = self.handles.insert(Handle::Vt(FdHandle {
|
||||
vt_i,
|
||||
flags: flags | fcntl_flags as usize,
|
||||
events: EventFlags::empty(),
|
||||
notified_read: false,
|
||||
}));
|
||||
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: id,
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(ENOENT))
|
||||
}
|
||||
}
|
||||
|
||||
fn fevent(
|
||||
&mut self,
|
||||
id: usize,
|
||||
flags: syscall::EventFlags,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<syscall::EventFlags> {
|
||||
let handle = self.get_vt_handle_mut(id)?;
|
||||
|
||||
handle.notified_read = false;
|
||||
handle.events = flags;
|
||||
|
||||
Ok(syscall::EventFlags::empty())
|
||||
}
|
||||
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
||||
FpathWriter::with_legacy(buf, "fbcon", |w| {
|
||||
let handle = self.get_vt_handle_mut(id)?;
|
||||
write!(w, "{}", handle.vt_i.0).unwrap();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
|
||||
let _handle = self.get_vt_handle_mut(id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result<usize> {
|
||||
self.handles.get(id)?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let handle = match self.handles.get(id)? {
|
||||
Handle::Vt(handle) => Ok(handle),
|
||||
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
}?;
|
||||
|
||||
if let Some(screen) = self.vts.get_mut(&handle.vt_i) {
|
||||
if !screen.can_read() {
|
||||
if handle.flags & O_NONBLOCK != 0 {
|
||||
Err(Error::new(EAGAIN))
|
||||
} else {
|
||||
Err(Error::new(EAGAIN))
|
||||
}
|
||||
} else {
|
||||
screen.read(buf)
|
||||
}
|
||||
} else {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &[u8],
|
||||
_offset: u64,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let vt_i = self.get_vt_handle_mut(id)?.vt_i;
|
||||
|
||||
if let Some(console) = self.vts.get_mut(&vt_i) {
|
||||
console.write(buf)
|
||||
} else {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
self.handles.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use orbclient::{Event, EventOption};
|
||||
use syscall::error::*;
|
||||
|
||||
use crate::display::Display;
|
||||
|
||||
pub struct TextScreen {
|
||||
pub display: Display,
|
||||
inner: console_draw::TextScreen,
|
||||
ctrl: bool,
|
||||
input: VecDeque<u8>,
|
||||
}
|
||||
|
||||
impl TextScreen {
|
||||
pub fn new(display: Display) -> TextScreen {
|
||||
TextScreen {
|
||||
display,
|
||||
inner: console_draw::TextScreen::new(),
|
||||
ctrl: false,
|
||||
input: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_handoff(&mut self) {
|
||||
log::info!("fbcond: Performing handoff");
|
||||
self.display.reopen_for_handoff();
|
||||
}
|
||||
|
||||
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) -> bool {
|
||||
!self.input.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl TextScreen {
|
||||
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||
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<usize> {
|
||||
if let Some(map) = &mut self.display.map {
|
||||
Display::handle_resize(map, &mut self.inner);
|
||||
|
||||
let damage = self.inner.write(map, buf, &mut self.input);
|
||||
|
||||
self.display.sync_rect(damage);
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user