//! `:input` //! //! A seperate scheme is required since all of the input from different input devices is required //! to be combined into a single stream which is later going to be processed by the "consumer" //! which usually is Orbital. //! //! ## Input Device ("producer") //! Write events to `input:producer`. //! //! ## Input Consumer ("consumer") //! Read events from `input:consumer`. Optionally, set the `EVENT_READ` flag to be notified when //! events are available. use std::collections::BTreeMap; use std::fs::File; use std::io::{Read, Write}; use std::os::fd::AsRawFd; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use inputd::{Cmd, VtActivate, VtMode}; use spin::Mutex; use orbclient::{Event, EventOption}; use syscall::{Error as SysError, EventFlags, Packet, SchemeMut, EINVAL}; enum Handle { Producer, Consumer { events: EventFlags, pending: Vec, notified: bool, vt: usize, }, Device { device: String, }, } impl Handle { pub fn is_producer(&self) -> bool { matches!(self, Handle::Producer) } } /// VT Inner State /// /// This is *required* to be lazily initialized since opening the handle to the display /// requires the system call to return first. Otherwise, it will block indefinitely. struct VtInner { handle_file: File, mode: VtMode, } struct Vt { display: String, index: usize, inner: spin::Once>, } impl Vt { pub fn new(display: D, index: usize) -> Arc where D: Into, { Arc::new(Self { display: display.into(), inner: spin::Once::new(), index, }) } pub fn inner(&self) -> &Mutex { self.inner.call_once(|| { let handle_file = File::open(format!("{}:handle", self.display)).unwrap(); Mutex::new(VtInner { handle_file, mode: VtMode::Default, }) }) } } struct InputScheme { handles: BTreeMap, next_id: AtomicUsize, next_vt_id: AtomicUsize, vts: BTreeMap>, super_key: bool, active_vt: Option>, todo: Vec, } impl InputScheme { pub fn new() -> Self { Self { next_id: AtomicUsize::new(0), next_vt_id: AtomicUsize::new(1), handles: BTreeMap::new(), vts: BTreeMap::new(), super_key: false, active_vt: None, todo: vec![], } } } impl SchemeMut for InputScheme { fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> syscall::Result { let mut path_parts = path.split('/'); let command = path_parts.next().ok_or(SysError::new(EINVAL))?; let fd = self.next_id.fetch_add(1, Ordering::SeqCst); let handle_ty = match command { "producer" => Handle::Producer, "consumer" => { let target = path_parts .next() .and_then(|x| x.parse::().ok()) .ok_or(SysError::new(EINVAL))?; Handle::Consumer { events: EventFlags::empty(), pending: Vec::new(), notified: false, vt: target, } } "handle" => { let display = path_parts.collect::>().join("."); Handle::Device { device: display } } _ => { log::error!("inputd: invalid path {path}"); return Err(SysError::new(EINVAL)); } }; log::info!("inputd: {path} channel has been opened"); self.handles.insert(fd, handle_ty); Ok(fd) } fn fpath(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { let handle = self.handles.get(&id).ok_or(SysError::new(EINVAL))?; if let Handle::Consumer { vt, .. } = handle { let display = self.vts.get(vt).ok_or(SysError::new(EINVAL))?; let vt = format!("{}:{vt}", display.display); let size = core::cmp::min(vt.len(), buf.len()); buf[..size].copy_from_slice(&vt.as_bytes()[..size]); Ok(size) } else { Err(SysError::new(EINVAL)) } } fn read(&mut self, id: usize, buf: &mut [u8]) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; match handle { Handle::Consumer { pending, .. } => { let copy = core::cmp::min(pending.len(), buf.len()); for (i, byte) in pending.drain(..copy).enumerate() { buf[i] = byte; } Ok(copy) } Handle::Device { device } => { assert!(buf.is_empty()); let vt = self.next_vt_id.fetch_add(1, Ordering::SeqCst); self.vts.insert(vt, Vt::new(device.clone(), vt)); Ok(vt) } _ => unreachable!(), } } fn write(&mut self, id: usize, buf: &[u8]) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; match handle { Handle::Device { device } => { assert!(buf.len() == core::mem::size_of::()); // SAFETY: We have verified the size of the buffer above. let cmd = unsafe { &*buf.as_ptr().cast::() }; self.vts.insert(cmd.vt, Vt::new(device.clone(), cmd.vt)); self.todo.push(cmd.clone()); return Ok(buf.len()); } Handle::Consumer { .. } => unreachable!(), _ => {} } if buf.len() == 1 && buf[0] > 0xf4 { return Ok(1); } let events = unsafe { core::slice::from_raw_parts( buf.as_ptr() as *const Event, buf.len() / core::mem::size_of::(), ) }; 'out: for event in events.iter() { let mut new_active_opt = None; match event.to_option() { EventOption::Key(key_event) => match key_event.scancode { f @ 0x3B..=0x44 if self.super_key => { // F1 through F10 new_active_opt = Some((f - 0x3A) as usize); } 0x57 if self.super_key => { // F11 new_active_opt = Some(11); } 0x58 if self.super_key => { // F12 new_active_opt = Some(12); } 0x5B => { // Super self.super_key = key_event.pressed; } _ => (), }, EventOption::Resize(resize_event) => { let active_vt = self.active_vt.as_ref().unwrap(); let mut vt_inner = active_vt.inner().lock(); inputd::send_comand( &mut vt_inner.handle_file, Cmd::Resize { vt: active_vt.index, width: resize_event.width, height: resize_event.height, // TODO(andypython): Figure out how to get the stride. stride: resize_event.width, }, )?; } _ => continue, } if let Some(new_active) = new_active_opt { if new_active == self.active_vt.as_ref().unwrap().index { continue 'out; } if let Some(new_active) = self.vts.get(&new_active).cloned() { { let active_vt = self.active_vt.as_ref().unwrap(); let mut vt_inner = active_vt.inner().lock(); inputd::send_comand( &mut vt_inner.handle_file, Cmd::Deactivate(active_vt.index), )?; } let mut vt_inner = new_active.inner().lock(); inputd::send_comand( &mut vt_inner.handle_file, Cmd::Activate { vt: new_active.index, mode: VtMode::Default, }, )?; self.active_vt = Some(new_active.clone()); } else { log::warn!("inputd: switch to non-existent VT #{new_active} was requested"); } } } assert!(handle.is_producer()); let active_vt = self.active_vt.as_ref().unwrap(); for handle in self.handles.values_mut() { match handle { Handle::Consumer { pending, notified, vt, .. } => { if *vt != active_vt.index { continue; } pending.extend_from_slice(buf); *notified = false; } _ => continue, } } Ok(buf.len()) } fn fevent( &mut self, id: usize, flags: syscall::EventFlags, ) -> syscall::Result { let handle = self.handles.get_mut(&id).ok_or(SysError::new(EINVAL))?; match handle { Handle::Consumer { ref mut events, ref mut notified, .. } => { *events = flags; *notified = false; } _ => unreachable!(), } Ok(EventFlags::empty()) } fn close(&mut self, _id: usize) -> syscall::Result { Ok(0) } } fn deamon(deamon: redox_daemon::Daemon) -> anyhow::Result<()> { // Create the ":input" scheme. let mut socket_file = File::create(":input")?; let mut scheme = InputScheme::new(); deamon.ready().unwrap(); loop { let mut should_handle = false; let mut packet = Packet::default(); socket_file.read(&mut packet)?; // The producer has written to the channel; the consumers should be notified. if packet.a == syscall::SYS_WRITE { should_handle = true; } scheme.handle(&mut packet); socket_file.write(&packet)?; while let Some(cmd) = scheme.todo.pop() { let vt = scheme.vts.get_mut(&cmd.vt).unwrap(); let mut vt_inner = vt.inner().lock(); // Failing to activate a VT is not a fatal error. if let Err(err) = inputd::send_comand( &mut vt_inner.handle_file, Cmd::Activate { vt: vt.index, mode: cmd.mode, }, ) { log::error!("inputd: failed to activate VT #{}: {err}", vt.index) } vt_inner.mode = cmd.mode; scheme.active_vt = Some(vt.clone()); } if !should_handle { continue; } for (id, handle) in scheme.handles.iter_mut() { if let Handle::Consumer { events, pending, ref mut notified, vt, } = handle { if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { continue; } let active_vt = scheme.active_vt.as_ref().unwrap(); // The activate VT is not the same as the VT that the consumer is listening to // for events. if active_vt.index != *vt { continue; } // Notify the consumer that we have some events to read. Yum yum. let mut event_packet = Packet::default(); event_packet.a = syscall::SYS_FEVENT; event_packet.b = *id; event_packet.c = EventFlags::EVENT_READ.bits(); // Specifies the number of bytes that can be read non-blocking. event_packet.d = pending.len(); socket_file.write(&event_packet)?; *notified = true; } } } } fn daemon_runner(redox_daemon: redox_daemon::Daemon) -> ! { deamon(redox_daemon).unwrap(); unreachable!(); } #[cfg(target_os = "redox")] pub fn setup_logging(level: log::LevelFilter, name: &str) { use redox_log::{OutputBuilder, RedoxLogger}; let mut logger = RedoxLogger::new().with_output( OutputBuilder::stderr() .with_filter(level) .with_ansi_escape_codes() .flush_on_newline(true) .build(), ); match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.log")) { Ok(builder) => { logger = logger.with_output(builder.with_filter(level).flush_on_newline(true).build()) } Err(err) => eprintln!("inputd: failed to create log: {}", err), } match OutputBuilder::in_redox_logging_scheme("disk", "pcie", format!("{name}.ansi.log")) { Ok(builder) => { logger = logger.with_output( builder .with_filter(level) .with_ansi_escape_codes() .flush_on_newline(true) .build(), ) } Err(err) => eprintln!("inputd: failed to create ANSI log: {}", err), } logger.enable().unwrap(); log::info!("inputd: enabled logger"); } pub fn main() { #[cfg(target_os = "redox")] setup_logging(log::LevelFilter::Trace, "inputd"); let mut args = std::env::args().skip(1); if let Some(val) = args.next() { match val.as_ref() { // Sets the VT mode of the specified VT to [`VtMode::Graphic`]. "-G" => { let vt = args.next().unwrap().parse::().unwrap(); // On startup, the VESA display driver is started which basically makes use of the framebuffer // provided by the firmware. The GPU devices are latter started by `pcid` (such as `virtio-gpu`). let mut devices = vec![]; let schemes = std::fs::read_dir(":").unwrap(); for entry in schemes { let path = entry.unwrap().path(); let path_str = path .into_os_string() .into_string() .expect("inputd: failed to convert path to string"); if path_str.contains("display") { println!("inputd: found display scheme {}", path_str); devices.push(path_str); } } if devices.is_empty() { // No display devices found. return; } let device = devices .iter() .filter(|d| !d.contains("vesa")) .collect::>(); let device = if device.is_empty() { "vesa" } else { // TODO: What should we do when there are multiple display devices? device[0][2..].split('.').nth(1).unwrap() }; let mut handle = inputd::Handle::new(device).expect("inputd: failed to open display handle"); handle .activate(vt, VtMode::Graphic) .expect("inputd: failed to activate VT in graphic mode"); } // Activates a VT. "-A" => { let vt = args.next().unwrap().parse::().unwrap(); let handle = File::open(format!("input:consumer/{vt}")) .expect("inputd: failed to open consumer handle"); let mut display_path = [0; 4096]; let written = syscall::fpath(handle.as_raw_fd() as usize, &mut display_path) .expect("inputd: fpath() failed"); assert!(written <= display_path.len()); drop(handle); let display_path = std::str::from_utf8(&display_path[..written]) .expect("inputd: display path UTF-8 validation failed"); let display_name = display_path .split('/') .skip(1) .next() .expect("inputd: invalid display path"); let display_scheme = display_name .split(':') .next() .expect("inputd: invalid display path"); let mut handle = inputd::Handle::new(display_scheme) .expect("inputd: failed to open display handle"); handle .activate(vt, VtMode::Default) .expect("inputd: failed to activate VT"); } _ => panic!("inputd: invalid argument: {}", val), } } else { redox_daemon::Daemon::new(daemon_runner).expect("virtio-core: failed to daemonize"); } }